Skip to content

Commit 1a42dc3

Browse files
Merge pull request #1087 from annie-xd-wang/abstract-z-stack-acquisition
Abstract z stack acquisition
2 parents b5b2c61 + d8e6bb4 commit 1a42dc3

File tree

11 files changed

+550
-286
lines changed

11 files changed

+550
-286
lines changed

src/navigate/controller/configuration_controller.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -411,23 +411,27 @@ def stage_setting_dict(self):
411411
return self.microscope_config["stage"]
412412
return None
413413

414-
@property
415-
def z_stages(self):
416-
"""Return a list of all z stage names.
414+
def get_stages_by_axis(self, axis_prefix="z"):
415+
"""Return a list of all stage names.
416+
417+
Parameters
418+
----------
419+
axis_prefix : str
420+
The axis prefix to get the stage names for.
417421
418422
Returns
419423
-------
420-
z_stages : list
421-
A list of z stage names.
424+
stages : list
425+
A list of stage names.
422426
"""
423427
if self.microscope_config is not None:
424428
stages = self.microscope_config["stage"]["hardware"]
425429
if isinstance(stages, ListProxy):
426430
stages = list(stages)
427-
return [stage["type"] for stage in stages if "z" in stage["axes"]]
428-
elif isinstance(stages, DictProxy):
429-
stages = dict(stages)
430-
return [stages["type"]] if "z" in stages["axes"] else []
431+
else:
432+
stages = [stages]
433+
return [f"{stage['type']} - {axis}" for stage in stages for axis in stage["axes"] if axis.startswith(axis_prefix)]
434+
return []
431435

432436
@property
433437
def number_of_channels(self):

src/navigate/controller/sub_controllers/channels_tab.py

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ def __init__(
106106
self.stack_acq_vals["start_position"].trace_add("write", self.update_z_steps)
107107
self.stack_acq_vals["end_position"].trace_add("write", self.update_z_steps)
108108
self.stack_acq_vals["start_focus"].trace_add("write", self.update_z_steps)
109+
self.stack_acq_vals["z_device"].trace_add("write", self.update_additional_stacking_axes)
110+
self.stack_acq_vals["f_device"].trace_add("write", self.update_additional_stacking_axes)
109111
self.stack_acq_buttons["set_start"].configure(
110112
command=self.update_start_position
111113
)
@@ -187,13 +189,26 @@ def initialize(self) -> None:
187189
self.stack_acq_widgets["cycling"].widget["values"] = ["Per Z", "Per Stack"]
188190

189191
# Set the default stage for acquiring a z-stack.
190-
self.stack_acq_widgets["z_device"].widget["values"] = config.z_stages
191-
if len(config.z_stages) == 1:
192+
z_stages = config.get_stages_by_axis("z")
193+
self.stack_acq_widgets["z_device"].widget["values"] = z_stages
194+
if len(z_stages) >= 1:
192195
self.stack_acq_widgets["z_device"].widget.current(0)
193-
self.stack_acq_widgets["z_device"].widget["state"] = "disabled"
194-
else:
195-
# Now need to allow the user to have an offset between the two stages.
196-
self.stack_acq_widgets["z_offset"].widget["state"] = "normal"
196+
197+
f_stages = config.get_stages_by_axis("f")
198+
self.stack_acq_widgets["f_device"].widget["values"] = f_stages
199+
if len(f_stages) >= 1:
200+
self.stack_acq_widgets["f_device"].widget.current(0)
201+
202+
axes = config.stage_axes
203+
axes = [axis for axis in axes if axis not in ("x", "y", "theta")]
204+
devices_dict = {}
205+
for axis in axes:
206+
temp = config.get_stages_by_axis(axis)
207+
for device in temp:
208+
device_name, axis = device.split(" - ")
209+
devices_dict[axis] = device_name
210+
self.view.stack_acq_frame.create_additional_stack_widgets(axes, devices_dict)
211+
197212

198213
self.filter_wheel_delay = [
199214
config.filter_wheel_setting_dict[i]["filter_wheel_delay"]
@@ -225,6 +240,27 @@ def populate_experiment_values(self) -> None:
225240
self.set_info(self.stack_acq_vals, self.microscope_state_dict)
226241
self.set_info(self.timepoint_vals, self.microscope_state_dict)
227242

243+
# set advanced stack acquistion settings
244+
for axis in ["z", "f"]:
245+
devices = self.stack_acq_widgets[f"{axis}_device"].widget["values"]
246+
idx = 0
247+
for i, device in enumerate(devices):
248+
if device.endswith(self.microscope_state_dict.get(f"primary_{axis}_axis", axis)):
249+
idx = i
250+
break
251+
self.stack_acq_widgets[f"{axis}_device"].widget.current(idx)
252+
253+
secondary_stack_settings= self.microscope_state_dict.get("secondary_stack_settings", {})
254+
variable_dict = self.view.stack_acq_frame.additional_stack_setting_variables
255+
for axis in secondary_stack_settings.keys():
256+
index_axis = f"stack_{axis}"
257+
if index_axis in variable_dict:
258+
variable_dict[index_axis].set(True)
259+
self.view.stack_acq_frame.update_setting_widgets(axis)()
260+
variable_dict[f"{axis}_offset"].set(secondary_stack_settings[axis])
261+
262+
self.update_additional_stacking_axes()
263+
228264
# check configuration for multi-position settings
229265
self.is_multiposition_val.set(self.microscope_state_dict["is_multiposition"])
230266
self.is_multiposition_cache = self.is_multiposition
@@ -339,7 +375,8 @@ def set_mode(self, mode: str) -> None:
339375
acquisition mode
340376
"""
341377

