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/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 5a117ff..ce89380 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);
@@ -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);
}
@@ -322,6 +323,7 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE
}
abortIfRequested(event, null);
}
+
HardwareSequences hardwareSequencesInProgress = new HardwareSequences();
try {
prepareHardware(event, hardwareSequencesInProgress);
@@ -329,6 +331,25 @@ 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;
+ }
+ //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()) {
@@ -338,6 +359,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,12 +686,10 @@ 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) {
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;
@@ -678,9 +698,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());
}
@@ -718,15 +735,6 @@ private void prepareHardware(final AcquisitionEvent event,
core_.loadXYStageSequence(xyStage, xSequence, ySequence);
hardwareSequencesInProgress.deviceNames.add(xyStage);
}
- if (event.isZSequenced()) {
- // at least some zStages freak out (in this case, NIDAQ board) when you
- // try to load a sequence while the sequence is still running. Nothing in
- // the engine stops a stage sequence if all goes well.
- // Stopping a sequence if it is not running hopefully will not harm anyone.
- core_.stopStageSequence(zStage);
- core_.loadStageSequence(zStage, zSequence);
- hardwareSequencesInProgress.deviceNames.add(zStage);
- }
if (event.isConfigGroupSequenced()) {
for (int i = 0; i < config.size(); i++) {
PropertySetting ps = config.getSetting(i);
@@ -758,38 +766,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() {
@@ -819,10 +795,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());
}
@@ -974,9 +954,76 @@ public void run() {
}
}, "Changing additional properties");
+ }
+
+ /**
+ * 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();
+ 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()) {
+ // at least some zStages freak out (in this case, NIDAQ board) when you
+ // try to load a sequence while the sequence is still running. Nothing in
+ // the engine stops a stage sequence if all goes well.
+ // Stopping a sequence if it is not running hopefully will not harm anyone.
+ core_.stopStageSequence(zStage);
+ 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());
+ }
- //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);
+ }
+ }, "Moving Z device");
}
/**
@@ -988,11 +1035,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");
@@ -1004,7 +1053,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() {
@@ -1069,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/AcqNotification.java b/src/main/java/org/micromanager/acqj/main/AcqNotification.java
index cfead99..9a64a89 100644
--- a/src/main/java/org/micromanager/acqj/main/AcqNotification.java
+++ b/src/main/java/org/micromanager/acqj/main/AcqNotification.java
@@ -13,6 +13,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_;
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;