From fc84aab9e2584e64e8959e1e05fe070d145b56a2 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Wed, 10 Apr 2024 14:48:56 -0700 Subject: [PATCH 1/5] Better error reporting --- .../org/micromanager/acqj/internal/Engine.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index d041023..bdca226 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -162,8 +162,9 @@ public Future submitEventIterator(Iterator eventIterator) { //cancelled return; } catch (ExecutionException ex) { - //some problem with acuisition, abort and propagate exception - ex.printStackTrace(); + //some problem with acquisition, abort and propagate exception + core_.logMessage(ex.getMessage()); + core_.logMessage(ex.getStackTrace().toString()); acq.abort(ex); throw new RuntimeException(ex); } @@ -802,10 +803,14 @@ public void run() { core_.waitForDevice(stageDeviceName); //Move Z core_.setPosition(stageDeviceName, event.getStageSingleAxisStagePosition(stageDeviceName)); + } + // wait only after having started to move all stages. + // there is a possibility this approach creates complications for certain devices + // but could bring significant speed advantages + for (String stageDeviceName : event.getStageDeviceNames()) { //wait for move to finish core_.waitForDevice(stageDeviceName); } -// } } catch (Exception ex) { throw new HardwareControlException(ex.getMessage()); } @@ -971,11 +976,13 @@ public void run() { * @throws HardwareControlException */ private void loopHardwareCommandRetries(Runnable r, String commandName) throws HardwareControlException { + Exception ex = null; for (int i = 0; i < HARDWARE_ERROR_RETRIES; i++) { try { r.run(); return; } catch (Exception e) { + ex = e; core_.logMessage(stackTraceToString(e)); System.err.println(getCurrentDateAndTime() + ": Problem " + commandName + "\n Retry #" + i + " in " + DELAY_BETWEEN_RETRIES_MS + " ms"); @@ -987,7 +994,7 @@ private void loopHardwareCommandRetries(Runnable r, String commandName) throws H } } } - throw new HardwareControlException(commandName + " unsuccessful"); + throw new HardwareControlException(commandName + " unsuccessful" + ": " + ex.getMessage()); } private static String getCurrentDateAndTime() { From 17bac91b8e50e21b92290f0108396551c57f0cd6 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Thu, 11 Apr 2024 11:49:01 -0700 Subject: [PATCH 2/5] Adds hook that runs after all hardware except for the ZDrive has been set to the correct place. --- .../micromanager/acqj/api/AcquisitionAPI.java | 33 +++-- .../micromanager/acqj/internal/Engine.java | 121 ++++++++++++------ .../acqj/main/AcqNotification.java | 1 + .../micromanager/acqj/main/Acquisition.java | 16 ++- 4 files changed, 113 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java b/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java index 521b917..765f05b 100644 --- a/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java +++ b/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java @@ -24,6 +24,9 @@ public interface AcquisitionAPI { // This hook runs before changes to the hardware (corresponding to the instructions in the // event) are made int BEFORE_HARDWARE_HOOK = 1; + // This hook runs after all hardware except for the Z-drive has been set in place. This is + // an ideal place for things such as autofocussing. + int BEFORE_Z_DRIVE_HOOK = 5; // This hook runs after changes to the hardware took place, but before camera exposure // (either a snap or a sequence) is started @@ -38,17 +41,17 @@ public interface AcquisitionAPI { int AFTER_EXPOSURE_HOOK = 4; /** - * Call to ready acquisition to start receiving acquisiton events. No more hooks - * or image processors should be added after this has been called + * Call to ready acquisition to start receiving acquisition events. No more hooks + * or image processors should be added after this has been called. * - * This method is no longer needed, because it is called automatically when + *

This method is no longer needed, because it is called automatically when * the first call to submitEventIterator is made */ @Deprecated public void start(); /** - * Add a AcqNotificationListener to receive asynchronous notifications about the acquisition + * Add a AcqNotificationListener to receive asynchronous notifications about the acquisition. */ public void addAcqNotificationListener(AcqNotificationListener listener); @@ -75,30 +78,34 @@ public interface AcquisitionAPI { public boolean areEventsFinished(); /** - * Blcok until all acquisitions events are finished or timeout is reached - * @param timeoutSeconds + * Block until all acquisitions events are finished or timeout is reached. + * + * @param timeoutSeconds wait a maximum of this many seconds. */ public void blockUntilEventsFinished(Double timeoutSeconds) throws InterruptedException; - /** - * Cancel any pending events and shutdown - */ + /** + * Cancel any pending events and shutdown. + */ public void abort(); /** * Abort, and provide an exception that is the reason for the abort. This - * is useful for passing exceptions across threads - * @param e + * is useful for passing exceptions across threads. + * + * @param e Exception that caused the abort. */ public void abort(Exception e); /** - * Has abort been called? + * Wether abort has been called. + * + * @return true if an abort has been requested. */ public boolean isAbortRequested(); /** - * return if acquisition is paused (i.e. not acquiring new data but not + * Return if acquisition is paused (i.e. not acquiring new data but not * finished) * * @return diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index bdca226..f756726 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -323,6 +323,7 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE } abortIfRequested(event, null); } + HardwareSequences hardwareSequencesInProgress = new HardwareSequences(); try { prepareHardware(event, hardwareSequencesInProgress); @@ -330,6 +331,22 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE stopHardwareSequences(hardwareSequencesInProgress); throw e; } + event.acquisition_.postNotification( new AcqNotification( + AcqNotification.Hardware.class, event.getAxesAsJSONString(), AcqNotification.Hardware.PRE_Z_DRIVE)); + for (AcquisitionHook h : event.acquisition_.getBeforeZDriveHooks()) { + event = h.run(event); + if (event == null) { + return; //The hook cancelled this event + } + abortIfRequested(event, hardwareSequencesInProgress); + } + + try { + startZDrive(event, hardwareSequencesInProgress); + } catch (HardwareControlException e) { + stopHardwareSequences(hardwareSequencesInProgress); + throw e; + } event.acquisition_.postNotification( new AcqNotification( AcqNotification.Hardware.class, event.getAxesAsJSONString(), AcqNotification.Hardware.POST_HARDWARE)); for (AcquisitionHook h : event.acquisition_.getAfterHardwareHooks()) { @@ -339,6 +356,7 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE } abortIfRequested(event, hardwareSequencesInProgress); } + // Hardware hook may have modified wait time, so check again if we should // pause until the minimum start time of the event has occurred. while (event.getMinimumStartTimeAbsolute() != null && @@ -664,7 +682,6 @@ private void prepareHardware(final AcquisitionEvent event, //prepare sequences if applicable if (event.getSequence() != null) { try { - DoubleVector zSequence = event.isZSequenced() ? new DoubleVector() : null; DoubleVector xSequence = event.isXYSequenced() ? new DoubleVector() : null; DoubleVector ySequence = event.isXYSequenced() ? new DoubleVector() : null; DoubleVector exposureSequence_ms =event.isExposureSequenced() ? new DoubleVector() : null; @@ -673,9 +690,6 @@ private void prepareHardware(final AcquisitionEvent event, core_.getConfigData(group, event.getSequence().get(0).getConfigPreset()); LinkedList propSequences = event.isConfigGroupSequenced() ? new LinkedList() : null; for (AcquisitionEvent e : event.getSequence()) { - if (zSequence != null) { - zSequence.add(e.getZPosition()); - } if (xSequence != null) { xSequence.add(e.getXPosition()); } @@ -713,10 +727,6 @@ private void prepareHardware(final AcquisitionEvent event, core_.loadXYStageSequence(xyStage, xSequence, ySequence); hardwareSequencesInProgress.deviceNames.add(xyStage); } - if (event.isZSequenced()) { - core_.loadStageSequence(zStage, zSequence); - hardwareSequencesInProgress.deviceNames.add(zStage); - } if (event.isConfigGroupSequenced()) { for (int i = 0; i < config.size(); i++) { PropertySetting ps = config.getSetting(i); @@ -742,38 +752,6 @@ private void prepareHardware(final AcquisitionEvent event, if (lastEvent_ != null && lastEvent_.acquisition_ != event.acquisition_) { lastEvent_ = null; //update all hardware if switching to a new acquisition } - /////////////////////////////Z stage//////////////////////////////////////////// - loopHardwareCommandRetries(new Runnable() { - @Override - public void run() { - try { - if (event.isZSequenced()) { - core_.startStageSequence(zStage); - } else { - Double previousZ = lastEvent_ == null ? null : lastEvent_.getSequence() == null ? lastEvent_.getZPosition() : - lastEvent_.getSequence().get(0).getZPosition(); - Double currentZ = event.getSequence() == null ? event.getZPosition() : event.getSequence().get(0).getZPosition(); - if (currentZ == null) { - return; - } - boolean change = previousZ == null || !previousZ.equals(currentZ); - if (!change) { - return; - } - - //wait for it to not be busy (is this even needed?) - core_.waitForDevice(zStage); - //Move Z - core_.setPosition(zStage, currentZ); - //wait for move to finish - core_.waitForDevice(zStage); - } - } catch (Exception ex) { - throw new HardwareControlException(ex.getMessage()); - } - - } - }, "Moving Z device"); /////////////////////////////Other stage devices //////////////////////////////////////////// loopHardwareCommandRetries(new Runnable() { @@ -967,6 +945,69 @@ public void run() { lastEvent_ = event.getSequence() == null ? event : event.getSequence().get(event.getSequence().size() - 1); } + /** + * Separate function to set the ZDrive. This should happen after + * all other devices are in place. This order makes it possible to + * create a hook that can be used to run a (hardware) autofocus routine + * after all other devices are in place and before the zDrive is set. + * + * @param event acquisition event with all information we need. + */ + private void startZDrive(final AcquisitionEvent event, + HardwareSequences hardwareSequencesInProgress) throws HardwareControlException { + final String zStage = core_.getFocusDevice(); + DoubleVector zSequence = event.isZSequenced() ? new DoubleVector() : null; + for (AcquisitionEvent e : event.getSequence()) { + if (zSequence != null) { + zSequence.add(e.getZPosition()); + } + } + try { + if (event.isZSequenced()) { + core_.loadStageSequence(zStage, zSequence); + hardwareSequencesInProgress.deviceNames.add(zStage); + } + } catch (Exception ex) { + ex.printStackTrace(); + throw new HardwareControlException(ex.getMessage()); + } + + ////////////////////////////Set the Z Drive//////////////////////////////////////////// + loopHardwareCommandRetries(new Runnable() { + @Override + public void run() { + try { + if (event.isZSequenced()) { + core_.startStageSequence(zStage); + } else { + Double previousZ = lastEvent_ == null ? null + : lastEvent_.getSequence() == null ? lastEvent_.getZPosition() + : lastEvent_.getSequence().get(0).getZPosition(); + Double currentZ = event.getSequence() == null ? event.getZPosition() + : event.getSequence().get(0).getZPosition(); + if (currentZ == null) { + return; + } + boolean change = previousZ == null || !previousZ.equals(currentZ); + if (!change) { + return; + } + + //wait for it to not be busy (is this even needed?) + core_.waitForDevice(zStage); + //Move Z + core_.setPosition(zStage, currentZ); + //wait for move to finish + core_.waitForDevice(zStage); + } + } catch (Exception ex) { + throw new HardwareControlException(ex.getMessage()); + } + + } + }, "Moving Z device"); + } + /** * Attempt a hardware command multiple times if it throws an exception. If still doesn't * work after those tries, give up and declare exception diff --git a/src/main/java/org/micromanager/acqj/main/AcqNotification.java b/src/main/java/org/micromanager/acqj/main/AcqNotification.java index b166d7a..d61d2ad 100644 --- a/src/main/java/org/micromanager/acqj/main/AcqNotification.java +++ b/src/main/java/org/micromanager/acqj/main/AcqNotification.java @@ -14,6 +14,7 @@ public class Acquisition { public class Hardware { public static final String PRE_HARDWARE = "pre_hardware"; + public static final String PRE_Z_DRIVE = "pre_z_drive"; public static final String POST_HARDWARE = "post_hardware"; } diff --git a/src/main/java/org/micromanager/acqj/main/Acquisition.java b/src/main/java/org/micromanager/acqj/main/Acquisition.java index efffad8..4cee3f3 100644 --- a/src/main/java/org/micromanager/acqj/main/Acquisition.java +++ b/src/main/java/org/micromanager/acqj/main/Acquisition.java @@ -51,12 +51,13 @@ public class Acquisition implements AcquisitionAPI { protected AcqEngJDataSink dataSink_; private Consumer summaryMetadataProcessor_; final public CMMCore core_; - private CopyOnWriteArrayList eventGenerationHooks_ = new CopyOnWriteArrayList(); - private CopyOnWriteArrayList beforeHardwareHooks_ = new CopyOnWriteArrayList(); - private CopyOnWriteArrayList afterHardwareHooks_ = new CopyOnWriteArrayList(); - private CopyOnWriteArrayList afterCameraHooks_ = new CopyOnWriteArrayList(); + private CopyOnWriteArrayList eventGenerationHooks_ = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList beforeHardwareHooks_ = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList beforeZDriveHooks_ = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList afterHardwareHooks_ = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList afterCameraHooks_ = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList afterExposureHooks_ = new CopyOnWriteArrayList<>(); - private CopyOnWriteArrayList imageProcessors_ = new CopyOnWriteArrayList(); + private CopyOnWriteArrayList imageProcessors_ = new CopyOnWriteArrayList<>(); protected LinkedBlockingDeque firstDequeue_ = new LinkedBlockingDeque(IMAGE_QUEUE_SIZE); private ConcurrentHashMap> processorOutputQueues_ @@ -291,6 +292,8 @@ public void addHook(AcquisitionHook h, int type) { eventGenerationHooks_.add(h); } else if (type == BEFORE_HARDWARE_HOOK) { beforeHardwareHooks_.add(h); + } else if (type == BEFORE_Z_DRIVE_HOOK) { + beforeZDriveHooks_.add(h); } else if (type == AFTER_HARDWARE_HOOK) { afterHardwareHooks_.add(h); } else if (type == AFTER_CAMERA_HOOK) { @@ -413,6 +416,9 @@ public Iterable getEventGenerationHooks() { public Iterable getBeforeHardwareHooks() { return beforeHardwareHooks_; } + public Iterable getBeforeZDriveHooks() { + return beforeZDriveHooks_; + } public Iterable getAfterHardwareHooks() { return afterHardwareHooks_; From 39fc88e19665aed2e0e4d7e3b31861af5d7011cc Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Thu, 11 Apr 2024 13:12:14 -0700 Subject: [PATCH 3/5] Fixed issue remembering last event in Engine. --- .../micromanager/acqj/internal/Engine.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index f756726..e290290 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -141,7 +141,7 @@ public Future submitEventIterator(Iterator eventIterator) { } } - //Wait here is acquisition is paused + //Wait here if acquisition is paused while (event.acquisition_.isPaused()) { try { Thread.sleep(5); @@ -347,6 +347,9 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE stopHardwareSequences(hardwareSequencesInProgress); throw e; } + //keep track of last event to know what state the hardware was in without having to query it + lastEvent_ = event.getSequence() == null ? event : event.getSequence().get(event.getSequence().size() - 1); + event.acquisition_.postNotification( new AcqNotification( AcqNotification.Hardware.class, event.getAxesAsJSONString(), AcqNotification.Hardware.POST_HARDWARE)); for (AcquisitionHook h : event.acquisition_.getAfterHardwareHooks()) { @@ -940,9 +943,6 @@ public void run() { } }, "Changing additional properties"); - - //keep track of last event to know what state the hardware was in without having to query it - lastEvent_ = event.getSequence() == null ? event : event.getSequence().get(event.getSequence().size() - 1); } /** @@ -956,20 +956,23 @@ public void run() { private void startZDrive(final AcquisitionEvent event, HardwareSequences hardwareSequencesInProgress) throws HardwareControlException { final String zStage = core_.getFocusDevice(); - DoubleVector zSequence = event.isZSequenced() ? new DoubleVector() : null; - for (AcquisitionEvent e : event.getSequence()) { - if (zSequence != null) { - zSequence.add(e.getZPosition()); + if (event.getSequence() != null) { + DoubleVector zSequence = event.isZSequenced() ? new DoubleVector() : null; + for (AcquisitionEvent e : event.getSequence()) { + if (zSequence != null) { + zSequence.add(e.getZPosition()); + } } - } - try { - if (event.isZSequenced()) { - core_.loadStageSequence(zStage, zSequence); - hardwareSequencesInProgress.deviceNames.add(zStage); + try { + if (event.isZSequenced()) { + core_.stopStageSequence(zStage); + core_.loadStageSequence(zStage, zSequence); + hardwareSequencesInProgress.deviceNames.add(zStage); + } + } catch (Exception ex) { + ex.printStackTrace(); + throw new HardwareControlException(ex.getMessage()); } - } catch (Exception ex) { - ex.printStackTrace(); - throw new HardwareControlException(ex.getMessage()); } ////////////////////////////Set the Z Drive//////////////////////////////////////////// From f867b2fe76bdac9572217dc5cb202cd1eafa3dce Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Thu, 11 Apr 2024 15:04:47 -0700 Subject: [PATCH 4/5] Bumped version to 0.37.0: --- pom.xml | 2 +- src/main/java/org/micromanager/acqj/internal/Engine.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 624d1c8..d562c54 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.micro-manager.acqengj AcqEngJ - 0.36.0 + 0.37.0 jar AcqEngJ Java-based Acquisition engine for Micro-Manager diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index d539126..80accaf 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -686,7 +686,6 @@ private void prepareHardware(final AcquisitionEvent event, HardwareSequences hardwareSequencesInProgress) throws HardwareControlException { //Get the hardware specific to this acquisition final String xyStage = core_.getXYStageDevice(); - final String zStage = core_.getFocusDevice(); final String slm = core_.getSLMDevice(); //prepare sequences if applicable if (event.getSequence() != null) { From 37675a5c6bf2418b90517e0ab5f52637ce6010d3 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Thu, 11 Apr 2024 16:31:38 -0700 Subject: [PATCH 5/5] Fixed problem never sequencing when adding other oneDDrives in postionlist. Also adds some Javadoc (more is needed). --- .../org/micromanager/acqj/internal/Engine.java | 5 ++++- .../micromanager/acqj/main/AcquisitionEvent.java | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java index 80accaf..ce89380 100644 --- a/src/main/java/org/micromanager/acqj/internal/Engine.java +++ b/src/main/java/org/micromanager/acqj/internal/Engine.java @@ -1118,7 +1118,10 @@ private static boolean isSequencable(List previousEvents, // arbitrary z stages // TODO implement sequences along arbitrary other stage decives for (String stageDevice : previousEvent.getStageDeviceNames() ) { - return false; + if (!nextEvent.getStageSingleAxisStagePosition(stageDevice) + .equals(previousEvent.getStageSingleAxisStagePosition(stageDevice))) { + return false; + } } //xy stage diff --git a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java index e7cf432..a2d2b0f 100644 --- a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java +++ b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java @@ -56,7 +56,7 @@ enum SpecialFlag { private Double timeout_ms_ = null; private String configGroup_, configPreset_ = null; - private Double exposure_ = null; //leave null to keep exposaure unchanged + private Double exposure_ = null; //leave null to keep exposure unchanged private Long miniumumStartTime_ms_ = null; //For pausing between time points @@ -532,12 +532,25 @@ public void setStageCoordinate(String deviceName, double v) { setStageCoordinate(deviceName, v, null); } + /** + * Sets the (desired) position for the requested single axis stage. + * + * @param deviceName Name (as it is known to Micro-Manager) of the stage + * @param v Desired position in microns + * @param axisName Optional name of this axis. Can be null (I am not sure what this is used for). + */ public void setStageCoordinate(String deviceName, double v, String axisName) { stageCoordinates_.put(deviceName, v); stageDeviceNamesToAxisNames_.put(deviceName, axisName == null ? deviceName : axisName); } + /** + * returns the position of this stage in this event. + * + * @param deviceName Name (as it is known to Micro-Manager) of the stage + * @return Position of this stage in microns, or null if the stage is not found in this event + */ public Double getStageSingleAxisStagePosition(String deviceName) { if (deviceName == null) { return null;