Skip to content

Commit 348d3ab

Browse files
Merge pull request #1211 from annie-xd-wang/fix-z-stack-setting
set up z-stack start and end positions according to primary z axis
2 parents 0d68c5a + fced2ac commit 348d3ab

7 files changed

Lines changed: 81 additions & 14 deletions

File tree

‎docs/source/01_getting_started/06_acquiring_data.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ Acquiring a Z-Stack
175175
:align: center
176176
:alt: Adjusting the stage position in **navigate**.
177177

178+
* If the microscope has more than one stage that can move in the Z direction, choose the stage that should perform the stack from :guilabel:`Z-Stack Device` before setting the start and end positions. This selected device is the primary Z-stack stage: :guilabel:`Start`, :guilabel:`End`, and :guilabel:`Step Size` are applied to this stage. Other stack-capable stages can be moved with fixed offsets from the primary Z-stack stage when their offset settings are enabled.
179+
178180
* Under the :guilabel:`Channels` tab, in :guilabel:`Stack Acquisition Settings (μm)` press :guilabel:`Set Start Pos/Foc`.
179181

180182
.. image:: ../images/press-start-pos.png

‎docs/source/02_user_guide/03_user_interface_walkthrough/03_acquisition_and_settings_notebooks.rst‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ This section defines Z-stack geometry and sequencing.
7676
1. :guilabel:`Start` / :guilabel:`End` are relative stack bounds.
7777
2. :guilabel:`Set Start Pos/Foc` and :guilabel:`Set End Pos/Foc` read current stage values.
7878
3. :guilabel:`Step Size` sets spacing in microns; :guilabel:`# slices` updates automatically.
79-
4. :guilabel:`Laser Cycling Settings` selects Per Stack or Per Z channel ordering.
79+
4. :guilabel:`Z-Stack Device` selects the primary Z-stack stage. When multiple stages can move along the Z direction, this is the stage that steps through the planes of the stack.
80+
5. :guilabel:`Focus Device` selects the focus stage that is ramped with the primary Z-stack stage when the start and end focus values differ.
81+
6. Additional stack-device offset controls, when present, move secondary stack stages at fixed offsets from the primary Z-stack stage.
82+
7. :guilabel:`Laser Cycling Settings` selects Per Stack or Per Z channel ordering.
8083

8184
.. _ui_timepoint_settings:
8285

‎docs/source/02_user_guide/05_acquiring_data/01_acquiring_guide/01_acquiring_guide.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Z-Stack Acquisition
5252

5353
This takes an image stack over the range and at the step size defined by the :ref:`stack acquisition settings <ui_stack_settings>` and optionally saves the stack to a file. The color channels will appear as in "Continuous Scan" and "Single Acquisition" if :guilabel:`Laser Cycling Settings` is set to "Per Z" in the stack acquisition settings. A single z-stack will be taken for each color channel, one channel at a time, if :guilabel:`Laser Cycling Settings` is set to "Per Stack".
5454

55+
If the microscope exposes multiple stages that can move along the Z direction, :guilabel:`Z-Stack Device` in the stack acquisition settings selects the primary Z-stack stage. This primary stage is the one that receives the Z-stack start, end, and step-size moves. The selected :guilabel:`Focus Device` can be ramped with it to maintain focus, and any secondary stack stages with enabled offsets follow the primary Z-stack position plus their configured offsets.
56+
5557
Z-Stack acquisition is implemented as the feature list below.
5658

5759
.. code-block:: python

‎src/navigate/controller/sub_controllers/channels_tab.py‎

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,16 @@ def populate_experiment_values(self) -> None:
298298
# after initialization
299299
self.in_initialization = False
300300
self.channel_setting_controller.in_initialization = False
301+
# get primary z and f axis
302+
primary_z_axis = self.microscope_state_dict.get("primary_z_axis", "z")
303+
primary_f_axis = self.microscope_state_dict.get("primary_f_axis", "f")
301304
# update z and f position
302305
self.z_origin = self.parent_controller.configuration["experiment"][
303306
"StageParameters"
304-
]["z"]
307+
][primary_z_axis]
305308
self.focus_origin = self.parent_controller.configuration["experiment"][
306309
"StageParameters"
307-
]["f"]
310+
][primary_f_axis]
308311
self.update_z_steps()
309312

