Repository navigation
Add frame and full pose reset controls #1137
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
64ec794
0367d71
9da32a5
d46b5c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,7 @@ | |
| DATASET_ATTRS_KEY: str = "movement_dataset_attrs" | ||
| TRACKS_LAYER_KEY: str = "movement_tracks_layer" | ||
| MAX_FRAME_IDX_KEY: str = "movement_max_frame_idx" | ||
| POINTS_BASELINE_KEY: str = "movement_points_baseline" | ||
|
|
||
| # Keep a set of viewers already wired by connect_viewer_callbacks, | ||
| # so we don't wire them twice. We use a WeakSet so tracking a viewer here | ||
|
|
@@ -186,6 +187,8 @@ def on_points_data_changed(event): | |
| layer = event.source | ||
| if not isinstance(layer, Points): | ||
| return | ||
| if getattr(event, "movement_reset", False) is True: | ||
| return | ||
|
|
||
| if event.action == ActionType.CHANGED: | ||
| moved_indices = list(event.data_indices) | ||
|
|
@@ -267,3 +270,89 @@ def set_tracks_layer_data(tracks_layer, data, properties): | |
| tracks_layer.data = data | ||
| tracks_layer.properties = properties | ||
| tracks_layer.color_by = color_by | ||
|
|
||
|
|
||
| def capture_points_baseline(points_layer: Points) -> None: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of storing a separate snapshot here, could we reuse the load-time state we already keep on the layer?
The reset buttons would then check for the new key instead of I tried this on a branch to check that it works: pr-1137-reset-from-original-position. Let me know what you think of this. We can further discuss this. Therefore, I would remove this |
||
| """Store independent copies of pose data immediately after loading. | ||
|
|
||
| The baseline includes edits saved in the input file. It is owned by the | ||
| layer, not the loader widget, and is never updated by subsequent edits. | ||
| """ | ||
| tracks = points_layer.metadata[TRACKS_LAYER_KEY] | ||
| points_layer.metadata[POINTS_BASELINE_KEY] = { | ||
| "data": tracks.data.copy(), | ||
| "points_properties": { | ||
| key: value.copy() for key, value in points_layer.properties.items() | ||
| }, | ||
| "tracks_properties": { | ||
| key: value.copy() for key, value in tracks.properties.items() | ||
| }, | ||
| "symbol": points_layer.symbol.copy(), | ||
| } | ||
|
|
||
|
|
||
| def reset_points_to_baseline( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this is more a personal taste, but what about changing this name to |
||
| points_layer: Points, frame: int | None = None | ||
| ) -> None: | ||
| """Restore loaded pose points for one frame, or all frames if None. | ||
|
|
||
| Deleted points are restored from the baseline, rather than inferred | ||
| from surviving edited flags. Other frames retain their current data. | ||
| Identity swaps and individual edit history are not supported. | ||
| """ | ||
| baseline = points_layer.metadata[POINTS_BASELINE_KEY] | ||
| tracks = points_layer.metadata[TRACKS_LAYER_KEY] | ||
| restore = ( | ||
| baseline["data"][:, 1] == frame | ||
| if frame is not None | ||
| else np.ones(len(baseline["data"]), dtype=bool) | ||
| ) | ||
| keep = ( | ||
| tracks.data[:, 1] != frame | ||
| if frame is not None | ||
| else np.zeros(len(tracks.data), dtype=bool) | ||
| ) | ||
| data = np.concatenate([tracks.data[keep], baseline["data"][restore]]) | ||
| order = np.lexsort((data[:, 1], data[:, 0])) | ||
| data = data[order] | ||
|
|
||
| def merge_properties(current, original): | ||
| keys = current.keys() | original.keys() | ||
| return { | ||
| key: np.concatenate( | ||
| [ | ||
| current.get(key, np.zeros(len(tracks.data), dtype=bool))[ | ||
| keep | ||
| ], | ||
| original.get( | ||
| key, np.zeros(len(baseline["data"]), dtype=bool) | ||
| )[restore], | ||
| ] | ||
| )[order] | ||
| for key in keys | ||
| } | ||
|
|
||
| properties = merge_properties( | ||
| points_layer.properties, baseline["points_properties"] | ||
| ) | ||
| track_properties = merge_properties( | ||
| tracks.properties, baseline["tracks_properties"] | ||
| ) | ||
| symbols = np.concatenate( | ||
| [points_layer.symbol[keep], baseline["symbol"][restore]] | ||
| )[order] | ||
| # Avoid interpreting restoration as a user drag or deletion. Notify | ||
| # observers once, after Points and Tracks are consistent again. | ||
| with points_layer.events.data.blocker(): | ||
| points_layer.data = data[:, 1:].copy() | ||
| points_layer.properties = properties | ||
| points_layer.symbol = symbols | ||
| points_layer.selected_data = set() | ||
| set_tracks_layer_data(tracks, data, track_properties) | ||
| points_layer.events.data( | ||
| value=points_layer.data, | ||
| action=ActionType.CHANGED, | ||
| data_indices=(), | ||
| movement_reset=True, | ||
| frame=frame, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| POINTS_LAYER_KEY, | ||
| POINTS_PROPERTIES_KEY, | ||
| TRACKS_LAYER_KEY, | ||
| capture_points_baseline, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do what I suggested, we would remove this because it would no longer exist. Instead, we would add |
||
| connect_viewer_callbacks, | ||
| frame_axis_is_sliced, | ||
| on_points_data_changed, | ||
|
|
@@ -212,6 +213,8 @@ def _on_load_clicked(self): | |
| # and a boxes layer if the dataset is a bounding boxes one | ||
| self._add_points_layer() | ||
| self._add_tracks_layer() | ||
| if self.data_bboxes is None: | ||
| capture_points_baseline(self.points_layer) | ||
| if self.data_bboxes is not None: | ||
| self._add_boxes_layer() | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is currently a PR #1126 updating the user guide with the edit timeline widget. Your reset feature should be described inside that section once that PR is approved.
Based on the style used for that part of the user guide, I would suggest the following:
Take this as a suggestion, we can further discussed once #1126 is merged.