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