-
Notifications
You must be signed in to change notification settings - Fork 5
Allow manual editing of registration result, including for time lapses #20
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
Changes from all commits
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fusion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spatial_image_utils, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msi_utils, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param_utils, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari.layers import Image, Labels | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -103,6 +104,23 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Alternating pattern': 'alternating_pattern', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_method = widgets.ComboBox( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| choices=['Phase Correlation', 'ITKElastix'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value='Phase Correlation', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label='Registration method:', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tooltip='Choose the pairwise registration method.\n' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '"Phase Correlation" is fast and works well for translation.\n' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '"ITKElastix" supports more transform types but requires the itk-elastix package.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.antspy_transform_types = widgets.Select( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| choices=['Translation', 'Rigid', 'Affine'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value=['Translation', 'Rigid'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label='Transform types:', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tooltip='Sequence of transform types applied in order. The last selected type is also used for global optimization.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_method.changed.connect(self._on_reg_method_changed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._on_reg_method_changed() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.button_stitch = widgets.Button(text='Register', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tooltip='Use the overlaps between tiles to determine their relative positions.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -140,7 +158,12 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.pair_pruning_method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets = self.reg_config_widgets_basic + self.reg_config_widgets_advanced | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets_method = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.antspy_transform_types, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets = self.reg_config_widgets_basic + self.reg_config_widgets_advanced + self.reg_config_widgets_method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Initialize tab screen | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets_tabs = QTabWidget() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -150,7 +173,9 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets_tabs.addTab( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgets.VBox(widgets=self.reg_config_widgets_basic).native, "Basic") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets_tabs.addTab( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgets.VBox(widgets=self.reg_config_widgets_advanced).native, "More") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgets.VBox(widgets=self.reg_config_widgets_advanced).native, "More") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.reg_config_widgets_tabs.addTab( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| widgets.VBox(widgets=self.reg_config_widgets_method).native, "Method") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.visualization_widgets = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.visualization_type_rbuttons, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -202,11 +227,16 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.fused_layers = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.params = dict() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # flag to suppress watch_layer_changes during programmatic affine updates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._updating_viewer = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # last timepoint that was applied to the viewer; used to skip no-op current_step events | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._last_applied_tp = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # create temporary directory for storing dask arrays | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.tmpdir = tempfile.TemporaryDirectory() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.visualization_type_rbuttons.changed.connect(self.update_viewer_transformations) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.dims.events.connect(self.update_viewer_transformations) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.dims.events.current_step.connect(self.update_viewer_transformations) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.button_stitch.clicked.connect(self.run_registration) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # self.button_stabilize.clicked.connect(self.run_stabilization) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -216,20 +246,45 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.button_load_layers_sel.clicked.connect(self.load_layers_sel) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _on_reg_method_changed(self, event=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Show/hide transform type widget based on the selected method.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.antspy_transform_types.visible = self.reg_method.value == 'ITKElastix' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def update_viewer_transformations(self, event=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set transformations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - for current timepoint | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - for each (compatible) layer loaded in viewer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Called from exactly two sources: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. viewer.dims.events.current_step (timepoint scroll) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. visualization_type_rbuttons.changed (Show toggle) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # events are constantly triggered by viewer.dims.events, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # but we only want to update if current_step changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasattr(event, 'type') and\ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.type != 'current_step': return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except AttributeError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # When called from a current_step event: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - only proceed after registration has been performed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - only proceed when the timepoint actually changed (transform-mode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # interactions also fire current_step without changing the tp) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasattr(event, 'type'): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.visualization_type_rbuttons.enabled: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+265
to
+270
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # - only proceed after registration has been performed | |
| # - only proceed when the timepoint actually changed (transform-mode | |
| # interactions also fire current_step without changing the tp) | |
| if hasattr(event, 'type'): | |
| if not self.visualization_type_rbuttons.enabled: | |
| return | |
| # - allow metadata-based per-timepoint updates even before | |
| # registration has been performed | |
| # - only proceed when the timepoint actually changed (transform-mode | |
| # interactions also fire current_step without changing the tp) | |
| if hasattr(event, 'type'): |
Copilot
AI
Apr 12, 2026
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.
Avoid bare except: here: it can hide unexpected errors (including bugs inside get_affine_from_sim) and makes failures silent. Prefer except Exception as e and either re-raise in debug/test contexts or at least log/notify with the exception message so users can diagnose missing/invalid transforms.
Copilot
AI
Apr 12, 2026
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.
This second bare except: also swallows all error types and can make diagnosing malformed parameters difficult. Consider catching specific exceptions for missing t coordinates / selection errors (e.g. KeyError, IndexError, ValueError) and use except Exception as e as a fallback that preserves the error details in the notification.
| raise(Exception()) | |
| except: | |
| notifications.notification_manager.receive_info( | |
| 'Timepoint %s: no parameters available, register first.' % curr_tp) | |
| continue | |
| raise ValueError("parameters contain NaN values") | |
| except (KeyError, IndexError, ValueError) as e: | |
| notifications.notification_manager.receive_info( | |
| 'Timepoint %s: no parameters available, register first. Details: %s' | |
| % (curr_tp, e)) | |
| continue | |
| except Exception as e: | |
| notifications.notification_manager.receive_info( | |
| 'Timepoint %s: unable to apply parameters. Details: %s' | |
| % (curr_tp, e)) | |
| continue |
Copilot
AI
Apr 12, 2026
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.
_capture_layer_transforms_to_msims builds an xarray affine using t_coords and a single matrix from l.affine, which will broadcast the current affine to all timepoints. For time-lapse datasets with time-varying affine_metadata, this overwrites all frames and loses stage-shift history. Instead, when t is present, update only the current timepoint entry in the existing affine_metadata (similar to _update_registered_param_for_current_tp).
| t_coords = sim.coords['t'] if 't' in sim.dims else None | |
| affine_xr = param_utils.affine_to_xaffine(affine, t_coords=t_coords) | |
| if 't' in sim.dims: | |
| # Preserve existing per-timepoint affine_metadata and update only | |
| # the current timepoint, mirroring registered transform updates. | |
| sdims = spatial_image_utils.get_spatial_dims_from_sim(sim) | |
| if len(self.viewer.dims.current_step) > len(sdims): | |
| curr_tp = self.viewer.dims.current_step[-len(sdims) - 1] | |
| else: | |
| curr_tp = 0 | |
| try: | |
| affine_xr = spatial_image_utils.get_affine_from_sim( | |
| sim, transform_key='affine_metadata').copy() | |
| except Exception: | |
| affine_xr = param_utils.affine_to_xaffine( | |
| affine, t_coords=sim.coords['t']) | |
| if 't' in affine_xr.dims: | |
| t_val = sim.coords['t'][curr_tp] | |
| affine_xr.loc[{'t': t_val}] = affine | |
| else: | |
| affine_xr = param_utils.affine_to_xaffine( | |
| affine, t_coords=sim.coords['t']) | |
| t_val = sim.coords['t'][curr_tp] | |
| affine_xr.loc[{'t': t_val}] = affine | |
| else: | |
| affine_xr = param_utils.affine_to_xaffine(affine, t_coords=None) |
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.
The widget is labeled for ITKElastix, but the variable name
antspy_transform_typessuggests ANTsPy. Renaming this to something likeelastix_transform_types(and updating downstream uses) would avoid confusion and make the UI code easier to maintain.