Skip to content

Commit

Permalink
Merge pull request #89 from nicost/AfterExposureHooks
Browse files Browse the repository at this point in the history
Adds after exposure hooks
  • Loading branch information
henrypinkard authored May 30, 2023
2 parents ba5c336 + 622b942 commit 386b491
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 45 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.26.6</version>
<version>0.27.0</version>
<packaging>jar</packaging>
<name>AcqEngJ</name>
<description>Java-based Acquisition engine for Micro-Manager</description>
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/org/micromanager/acqj/api/AcquisitionAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
103 changes: 70 additions & 33 deletions src/main/java/org/micromanager/acqj/internal/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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"));
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Void> 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<String, Integer> cameraImageCounts = new HashMap<String, Integer>();
Set<String> cameraDeviceNames = new HashSet<String>();
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<String, Integer> cameraImageCounts = new HashMap<String, Integer>();
Set<String> cameraDeviceNames = new HashSet<String>();
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
Expand All @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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");
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/org/micromanager/acqj/main/Acquisition.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class Acquisition implements AcquisitionAPI {
private CopyOnWriteArrayList<AcquisitionHook> beforeHardwareHooks_ = new CopyOnWriteArrayList<AcquisitionHook>();
private CopyOnWriteArrayList<AcquisitionHook> afterHardwareHooks_ = new CopyOnWriteArrayList<AcquisitionHook>();
private CopyOnWriteArrayList<AcquisitionHook> afterCameraHooks_ = new CopyOnWriteArrayList<AcquisitionHook>();
private CopyOnWriteArrayList<AcquisitionHook> afterExposureHooks_ = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<TaggedImageProcessor> imageProcessors_ = new CopyOnWriteArrayList<TaggedImageProcessor>();
protected LinkedBlockingDeque<TaggedImage> firstDequeue_
= new LinkedBlockingDeque<TaggedImage>(IMAGE_QUEUE_SIZE);
Expand Down Expand Up @@ -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);
Expand All @@ -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 {
Expand Down Expand Up @@ -363,9 +366,17 @@ public Iterable<AcquisitionHook> getBeforeHardwareHooks() {
return beforeHardwareHooks_;
}

public Iterable<AcquisitionHook> getAfterHardwareHooks() { return afterHardwareHooks_; }
public Iterable<AcquisitionHook> getAfterHardwareHooks() {
return afterHardwareHooks_;
}

public Iterable<AcquisitionHook> getAfterCameraHooks() {
return afterCameraHooks_;
}

public Iterable<AcquisitionHook> getAfterCameraHooks() { return afterCameraHooks_; }
public Iterable<AcquisitionHook> getAfterExposureHooks() {
return afterExposureHooks_;
}

public void addToOutput(TaggedImage ti) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<ThreeTuple> properties_ = new TreeSet<ThreeTuple>();

//for hardware sequencing
Expand All @@ -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
Expand Down

0 comments on commit 386b491

Please sign in to comment.