Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload saved view #122

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions plugins/utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ view = fo.DatasetView._build(dataset, src_view._serialize())
where the operator's form allows you to choose the source dataset and view to
apply.

### reload_saved_view

You can use this operator to reload saved views on a dataset that are
_generated_ (patches, frames, or clips).

This operator is essentially a wrapper around the following pattern:

```py
view = dataset.load_saved_view(name)
view.reload()

dataset.save_view(name, view, overwrite=True)
```

where the operator's form allows you to select the saved view to reload.

### compute_metadata

You can use this operator to populate the `metadata` field of a collection.
Expand Down
135 changes: 135 additions & 0 deletions plugins/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
|
"""
import contextlib
from datetime import datetime
import json
import multiprocessing.dummy

from bson import json_util
import humanize

import eta.core.utils as etau

Expand Down Expand Up @@ -1418,6 +1420,138 @@ def serialize_view(view):
return json.loads(json_util.dumps(view._serialize()))


class ReloadSavedView(foo.Operator):
@property
def config(self):
return foo.OperatorConfig(
name="reload_saved_view",
label="Reload saved view",
light_icon="/assets/icon-light.svg",
dark_icon="/assets/icon-dark.svg",
dynamic=True,
)

def resolve_placement(self, ctx):
if ctx.view._is_generated and ctx.view.name is not None:
return types.Placement(
types.Places.SAMPLES_GRID_ACTIONS,
types.Button(
label="Reload saved view",
icon="/assets/autorenew.svg",
prompt=True,
),
)

def resolve_input(self, ctx):
inputs = types.Object()

_get_reload_saved_view_inputs(ctx, inputs)

view = types.View(label="Reload saved view")
return types.Property(inputs, view=view)

def execute(self, ctx):
name = ctx.params["name"]

view = ctx.dataset.load_saved_view(name)
view.reload()

view_doc = ctx.dataset._get_saved_view_doc(name)
view_doc.view_stages = [
json_util.dumps(s) for s in view._serialize(include_uuids=False)
]
# `view_stages` may not have changed so we force `last_modified_at` to
# update just in case
view_doc.last_modified_at = datetime.utcnow()
view_doc.save()

if ctx.view.name == view.name:
ctx.trigger("set_view", params={"name": name})
ctx.trigger("reload_dataset")


def _get_reload_saved_view_inputs(ctx, inputs):
saved_views = _get_generated_saved_views(ctx.dataset)

if not saved_views:
warning = types.Warning(
label="This dataset has no saved views that need reloading"
)
prop = inputs.view("warning", warning)
prop.invalid = True
return

view_choices = types.AutocompleteView()
for name in saved_views:
view_choices.add_choice(name, label=name)

if ctx.view.name in saved_views:
default = ctx.view.name
else:
default = None

inputs.enum(
"name",
view_choices.values(),
default=default,
required=True,
label="Saved view",
description="The name of a saved view to reload",
view=view_choices,
)

name = ctx.params.get("name", None)
if name not in saved_views:
return

# @todo can remove this if we require `fiftyone>=1.0`
if not ctx.dataset.has_field("last_modified_at"):
return

last_modified_at = ctx.dataset._get_last_modified_at()
if ctx.dataset._contains_videos(any_slice=True):
last_modified_at = max(
last_modified_at,
ctx.dataset._get_last_modified_at(frames=True),
)

view_doc = ctx.dataset._get_saved_view_doc(name)

if last_modified_at > view_doc.last_modified_at:
dt = last_modified_at - view_doc.last_modified_at
dt_str = humanize.naturaldelta(dt)
view = types.Notice(
label=(
f"Saved view '{name}' may need to be reloaded.\n\n"
f"The dataset's samples were last modified {dt_str} after the "
"view was generated."
)
)
inputs.view("notice", view)
else:
dt = view_doc.last_modified_at - last_modified_at
dt_str = humanize.naturaldelta(dt)
view = types.Notice(
label=(
f"Saved view '{name}' may not need to be reloaded.\n\n"
f"It was generated {dt_str} after the last known modification "
"to the dataset's current samples."
)
)
inputs.view("notice", view)


def _get_generated_saved_views(dataset):
generated_views = set()

for view_doc in dataset._doc.saved_views:
for stage_str in view_doc.view_stages:
if '"_state"' in stage_str:
generated_views.add(view_doc.name)

return sorted(generated_views)


class ComputeMetadata(foo.Operator):
@property
def config(self):
Expand Down Expand Up @@ -2191,6 +2325,7 @@ def register(p):
p.register(DeleteDataset)
p.register(DeleteSamples)
p.register(ApplySavedView)
p.register(ReloadSavedView)
p.register(ComputeMetadata)
p.register(GenerateThumbnails)
p.register(Delegate)
1 change: 1 addition & 0 deletions plugins/utils/assets/autorenew.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions plugins/utils/fiftyone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ operators:
- delete_dataset
- delete_samples
- apply_saved_view
- reload_saved_view
- compute_metadata
- generate_thumbnails
- delegate