Skip to content

Conversation

@hongquanli
Copy link
Contributor

Summary

  • Add "From Center" z-stack mode to both WellplateMultiPointWidget and FlexibleMultiPointWidget
  • Implement autofocus restrictions based on z-stack mode:
    • Contrast AF: only enabled for "From Center" mode (focus plane is at center)
    • Laser AF: only enabled for "From Bottom" mode (can find surface before z-stack)
  • Add warnings when YAML/cache loading encounters unsupported z-modes or AF conflicts
  • Simplify worker's prepare_z_stack() - UI now calculates correct z_range for each mode

Test plan

  • Test "From Center" mode: verify z-stack is centered around current position
  • Test "From Bottom" mode: verify z-stack starts at current position going up
  • Test AF checkbox states update correctly when changing z-mode
  • Test YAML loading with different z_stacking_config values shows appropriate warnings
  • Test cache loading restores z-mode and syncs AF checkbox states

🤖 Generated with Claude Code

hongquanli and others added 6 commits January 28, 2026 01:30
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>
Copy link
Contributor

Copilot AI left a 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 ZStackMode enum 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 a print with 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 via on_z_stack_mode_changed(), which is unrelated to the Set Z-range checkbox. Consider treating Set Z-range as ZStackMode.SET_RANGE and calling update_autofocus_checkboxes(...) (and possibly set_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):
Copy link

Copilot AI Jan 28, 2026

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.

Suggested change
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()

Copilot uses AI. Check for mistakes.
Comment on lines 8989 to 9001
# 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)
Copy link

Copilot AI Jan 28, 2026

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).

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,318 @@
"""Tests for z-stack mode helpers in widgets.py."""

import logging
Copy link

Copilot AI Jan 28, 2026

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.

Suggested change
import logging

Copilot uses AI. Check for mistakes.
"""Tests for z-stack mode helpers in widgets.py."""

import logging
from unittest.mock import MagicMock, Mock
Copy link

Copilot AI Jan 28, 2026

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.

Suggested change
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants