Skip to content

Commit

Permalink
Merge pull request micro-manager#118 from nicost/errorReporing
Browse files Browse the repository at this point in the history
Adds hook that runs after all hardware except for the Z drive has been moved in postion.
  • Loading branch information
nicost authored Apr 13, 2024
2 parents c4cfc89 + 37675a5 commit 198aabe
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 74 deletions.
2 changes: 1 addition & 1 deletion 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.acqengj</groupId>
<artifactId>AcqEngJ</artifactId>
<version>0.36.0</version>
<version>0.37.0</version>
<packaging>jar</packaging>
<name>AcqEngJ</name>
<description>Java-based Acquisition engine for Micro-Manager</description>
Expand Down
33 changes: 20 additions & 13 deletions src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
* <p>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);

Expand All @@ -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
Expand Down
160 changes: 106 additions & 54 deletions src/main/java/org/micromanager/acqj/internal/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public Future submitEventIterator(Iterator<AcquisitionEvent> eventIterator) {
}
}

//Wait here is acquisition is paused
//Wait here if acquisition is paused
while (event.acquisition_.isPaused()) {
try {
Thread.sleep(5);
Expand All @@ -162,8 +162,9 @@ public Future submitEventIterator(Iterator<AcquisitionEvent> 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);
}
Expand Down Expand Up @@ -322,13 +323,33 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE
}
abortIfRequested(event, null);
}

HardwareSequences hardwareSequencesInProgress = new HardwareSequences();
try {
prepareHardware(event, hardwareSequencesInProgress);
} catch (HardwareControlException e) {
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()) {
Expand All @@ -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 &&
Expand Down Expand Up @@ -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;
Expand All @@ -678,9 +698,6 @@ private void prepareHardware(final AcquisitionEvent event,
core_.getConfigData(group, event.getSequence().get(0).getConfigPreset());
LinkedList<StrVector> propSequences = event.isConfigGroupSequenced() ? new LinkedList<StrVector>() : null;
for (AcquisitionEvent e : event.getSequence()) {
if (zSequence != null) {
zSequence.add(e.getZPosition());
}
if (xSequence != null) {
xSequence.add(e.getXPosition());
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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");
}

/**
Expand All @@ -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");
Expand All @@ -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() {
Expand Down Expand Up @@ -1069,7 +1118,10 @@ private static boolean isSequencable(List<AcquisitionEvent> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

}
Expand Down
Loading

0 comments on commit 198aabe

Please sign in to comment.