342-
# TODO: Why do we have mode and image_mode?
378+
# image_mode: imaging mode, e.g., "live", "single", "z-stack", "customized"
379+
# stack acquisition settings are disabled in "live" and "single" mode.
343380
image_mode = self.microscope_state_dict["image_mode"]
344381
self.mode = mode
345382
self.channel_setting_controller.set_mode(mode)
@@ -359,9 +396,18 @@ def set_mode(self, mode: str) -> None:
359396
"step_size",
360397
]:
361398
self.stack_acq_widgets[widget_name].widget["state"] = state
362-
self.stack_acq_widgets["cycling"].widget["state"] = (
363-
"readonly" if state == "normal" else "disabled"
364-
)
399+
for widget_name in ["cycling", "z_device", "f_device"]:
400+
self.stack_acq_widgets[widget_name].widget["state"] = (
401+
"readonly" if state == "normal" else "disabled"
402+
)
403+
for widget_name in self.view.stack_acq_frame.additional_stack_setting_variables:
404+
if widget_name.startswith("stack_"):
405+
self.stack_acq_widgets[widget_name]["state"] = state
406+
elif widget_name.endswith("_offset"):
407+
self.stack_acq_widgets[widget_name]["state"] = state
408+
if state == "normal":
409+
self.update_additional_stacking_axes()
410+
365411
self.view.stack_timepoint_frame.save_check["state"] = (
366412
"normal" if image_mode == "single" and mode == "stop" else state
367413
)
@@ -408,13 +454,17 @@ def update_z_steps(self, *_: tuple[str]) -> None:
408454
step_size = float(self.stack_acq_vals["step_size"].get())
409455
if step_size < 0.001:
410456
self.stack_acq_vals["number_z_steps"].set(0)
411-
self.stack_acq_vals["abs_z_start"].set(0)
412-
self.stack_acq_vals["abs_z_end"].set(0)
457+
self.microscope_state_dict["abs_z_start"] = 0
458+
self.microscope_state_dict["abs_z_end"] = 0
459+
# self.stack_acq_vals["abs_z_start"].set(0)
460+
# self.stack_acq_vals["abs_z_end"].set(0)
413461
return
414462
except tk.TclError:
415463
self.stack_acq_vals["number_z_steps"].set(0)
416-
self.stack_acq_vals["abs_z_start"].set(0)
417-
self.stack_acq_vals["abs_z_end"].set(0)
464+
self.microscope_state_dict["abs_z_start"] = 0
465+
self.microscope_state_dict["abs_z_end"] = 0
466+
# self.stack_acq_vals["abs_z_start"].set(0)
467+
# self.stack_acq_vals["abs_z_end"].set(0)
418468
return
419469
except (KeyError, AttributeError):
420470
logger.error("Error caught: updating z_steps")
@@ -428,11 +478,15 @@ def update_z_steps(self, *_: tuple[str]) -> None:
428478
# Shift the start/stop positions by the relative position
429479
flip_flags = self.parent_controller.configuration_controller.stage_flip_flags
430480
if flip_flags["z"]:
431-
self.stack_acq_vals["abs_z_start"].set(self.z_origin + end_position)
432-
self.stack_acq_vals["abs_z_end"].set(self.z_origin + start_position)
481+
self.microscope_state_dict["abs_z_start"] = self.z_origin + end_position
482+
self.microscope_state_dict["abs_z_end"] = self.z_origin + start_position
483+
# self.stack_acq_vals["abs_z_start"].set(self.z_origin + end_position)
484+
# self.stack_acq_vals["abs_z_end"].set(self.z_origin + start_position)
433485
else:
434-
self.stack_acq_vals["abs_z_start"].set(self.z_origin + start_position)
435-
self.stack_acq_vals["abs_z_end"].set(self.z_origin + end_position)
486+
self.microscope_state_dict["abs_z_start"] = self.z_origin + start_position
487+
self.microscope_state_dict["abs_z_end"] = self.z_origin + end_position
488+
# self.stack_acq_vals["abs_z_start"].set(self.z_origin + start_position)
489+
# self.stack_acq_vals["abs_z_end"].set(self.z_origin + end_position)
436490