310313
self.show_verbose_info("Channels tab has been set new values")
@@ -516,9 +519,13 @@ def update_z_steps(self, *_: tuple[str]) -> None:
516519
)
517520
self.stack_acq_vals["number_z_steps"].set(number_z_steps)
518521

522+
# get the primary z-stack and focus axis
523+
primary_z_stage = self.stack_acq_vals["z_device"].get()
524+
primary_z_axis = primary_z_stage.split(" - ")[1]
525+
519526
# Shift the start/stop positions by the relative position
520527
flip_flags = self.parent_controller.configuration_controller.stage_flip_flags
521-
if flip_flags["z"]:
528+
if flip_flags[primary_z_axis]:
522529
self.microscope_state_dict["abs_z_start"] = self.z_origin + end_position
523530
self.microscope_state_dict["abs_z_end"] = self.z_origin + start_position
524531
# self.stack_acq_vals["abs_z_start"].set(self.z_origin + end_position)
@@ -533,7 +540,7 @@ def update_z_steps(self, *_: tuple[str]) -> None:
533540
self.microscope_state_dict["start_position"] = start_position
534541
self.microscope_state_dict["end_position"] = end_position
535542
self.microscope_state_dict["step_size"] = step_size * (
536-
-1 if flip_flags["z"] else 1
543+
-1 if flip_flags[primary_z_axis] else 1
537544
)
538545
self.microscope_state_dict["number_z_steps"] = number_z_steps
539546
self.stack_acq_vals["bottom"].set(self.microscope_state_dict["abs_z_start"])
@@ -568,17 +575,21 @@ def update_start_position(self, *_: tuple[str]) -> None:
568575
_ : tuple[str]
569576
Values is a tuple of strings. e.g., ('PY_VAR0', '', 'write')
570577
"""
571-
578+
# get the primary z-stack and focus axis
579+
primary_z_stage = self.stack_acq_vals["z_device"].get()
580+
primary_z_axis = primary_z_stage.split(" - ")[1]
581+
primary_f_stage = self.stack_acq_vals["f_device"].get()
582+
primary_f_axis = primary_f_stage.split(" - ")[1]
572583
# We have a new origin
573584
self.z_origin = self.parent_controller.configuration["experiment"][
574585
"StageParameters"
575-
]["z"]
586+
][primary_z_axis]
576587
self.focus_origin = self.parent_controller.configuration["experiment"][
577588
"StageParameters"
578-
]["f"]
589+
][primary_f_axis]
579590

580591
flip_flags = self.parent_controller.configuration_controller.stage_flip_flags
581-
if flip_flags["z"]:
592+
if flip_flags[primary_z_axis]:
582593
self.stack_acq_vals["end_position"].set(0)
583594
self.stack_acq_vals["end_focus"].set(0)
584595
else:
@@ -597,13 +608,18 @@ def update_end_position(self, *_: tuple[str]) -> None:
597608
Values is a tuple of strings. e.g., ('PY_VAR0', '', 'write')
598609
599610
"""
611+
# get the primary z-stack and focus axis
612+
primary_z_stage = self.stack_acq_vals["z_device"].get()
613+
primary_z_axis = primary_z_stage.split(" - ")[1]
614+
primary_f_stage = self.stack_acq_vals["f_device"].get()
615+
primary_f_axis = primary_f_stage.split(" - ")[1]
600616
# Grab current values
601617
z_end = self.parent_controller.configuration["experiment"]["StageParameters"][
602-
"z"
618+
primary_z_axis
603619
]
604620
focus_end = self.parent_controller.configuration["experiment"][
605621
"StageParameters"
606-
]["f"]
622+
][primary_f_axis]
607623

