Skip to content

Commit

Permalink
Merge pull request #757 from micro-manager/tests
Browse files Browse the repository at this point in the history
Added Before_Z_Hooks copy of AcqEngJ code.
  • Loading branch information
henrypinkard authored Apr 15, 2024
2 parents 7587ed1 + 37e3975 commit 65d2d5a
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 38 deletions.
4 changes: 2 additions & 2 deletions java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.micro-manager.pycro-manager</groupId>
<artifactId>PycroManagerJava</artifactId>
<version>0.46.2</version>
<version>0.46.3</version>
<packaging>jar</packaging>
<name>Pycro-Manager Java</name>
<description>The Java components of Pycro-Manager</description>
Expand Down Expand Up @@ -55,7 +55,7 @@
<dependency>
<groupId>org.micro-manager.acqengj</groupId>
<artifactId>AcqEngJ</artifactId>
<version>0.36.0</version>
<version>0.37.1</version>
</dependency>
<dependency>
<groupId>org.micro-manager.ndviewer</groupId>
Expand Down
2 changes: 1 addition & 1 deletion pycromanager/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version_info = (0, 31, 2)
version_info = (0, 32, 0)
__version__ = ".".join(map(str, version_info))
1 change: 1 addition & 0 deletions pycromanager/acq_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
104 changes: 74 additions & 30 deletions pycromanager/acquisition/acq_eng_py/internal/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()):
Expand All @@ -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
Expand All @@ -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")

Expand Down
15 changes: 12 additions & 3 deletions pycromanager/acquisition/acq_eng_py/main/AcqEngPy_Acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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_ = []
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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_

Expand Down
7 changes: 5 additions & 2 deletions pycromanager/acquisition/acq_eng_py/main/acq_notification.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import warnings


class AcqNotification:

Expand All @@ -12,6 +14,7 @@ def to_string():

class Hardware:
PRE_HARDWARE = "pre_hardware"
PRE_Z_DRIVE = "pre_z_drive"
POST_HARDWARE = "post_hardware"

@staticmethod
Expand Down Expand Up @@ -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


Expand Down

0 comments on commit 65d2d5a

Please sign in to comment.