-
Notifications
You must be signed in to change notification settings - Fork 47
feat: Add 'From Center' z-stack mode with autofocus restrictions #483
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: master
Are you sure you want to change the base?
Conversation
Adds a new z-stack acquisition mode that centers the z-stack around the current focus position, in addition to the existing "From Bottom" mode. Changes: - WellplateMultiPointWidget: Add "From Center" option to z-mode dropdown - FlexibleMultiPointWidget: Connect existing combobox_z_stack to controller - Both widgets: Calculate correct z_range for each mode (always [bottom, top]) - Worker: Remove redundant offset calculation in prepare_z_stack() - Fix: Sync z_stacking_config when loading from cache or YAML Design: z_range is always [bottom, top] in absolute coordinates. The UI calculates the correct range based on mode, and the worker just executes. For FROM_CENTER, the stage returns to the center (user's focus position) after acquisition completes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Autofocus options are now enabled/disabled based on z-stack mode: - Contrast AF: only enabled for "From Center" (focus plane is at center) - Laser AF: only enabled for "From Bottom" (can find surface first) Both AF options are disabled for "From Top" and "Set Range" modes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Warn when YAML has z_stacking_config='FROM TOP' in wellplate mode (not supported, falls back to 'From Bottom') - Warn when AF settings from YAML are disabled due to z-mode restrictions (e.g., contrast AF disabled when z-mode is not 'From Center') Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ZStackMode IntEnum with FROM_BOTTOM, FROM_CENTER, FROM_TOP, SET_RANGE - Enum properties: allows_contrast_af, allows_laser_af, worker_config_index - Extract shared helpers: calculate_z_range(), update_autofocus_checkboxes(), log_af_restriction_warnings() - Fix conflicting AF checkbox management in WellplateMultiPointWidget - Fix silent failures: add validation and logging for invalid z-modes - Replace print() with proper logging in set_z_stacking_config() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- TestZStackModeEnum: 11 tests for enum properties and values - TestCalculateZRange: 5 tests for z-range calculation - TestUpdateAutofocusCheckboxes: 7 tests for AF checkbox behavior - TestLogAfRestrictionWarnings: 5 tests for warning logging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.
Pull request overview
Adds a new “From Center” z-stack mode to the multipoint UI and enforces autofocus (AF) compatibility rules based on the selected z-stack mode, with additional YAML/cache load warnings and helper-unit tests.
Changes:
- Introduces a shared
ZStackModeenum plus helpers to compute z-range and enable/disable AF checkboxes. - Updates both multipoint widgets to expose “From Center” and synchronize AF checkbox state + YAML/cache loading behavior.
- Simplifies the worker’s
prepare_z_stack()and replaces aprintwith structured logging in the controller.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| software/control/widgets.py | Adds ZStackMode + z-range/AF helpers; wires “From Center” mode into UI, YAML/cache sync, and z-range calculation. |
| software/control/core/multi_point_worker.py | Removes FROM CENTER repositioning in prepare_z_stack() (now only stabilization sleep). |
| software/control/core/multi_point_controller.py | Uses logger instead of print, and warns on invalid z-stacking indices. |
| software/tests/control/test_z_stack_mode.py | Adds unit tests for ZStackMode and helper functions. |
Comments suppressed due to low confidence (1)
software/control/widgets.py:5883
- In FlexibleMultiPointWidget, enabling “Set Z-range” no longer updates autofocus checkbox states.
toggle_z_range_controls()used to disable reflection AF when the Z-range UI is shown, but now AF is only updated viaon_z_stack_mode_changed(), which is unrelated to the Set Z-range checkbox. Consider treating Set Z-range asZStackMode.SET_RANGEand callingupdate_autofocus_checkboxes(...)(and possiblyset_z_stacking_config(mode.worker_config_index)) when the checkbox toggles so AF restrictions stay consistent.
def toggle_z_range_controls(self, state):
is_visible = bool(state)
# Hide/show widgets in z_min_layout
for i in range(self.z_min_layout.count()):
widget = self.z_min_layout.itemAt(i).widget()
if widget is not None:
widget.setVisible(is_visible)
widget = self.z_max_layout.itemAt(i).widget()
if widget is not None:
widget.setVisible(is_visible)
# Enable/disable NZ entry based on the inverse of is_visible
self.entry_NZ.setEnabled(not is_visible)
current_z = self.stage.get_pos().z_mm * 1000
self.entry_minZ.setValue(current_z)
if is_visible:
self._reset_reflection_af_reference()
self.entry_maxZ.setValue(current_z)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1282,10 +1282,7 @@ def perform_autofocus(self, region_id, fov): | |||
| return True | |||
|
|
|||
| def prepare_z_stack(self): | |||
Copilot
AI
Jan 28, 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.
prepare_z_stack() no longer repositions the stage for FROM CENTER, but perform_autofocus() still runs before prepare_z_stack() and assumes the current Z is the stack center, and move_z_back_after_stack() still contains FROM CENTER-specific logic. With the new behavior, subsequent FOVs will start imaging from the AF plane (center) instead of the stack start, and the first FOV may autofocus at the stack start (z_range[0]) rather than the center. Either restore a FROM CENTER reposition here (preferably moving to z_range[0] relative to the post-AF center), or update the worker to consistently use z_range/midpoint for AF and stack start/end across FOVs.
| def prepare_z_stack(self): | |
| def prepare_z_stack(self): | |
| # For FROM CENTER stacking, autofocus leaves us at the stack center. | |
| # Move to the start of the z range (relative to this center) before imaging. | |
| if getattr(self, "NZ", 1) > 1 and getattr(self, "z_stacking_config", None) == "FROM CENTER": | |
| try: | |
| start_offset_um = self.z_range[0] | |
| except (AttributeError, IndexError, TypeError): | |
| start_offset_um = None | |
| if start_offset_um is not None: | |
| self._log.info( | |
| "Preparing z stack (FROM CENTER): moving to start offset %.3f µm from AF plane", | |
| start_offset_um, | |
| ) | |
| # z_range values are in µm; convert to mm for the stage.move_z API. | |
| self.stage.move_z(start_offset_um / 1000.0) | |
| self.wait_till_operation_is_completed() |
| # Z mode - map YAML config to combobox text | ||
| z_mode_map = { | ||
| "FROM BOTTOM": "From Bottom", | ||
| "FROM CENTER": "From Center", | ||
| "SET RANGE": "Set Range", | ||
| } | ||
| z_mode = z_mode_map.get(yaml_data.z_stacking_config, "From Bottom") | ||
| if yaml_data.z_stacking_config == "FROM TOP": | ||
| self._log.warning( | ||
| f"YAML has z_stacking_config='FROM TOP' which is not supported in wellplate mode. " | ||
| f"Using 'From Bottom' instead." | ||
| ) | ||
| self.combobox_z_mode.setCurrentText(z_mode) |
Copilot
AI
Jan 28, 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.
Wellplate YAML z-mode parsing only warns for FROM TOP, but any other unknown z_stacking_config value silently falls back to From Bottom via dict.get(..., "From Bottom"). If the intent is to warn on unsupported/unknown YAML values (per PR description and the flexible widget behavior), add a warning when yaml_data.z_stacking_config is not in z_mode_map (and not one of the explicitly handled unsupported values).
| @@ -0,0 +1,318 @@ | |||
| """Tests for z-stack mode helpers in widgets.py.""" | |||
|
|
|||
| import logging | |||
Copilot
AI
Jan 28, 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.
Import of 'logging' is not used.
| import logging |
| """Tests for z-stack mode helpers in widgets.py.""" | ||
|
|
||
| import logging | ||
| from unittest.mock import MagicMock, Mock |
Copilot
AI
Jan 28, 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.
Import of 'Mock' is not used.
| from unittest.mock import MagicMock, Mock | |
| from unittest.mock import MagicMock |
Summary
prepare_z_stack()- UI now calculates correct z_range for each modeTest plan
🤖 Generated with Claude Code