diff --git a/pom.xml b/pom.xml
index 7b4faaa..2f4950f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.micro-manager.acqengj
AcqEngJ
- 0.26.6
+ 0.27.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 0d380c3..655ff66 100644
--- a/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java
+++ b/src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java
@@ -19,10 +19,23 @@
*/
public interface AcquisitionAPI {
- public static final int EVENT_GENERATION_HOOK = 0;
- public static final int BEFORE_HARDWARE_HOOK = 1;
- public static final int AFTER_HARDWARE_HOOK = 2;
- public static final int AFTER_CAMERA_HOOK = 3;
+ int EVENT_GENERATION_HOOK = 0;
+
+ // 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 changes to the hardware took place, but before camera exposure
+ // (either a snap or a sequence) is started
+ int AFTER_HARDWARE_HOOK = 2;
+
+ // Hook runs after the camera sequence acquisition has started. This can be used for
+ // external triggering of the camera
+ int AFTER_CAMERA_HOOK = 3;
+
+ // Hook runs after the camera exposure ended (when possible, before readout of the camera
+ // and availability of the images in memory).
+ int AFTER_EXPOSURE_HOOK = 4;
/**
* Call to ready acquisition to start receiving acquisiton events. No more hooks
diff --git a/src/main/java/org/micromanager/acqj/internal/Engine.java b/src/main/java/org/micromanager/acqj/internal/Engine.java
index 7f9fd4f..3066f8f 100644
--- a/src/main/java/org/micromanager/acqj/internal/Engine.java
+++ b/src/main/java/org/micromanager/acqj/internal/Engine.java
@@ -16,10 +16,9 @@
//
package org.micromanager.acqj.internal;
-/*
- * To change this template, choose Tools | Templates and open the template in
- * the editor.
- */
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.micromanager.acqj.api.AcquisitionAPI;
import org.micromanager.acqj.main.AcquisitionEvent;
import org.micromanager.acqj.api.AcquisitionHook;
@@ -55,6 +54,7 @@ public class Engine {
private static ExecutorService eventGeneratorExecutor_;
//Thread on which all communication with hardware occurs
private static ExecutorService acqExecutor_;
+ private static ExecutorService afterExposureExecutor_;
public Engine(CMMCore core) {
@@ -64,6 +64,8 @@ public Engine(CMMCore core) {
acqExecutor_ = Executors.newSingleThreadExecutor(r -> {
return new Thread(r, "Acquisition Engine Thread");
});
+ afterExposureExecutor_ = Executors.newSingleThreadExecutor(r ->
+ new Thread(r, "After Exposure Thread"));
eventGeneratorExecutor_ = Executors
.newSingleThreadExecutor((Runnable r) -> new Thread(r, "Acq Eng event generator"));
}
@@ -290,6 +292,10 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE
h.run(event);
h.close();
}
+ for (AcquisitionHook h : event.acquisition_.getAfterExposureHooks()) {
+ h.run(event);
+ h.close();
+ }
event.acquisition_.addToOutput(new TaggedImage(null, null));
event.acquisition_.markEventsFinished();
} else {
@@ -348,39 +354,53 @@ private void executeAcquisitionEvent(AcquisitionEvent event) throws InterruptedE
return;
}
- private void acquireImages(AcquisitionEvent event) throws HardwareControlException {
- acquireImages(event, true);
- }
-
/**
* Acquire 1 or more images in a sequence, add some metadata, then
- * put them into an output queue
+ * put them into an output queue.
*
* @param event
- * @param runAfterCameraHook
* @throws HardwareControlException
*/
- private void acquireImages(final AcquisitionEvent event,
- boolean runAfterCameraHook) throws HardwareControlException {
+ private void acquireImages(final AcquisitionEvent event) throws HardwareControlException {
+ FutureTask future = null;
try {
if (event.getSequence() != null && event.getSequence().size() > 1) {
//start hardware sequence
// core_.clearCircularBuffer();
- if (event.getSequence().stream().filter(e -> e.getCameraDeviceName() != null).count() == 0) {
- // Using the default (Core-Camera)
- core_.startSequenceAcquisition(event.getSequence().size(), 0, true);
- } else {
- // figure out how many images on each camera and start sequence with appropriate number on each
- HashMap cameraImageCounts = new HashMap();
- Set cameraDeviceNames = new HashSet();
- event.getSequence().stream().map(e -> e.getCameraDeviceName()).forEach(e -> cameraDeviceNames.add(e));
- for (String cameraDeviceName : cameraDeviceNames) {
- cameraImageCounts.put(cameraDeviceName, (int) event.getSequence().stream().filter(
- e -> e.getCameraDeviceName() != null && e.getCameraDeviceName().equals(cameraDeviceName)).count());
- core_.startSequenceAcquisition(cameraDeviceName, cameraImageCounts.get(cameraDeviceName), 0, true);
- }
+ // figure out how many images on each camera and start sequence with appropriate number on each
+ HashMap cameraImageCounts = new HashMap();
+ Set cameraDeviceNames = new HashSet();
+ event.getSequence().stream().map(e -> e.getCameraDeviceName()).forEach(e -> cameraDeviceNames.add(e));
+ cameraDeviceNames.remove(null);
+ if (cameraDeviceNames.isEmpty()) {
+ cameraDeviceNames.add(core_.getCameraDevice());
}
+ for (String cameraDeviceName : cameraDeviceNames) {
+ cameraImageCounts.put(cameraDeviceName, (int) event.getSequence().stream().filter(
+ e -> e.getCameraDeviceName() != null &&
+ e.getCameraDeviceName().equals(cameraDeviceName)).count());
+ if (cameraDeviceNames.size() == 1 && cameraDeviceName.equals(core_.getCameraDevice())) {
+ cameraImageCounts.put(cameraDeviceName, event.getSequence().size());
+ }
+ core_.startSequenceAcquisition(cameraDeviceName,
+ cameraImageCounts.get(cameraDeviceName), 0, true);
+ }
+ // Run after exposure hooks on a separate thread that checks if
+ // sequence finished. AcquireImages can only exit after the last
+ // future returns.
+ future = new FutureTask<>(() -> {
+ for (String cameraDeviceName : cameraDeviceNames) {
+ while (core_.isSequenceRunning(cameraDeviceName)) {
+ Thread.sleep(1);
+ }
+ }
+ for (AcquisitionHook h : event.acquisition_.getAfterExposureHooks()) {
+ h.run(event);
+ }
+ return null;
+ });
+ afterExposureExecutor_.execute(future);
} else {
//snap one image with no sequencing
@@ -391,6 +411,13 @@ private void acquireImages(final AcquisitionEvent event,
core_.setCameraDevice(currentCamera);
} else {
core_.snapImage();
+ // note: SnapImage will block until exposure finishes.
+ // If it is desired that AfterCameraHooks trigger cameras
+ // in Snap mode, those hooks (or SnapImage) should run in a separate thread, started
+ // after snapImage is started.
+ for (AcquisitionHook h : event.acquisition_.getAfterExposureHooks()) {
+ h.run(event);
+ }
}
}
} catch (Exception ex) {
@@ -417,18 +444,16 @@ private void acquireImages(final AcquisitionEvent event,
}
}
- //Run a hook after the camera sequence acquistion has started. This is used for setups
- //where an external device is used to trigger the camera
- if (runAfterCameraHook) {
- for (AcquisitionHook h : event.acquisition_.getAfterCameraHooks()) {
- h.run(event);
- }
+ // Run a hook after the camera sequence acquisition has started. This can be used for
+ // external triggering of the camera (when it is in sequence mode).
+ for (AcquisitionHook h : event.acquisition_.getAfterCameraHooks()) {
+ h.run(event);
}
if (event.acquisition_.isDebugMode()) {
core_.logMessage("images acquired, adding to output" );
}
- //Loop through and collect all acquired images. There will be
+ // Loop through and collect all acquired images. There will be
// (# of images in sequence) x (# of camera channels) of them
for (int i = 0; i < (event.getSequence() == null ? 1 : event.getSequence().size()); i++) {
double exposure;
@@ -512,6 +537,18 @@ private void acquireImages(final AcquisitionEvent event,
correspondingEvent.acquisition_.addToImageMetadata(ti.tags);
correspondingEvent.acquisition_.addToOutput(ti);
+
+ }
+ }
+ if (future != null) {
+ try {
+ future.get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ core_.logMessage("Interrupted while waiting for the future");
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ core_.logMessage("Exception during future execution");
}
}
}
@@ -883,7 +920,7 @@ public void run() {
}
}
- }, "Changing exposure");
+ }, "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);
diff --git a/src/main/java/org/micromanager/acqj/main/Acquisition.java b/src/main/java/org/micromanager/acqj/main/Acquisition.java
index ae1fac0..0fbcfc9 100644
--- a/src/main/java/org/micromanager/acqj/main/Acquisition.java
+++ b/src/main/java/org/micromanager/acqj/main/Acquisition.java
@@ -51,6 +51,7 @@ public class Acquisition implements AcquisitionAPI {
private CopyOnWriteArrayList beforeHardwareHooks_ = new CopyOnWriteArrayList();
private CopyOnWriteArrayList afterHardwareHooks_ = new CopyOnWriteArrayList();
private CopyOnWriteArrayList afterCameraHooks_ = new CopyOnWriteArrayList();
+ private CopyOnWriteArrayList afterExposureHooks_ = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList imageProcessors_ = new CopyOnWriteArrayList();
protected LinkedBlockingDeque firstDequeue_
= new LinkedBlockingDeque(IMAGE_QUEUE_SIZE);
@@ -240,7 +241,7 @@ public void addImageProcessor(TaggedImageProcessor p) {
@Override
public void addHook(AcquisitionHook h, int type) {
if (started_) {
- throw new RuntimeException("Cannot add hook after acquisiton started");
+ throw new RuntimeException("Cannot add hook after acquisition started");
}
if (type == EVENT_GENERATION_HOOK) {
eventGenerationHooks_.add(h);
@@ -250,12 +251,14 @@ public void addHook(AcquisitionHook h, int type) {
afterHardwareHooks_.add(h);
} else if (type == AFTER_CAMERA_HOOK) {
afterCameraHooks_.add(h);
+ } else if (type == AFTER_EXPOSURE_HOOK) {
+ afterExposureHooks_.add(h);
}
}
@Override
/**
- * Block until everything finished
+ * Block until everything finished.
*/
public void waitForCompletion() {
try {
@@ -363,9 +366,17 @@ public Iterable getBeforeHardwareHooks() {
return beforeHardwareHooks_;
}
- public Iterable getAfterHardwareHooks() { return afterHardwareHooks_; }
+ public Iterable getAfterHardwareHooks() {
+ return afterHardwareHooks_;
+ }
+
+ public Iterable getAfterCameraHooks() {
+ return afterCameraHooks_;
+ }
- public Iterable getAfterCameraHooks() { return afterCameraHooks_; }
+ public Iterable getAfterExposureHooks() {
+ return afterExposureHooks_;
+ }
public void addToOutput(TaggedImage ti) {
try {
diff --git a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java
index 7b89b24..9b59336 100644
--- a/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java
+++ b/src/main/java/org/micromanager/acqj/main/AcquisitionEvent.java
@@ -32,7 +32,7 @@
import org.micromanager.acqj.internal.Engine;
/**
- * Information about the acquisition of a single image or a sequence of image
+ * Information about the acquisition of a single image or a sequence of image.
*
*/
public class AcquisitionEvent {
@@ -71,7 +71,7 @@ enum SpecialFlag {
//Pattern to project onto SLM. Can either be int[] or byte[]
private Object slmImage_ = null;
- //Arbitary additional properties
+ //Arbitrary additional properties
private TreeSet properties_ = new TreeSet();
//for hardware sequencing
@@ -86,7 +86,7 @@ public AcquisitionEvent(AcquisitionAPI acq) {
}
/**
- * Constructor used for running a list of events in a sequence It should have
+ * Constructor used for running a list of events in a sequence. It should have
* already been verified that these events are sequencable. This constructor
* figures out which device types need a sequence and which ones can be left
* with a single value