diff --git a/java/pom.xml b/java/pom.xml
index 74dc7907..8867b85c 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.micro-manager.pycro-manager
PycroManagerJava
- 0.46.2
+ 0.46.3
jar
Pycro-Manager Java
The Java components of Pycro-Manager
@@ -55,7 +55,7 @@
org.micro-manager.acqengj
AcqEngJ
- 0.36.0
+ 0.37.1
org.micro-manager.ndviewer
diff --git a/pycromanager/_version.py b/pycromanager/_version.py
index b5a480a9..efd95b4b 100644
--- a/pycromanager/_version.py
+++ b/pycromanager/_version.py
@@ -1,2 +1,2 @@
-version_info = (0, 31, 2)
+version_info = (0, 32, 0)
__version__ = ".".join(map(str, version_info))
diff --git a/pycromanager/acq_future.py b/pycromanager/acq_future.py
index a26fbb35..94ff746d 100644
--- a/pycromanager/acq_future.py
+++ b/pycromanager/acq_future.py
@@ -27,6 +27,7 @@ def _add_notifications(self, axes_or_axes_list):
# TODO maybe unify snap and sequence cause this is confusing
self._notification_recieved[_axes_to_key(axes)] = {
AcqNotification.Hardware.PRE_HARDWARE: False,
+ AcqNotification.Hardware.PRE_Z_DRIVE: False,
AcqNotification.Hardware.POST_HARDWARE: False,
AcqNotification.Camera.PRE_SNAP: False,
diff --git a/pycromanager/acquisition/acq_eng_py/internal/engine.py b/pycromanager/acquisition/acq_eng_py/internal/engine.py
index da02f57c..cb4509fd 100644
--- a/pycromanager/acquisition/acq_eng_py/internal/engine.py
+++ b/pycromanager/acquisition/acq_eng_py/internal/engine.py
@@ -179,6 +179,21 @@ def execute_acquisition_event(self, event: AcquisitionEvent):
except HardwareControlException as e:
self.stop_hardware_sequences(hardware_sequences_in_progress)
raise e
+
+ event.acquisition_.post_notification(AcqNotification(
+ AcqNotification.Hardware, event.axisPositions_, AcqNotification.Hardware.PRE_Z_DRIVE))
+ for h in event.acquisition_.get_before_z_drive_hooks():
+ event = h.run(event)
+ if event is None:
+ return # The hook cancelled this event
+ self.abort_if_requested(event, None)
+ hardware_sequences_in_progress = HardwareSequences()
+ try:
+ self.start_z_drive(event, hardware_sequences_in_progress)
+ except HardwareControlException as e:
+ self.stop_hardware_sequences(hardware_sequences_in_progress)
+ raise e
+
# TODO restore this
event.acquisition_.post_notification(AcqNotification(
AcqNotification.Hardware, event.axisPositions_, AcqNotification.Hardware.POST_HARDWARE))
@@ -486,30 +501,6 @@ def change_channels(event):
ex.print_stack_trace()
raise HardwareControlException(ex.get_message())
- def move_z_device(event):
- try:
- if event.is_z_sequenced():
- self.core.start_stage_sequence(z_stage)
- else:
- previous_z = None if self.last_event is None else None if self.last_event.get_sequence() is None else \
- self.last_event.get_sequence()[0].get_z_position()
- current_z = event.get_z_position() if event.get_sequence() is None else \
- event.get_sequence()[0].get_z_position()
- if current_z is None:
- return
- change = previous_z is None or previous_z != current_z
- if not change:
- return
-
- # Wait for it to not be busy
- self.core.wait_for_device(z_stage)
- # Move Z
- self.core.set_position(z_stage, float(current_z))
- # Wait for move to finish
- self.core.wait_for_device(z_stage)
- except Exception as ex:
- raise HardwareControlException(ex)
-
def move_other_stage_devices(event):
try:
for stage_device_name in event.get_stage_device_names():
@@ -572,7 +563,6 @@ def change_additional_properties(event):
try:
# Get the hardware specific to this acquisition
xy_stage = self.core.get_xy_stage_device()
- z_stage = self.core.get_focus_device()
slm = self.core.get_slm_device()
# Prepare sequences if applicable
@@ -623,9 +613,7 @@ def change_additional_properties(event):
self.core.load_xy_stage_sequence(xy_stage, x_sequence, y_sequence)
hardware_sequences_in_progress.device_names.add(xy_stage)
- if event.is_z_sequenced():
- self.core.load_stage_sequence(z_stage, z_sequence)
- hardware_sequences_in_progress.device_names.add(z_stage)
+
if event.is_config_group_sequenced():
for i in range(config.size()):
@@ -644,8 +632,7 @@ def change_additional_properties(event):
if self.last_event is not None and self.last_event.acquisition_ != event.acquisition_:
self.last_event = None # Update all hardware if switching to a new acquisition
- # Z stage
- loop_hardware_command_retries(lambda: move_z_device(event), "Moving Z device")
+
# Other stage devices
loop_hardware_command_retries(lambda: move_other_stage_devices(event), "Moving other stage devices")
# XY Stage
@@ -664,6 +651,63 @@ def change_additional_properties(event):
traceback.print_exc()
raise HardwareControlException("Error executing event")
+ def start_z_drive(self, event: AcquisitionEvent, hardware_sequences_in_progress: HardwareSequences) -> None:
+ def loop_hardware_command_retries(r, command_name):
+ for i in range(HARDWARE_ERROR_RETRIES):
+ try:
+ r()
+ return
+ except Exception as e:
+ self.core.log_message(traceback.format_exc())
+ print(self.get_current_date_and_time() + ": Problem " + command_name + "\n Retry #" + str(
+ i) + " in " + str(DELAY_BETWEEN_RETRIES_MS) + " ms")
+ time.sleep(DELAY_BETWEEN_RETRIES_MS / 1000)
+ raise HardwareControlException(command_name + " unsuccessful")
+
+ def move_z_device(event):
+ try:
+ if event.is_z_sequenced():
+ self.core.start_stage_sequence(z_stage)
+ else:
+ previous_z = None if self.last_event is None else None if self.last_event.get_sequence() is None else \
+ self.last_event.get_sequence()[0].get_z_position()
+ current_z = event.get_z_position() if event.get_sequence() is None else \
+ event.get_sequence()[0].get_z_position()
+ if current_z is None:
+ return
+ change = previous_z is None or previous_z != current_z
+ if not change:
+ return
+
+ # Wait for it to not be busy
+ self.core.wait_for_device(z_stage)
+ # Move Z
+ self.core.set_position(z_stage, float(current_z))
+ # Wait for move to finish
+ self.core.wait_for_device(z_stage)
+ except Exception as ex:
+ raise HardwareControlException(ex)
+
+ try:
+ z_stage = self.core.get_focus_device()
+ if event.get_sequence() is not None:
+ z_sequence = pymmcore.DoubleVector() if event.is_z_sequenced() else None
+ for e in event.get_sequence():
+ if z_sequence is not None:
+ z_sequence.add(e.get_z_position())
+ if event.is_z_sequenced():
+ self.core.load_stage_sequence(z_stage, z_sequence)
+ hardware_sequences_in_progress.device_names.add(z_stage)
+
+ # Z stage
+ loop_hardware_command_retries(lambda: move_z_device(event), "Moving Z device")
+ except:
+ traceback.print_exc()
+ raise HardwareControlException("Error executing event")
+
+
+
+
def get_current_date_and_time(self):
return datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
diff --git a/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py b/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py
index bb2c29f3..70e493b5 100644
--- a/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py
+++ b/pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py
@@ -16,15 +16,18 @@ class Acquisition():
# This hook runs before changes to the hardware (corresponding to the instructions in the
# event) are made
BEFORE_HARDWARE_HOOK = 1
+ # This hook runs after all changes to the hardware except dor setting th Z drive have been
+ # made. This is useful for things such as autofocus.
+ BEFORE_Z_DRIVE = 2
# This hook runs after changes to the hardware took place, but before camera exposure
# (either a snap or a sequence) is started
- AFTER_HARDWARE_HOOK = 2
+ AFTER_HARDWARE_HOOK = 3
# Hook runs after the camera sequence acquisition has started. This can be used for
# external triggering of the camera
- AFTER_CAMERA_HOOK = 3
+ AFTER_CAMERA_HOOK = 4
# Hook runs after the camera exposure ended (when possible, before readout of the camera
# and availability of the images in memory).
- AFTER_EXPOSURE_HOOK = 4
+ AFTER_EXPOSURE_HOOK = 5
IMAGE_QUEUE_SIZE = 30
@@ -36,6 +39,7 @@ def __init__(self, sink, summary_metadata_processor=None, initialize=True):
self.paused_ = False
self.event_generation_hooks_ = []
self.before_hardware_hooks_ = []
+ self.before_z_hooks_ = []
self.after_hardware_hooks_ = []
self.after_camera_hooks_ = []
self.after_exposure_hooks_ = []
@@ -155,6 +159,8 @@ def add_hook(self, h, type_):
self.event_generation_hooks_.append(h)
elif type_ == self.BEFORE_HARDWARE_HOOK:
self.before_hardware_hooks_.append(h)
+ elif type_ == self.BEFORE_Z_HOOK:
+ self.before_z_hooks_.append(h)
elif type_ == self.AFTER_HARDWARE_HOOK:
self.after_hardware_hooks_.append(h)
elif type_ == self.AFTER_CAMERA_HOOK:
@@ -221,6 +227,9 @@ def get_event_generation_hooks(self):
def get_before_hardware_hooks(self):
return self.before_hardware_hooks_
+ def get_before_z_hooks(self):
+ return self.before_z_hooks_
+
def get_after_hardware_hooks(self):
return self.after_hardware_hooks_
diff --git a/pycromanager/acquisition/acq_eng_py/main/acq_notification.py b/pycromanager/acquisition/acq_eng_py/main/acq_notification.py
index e8b77457..d6ed5280 100644
--- a/pycromanager/acquisition/acq_eng_py/main/acq_notification.py
+++ b/pycromanager/acquisition/acq_eng_py/main/acq_notification.py
@@ -1,4 +1,6 @@
import json
+import warnings
+
class AcqNotification:
@@ -12,6 +14,7 @@ def to_string():
class Hardware:
PRE_HARDWARE = "pre_hardware"
+ PRE_Z_DRIVE = "pre_z_drive"
POST_HARDWARE = "post_hardware"
@staticmethod
@@ -50,14 +53,14 @@ def __init__(self, type, payload, milestone=None):
AcqNotification.Camera.PRE_SEQUENCE_STARTED, AcqNotification.Camera.POST_SEQUENCE_STOPPED]:
self.type = AcqNotification.Camera
self.payload = json.loads(payload) if isinstance(payload, str) else payload # convert from '{'time': 5}' to {'time': 5}
- elif milestone in [AcqNotification.Hardware.PRE_HARDWARE, AcqNotification.Hardware.POST_HARDWARE]:
+ elif milestone in [AcqNotification.Hardware.PRE_HARDWARE, AcqNotification.Hardware.PRE_Z_DRIVE, AcqNotification.Hardware.POST_HARDWARE]:
self.type = AcqNotification.Hardware
self.payload = json.loads(payload) if isinstance(payload, str) else payload # convert from '{'time': 5}' to {'time': 5}
elif milestone == AcqNotification.Image.IMAGE_SAVED:
self.type = AcqNotification.Image
self.payload = payload
else:
- raise ValueError("Unknown milestone")
+ warnings.warn(f"Unknown notification type {type} with milestone {milestone}")
self.milestone = milestone