437491
# update experiment MicroscopeState dict
438492
self.microscope_state_dict["start_position"] = start_position
@@ -441,10 +495,13 @@ def update_z_steps(self, *_: tuple[str]) -> None:
441495
-1 if flip_flags["z"] else 1
442496
)
443497
self.microscope_state_dict["number_z_steps"] = number_z_steps
444-
self.microscope_state_dict["abs_z_start"] = self.stack_acq_vals[
445-
"abs_z_start"
446-
].get()
447-
self.microscope_state_dict["abs_z_end"] = self.stack_acq_vals["abs_z_end"].get()
498+
self.stack_acq_vals["bottom"].set(
499+
self.microscope_state_dict["abs_z_start"]
500+
)
501+
self.stack_acq_vals["top"].set(
502+
self.microscope_state_dict["abs_z_end"]
503+
)
504+
448505
try:
449506
self.microscope_state_dict["start_focus"] = self.stack_acq_vals[
450507
"start_focus"
@@ -814,6 +871,25 @@ def update_experiment_values(self) -> None:
814871
self.channel_setting_controller.update_experiment_values()
815872
self.update_z_steps()
816873

874+
# update primary/secondary stack acquisition stage settings
875+
primary_z_stage = self.stack_acq_vals["z_device"].get()
876+
primary_z_axis = primary_z_stage.split(" - ")[1]
877+
primary_f_stage = self.stack_acq_vals["f_device"].get()
878+
primary_f_axis = primary_f_stage.split(" - ")[1]
879+
880+
self.microscope_state_dict["primary_z_axis"] = primary_z_axis
881+
self.microscope_state_dict["primary_f_axis"] = primary_f_axis
882+
883+
secondary_stack_settings= {}
884+
variable_dict = self.view.stack_acq_frame.additional_stack_setting_variables
885+
for k in variable_dict.keys():
886+
if k.startswith("stack_") and variable_dict[k].get():
887+
axis = k.split("_")[1]
888+
offset = variable_dict[f"{axis}_offset"].get()
889+
secondary_stack_settings[axis] = offset
890+
891+
self.microscope_state_dict["secondary_stack_settings"] = secondary_stack_settings
892+
817893
def verify_experiment_values(self) -> str:
818894
"""Verify channel tab settings and return warning info
819895
@@ -858,6 +934,34 @@ def set_exposure_time(self, channel_exposure_time: tuple[str, float]) -> None:
858934
self.channel_setting_controller.view.exptime_variables[idx].set(exposure_time)
859935
self.channel_setting_controller.in_initialization = False
860936

937+
def update_additional_stacking_axes(self, *args, **kwargs):
938+
if self.stack_acq_vals["z_device"].get():
939+
z_axis = self.stack_acq_vals["z_device"].get().split(" - ")[1]
940+
else:
941+
z_axis = ""
942+
if self.stack_acq_vals["f_device"].get():
943+
f_axis = self.stack_acq_vals["f_device"].get().split(" - ")[1]
944+
else:
945+
f_axis = ""
946+
947+
# enable all axis
948+
for widget_name in self.view.stack_acq_frame.additional_stack_setting_variables:
949+
if widget_name.startswith("stack_"):
950+
self.stack_acq_widgets[widget_name].state(["!disabled"])
951+
952+
# disable primary z and f
953+
variable_dict = self.view.stack_acq_frame.additional_stack_setting_variables
954+
if f"stack_{z_axis}" in variable_dict:
955+
self.stack_acq_widgets[f"stack_{z_axis}"].state(["disabled"])
956+
if variable_dict[f"stack_{z_axis}"].get():
957+
variable_dict[f"stack_{z_axis}"].set(False)
958+
self.view.stack_acq_frame.update_setting_widgets(z_axis)()
959+
if f"stack_{f_axis}" in variable_dict:
960+
self.stack_acq_widgets[f"stack_{f_axis}"].state(["disabled"])
961+
if variable_dict[f"stack_{f_axis}"].get():
962+
variable_dict[f"stack_{f_axis}"].set(False)
963+
self.view.stack_acq_frame.update_setting_widgets(f_axis)()
964+
861965
@property
862966
def custom_events(self) -> dict[str, callable]:
863967
"""Custom events for the channels tab."""

0 commit comments

Comments
 (0)