608624
z_start = self.z_origin
609625
focus_start = self.focus_origin
@@ -623,7 +639,7 @@ def update_end_position(self, *_: tuple[str]) -> None:
623639
end_pos = z_end - self.z_origin
624640
start_focus = focus_start - self.focus_origin
625641
end_focus = focus_end - self.focus_origin
626-
if flip_flags["z"]:
642+
if flip_flags[primary_z_axis]:
627643
start_pos, end_pos = end_pos, start_pos
628644
start_focus, end_focus = end_focus, start_focus
629645
self.stack_acq_vals["start_position"].set(start_pos)

‎src/navigate/model/features/common_features.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,8 +1280,8 @@ def pre_signal_func(self) -> None:
12801280

12811281
# restore z, f
12821282
pos_dict = self.model.get_stage_position()
1283-
self.restore_z = pos_dict["z_pos"]
1284-
self.restore_f = pos_dict["f_pos"]
1283+
self.restore_z = pos_dict[f"{self.primary_z_axis}_pos"]
1284+
self.restore_f = pos_dict[f"{self.primary_f_axis}_pos"]
12851285
self.pre_position = {
12861286
axis: float(pos_dict[f"{axis}_pos"]) for axis in self.stage_axes
12871287
}

‎test/model/devices/stages/test_asi.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ def setup_class(
146146
}
147147
self.stage_configuration = stage_configuration
148148
self.stage_configuration["stage"]["hardware"]["type"] = "ASI"
149+
self.stage_configuration.setdefault("zoom", {}).setdefault(
150+
"pixel_size", {"5X": 1.3}
151+
)
149152
self.asi_serial_device = asi_serial_device
150153
self.random_single_axis_test = random_single_axis_test
151154
self.random_multiple_axes_test = random_multiple_axes_test

‎test/model/features/test_common_features.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,47 @@ def test_multiposition_theta_move_pauses_data_thread(dummy_model_to_test_feature
6868
assert record_names.index("move_stage") < record_names.index("resume_data_thread")
6969

7070

71+
def test_z_stack_preserves_primary_axis_restore_and_pre_position(
72+
dummy_model_to_test_features,
73+
):
74+
model = dummy_model_to_test_features
75+
model.virtual_microscopes = {}
76+
microscope_state = model.configuration["experiment"]["MicroscopeState"]
77+
stage_parameters = model.configuration["experiment"]["StageParameters"]
78+
previous_primary_z_axis = microscope_state.get("primary_z_axis")
79+
previous_primary_f_axis = microscope_state.get("primary_f_axis")
80+
previous_stage_parameters = dict(stage_parameters)
81+
82+
try:
83+
microscope_state["is_multiposition"] = False
84+
microscope_state["primary_z_axis"] = "theta"
85+
microscope_state["primary_f_axis"] = "x"
86+
stage_parameters.update({"x": 11, "y": 22, "z": 33, "theta": 44, "f": 55})
87+
88+
feature = ZStackAcquisition(model)
89+
feature.pre_signal_func()
90+
91+
assert feature.restore_z == 44
92+
assert feature.restore_f == 11
93+
assert feature.pre_position == {
94+
"x": 11.0,
95+
"y": 22.0,
96+
"z": 33.0,
97+
"theta": 44.0,
98+
"f": 55.0,
99+
}
100+
finally:
101+
if previous_primary_z_axis is None:
102+
microscope_state.pop("primary_z_axis", None)
103+
else:
104+
microscope_state["primary_z_axis"] = previous_primary_z_axis
105+
if previous_primary_f_axis is None:
106+
microscope_state.pop("primary_f_axis", None)
107+
else:
108+
microscope_state["primary_f_axis"] = previous_primary_f_axis
109+
stage_parameters.update(previous_stage_parameters)
110+
111+
71112
class TestZStack:
72113
@pytest.fixture(autouse=True)
73114
def _prepare_test(self, dummy_model_to_test_features):

0 commit comments

Comments
 (0)