org.micromanager.acqj.main.Acquisition Maven / Gradle / Ivy
///////////////////////////////////////////////////////////////////////////////
// AUTHOR: Henry Pinkard, [email protected]
//
// COPYRIGHT: University of California, San Francisco, 2015
//
// LICENSE: This file is distributed under the BSD license.
// License text is included with the source distribution.
//
// This file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES.
//
package org.micromanager.acqj.main;
import java.util.Iterator;
import java.util.concurrent.*;
import java.util.function.Consumer;
import mmcorej.CMMCore;
import mmcorej.TaggedImage;
import mmcorej.org.json.JSONException;
import mmcorej.org.json.JSONObject;
import org.micromanager.acqj.api.AcquisitionAPI;
import org.micromanager.acqj.api.AcquisitionHook;
import org.micromanager.acqj.api.DataSink;
import org.micromanager.acqj.api.TaggedImageProcessor;
import org.micromanager.acqj.internal.Engine;
/**
* This is the main class for using AcqEngJ. AcquisitionAPI defines its public API.
* A full usage example can be found in example/FullExample.java
*/
public class Acquisition implements AcquisitionAPI {
private static final int IMAGE_QUEUE_SIZE = 30;
protected String xyStage_, zStage_, slm_;
protected volatile boolean eventsFinished_;
protected volatile boolean abortRequested_ = false;
private JSONObject summaryMetadata_;
private long startTime_ms_ = -1;
private volatile boolean paused_ = false;
private CopyOnWriteArrayList channelNames_ = new CopyOnWriteArrayList();
protected DataSink dataSink_;
private Consumer summaryMDAdder_;
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 imageProcessors_ = new CopyOnWriteArrayList();
protected LinkedBlockingDeque firstDequeue_
= new LinkedBlockingDeque(IMAGE_QUEUE_SIZE);
private ConcurrentHashMap> processorOutputQueues_
= new ConcurrentHashMap>();
public boolean debugMode_ = false;
private ThreadPoolExecutor savingExecutor_ = null;
private Exception abortException_ = null;
private Consumer imageMetadataProcessor_;
/**
* Primary constructor for creating Acquisitons. If DataSink is null, then a
* TaggedImageProcessor that diverts the images to custom downstream processing
* must be implemented.
*
* After this constructor returns, the Acquisiton will be ready to be given instructions
* by calling submitEventIterator. However, if any acquisition hooks or image processors
* are desired, they should be added before the first call of submitEventIterator
*
*/
public Acquisition(DataSink sink) {
this(sink, null);
}
/**
* Version of the constructor that accepts a function that can modify SummaryMetadata as needed
*/
public Acquisition(DataSink sink, Consumer summaryMDAdder) {
core_ = Engine.getCore();
summaryMDAdder_ = summaryMDAdder;
dataSink_ = sink;
initialize();
}
/**
* Don't delete, called by python side
*/
public DataSink getDataSink() {
return dataSink_;
}
/**
* Don't delete, called by python side
*/
public void setDebugMode(boolean debug) {debugMode_ = debug; }
public boolean isDebugMode() {return debugMode_;}
public boolean isAbortRequested() {
return abortRequested_;
}
/**
* Auto abort caused by an exception during acquisition
* @param e
*/
public void abort(Exception e) {
abortException_ = e;
abort();
}
// Pycromanager calls this
public void checkForExceptions() throws Exception {
if (abortException_ != null) {
throw abortException_;
}
}
public void abort() {
if (abortRequested_) {
return;
}
abortRequested_ = true;
if (this.isPaused()) {
this.setPaused(false);
}
Engine.getInstance().finishAcquisition(this);
}
private void addToSummaryMetadata(JSONObject summaryMetadata) {
if (summaryMDAdder_ != null) {
summaryMDAdder_.accept(summaryMetadata);
}
}
public void addToImageMetadata(JSONObject tags) {
if (imageMetadataProcessor_ != null) {
imageMetadataProcessor_.accept(tags);
}
}
@Override
public Future submitEventIterator(Iterator evt) {
return Engine.getInstance().submitEventIterator(evt);
}
private void startSavingExecutor() {
savingExecutor_ = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(),
(Runnable r) -> new Thread(r, "Acquisition image processing and saving thread"));
savingExecutor_.submit(() -> {
try {
while (true) {
if (debugMode_) {
core_.logMessage("Image queue size: " + firstDequeue_.size());
}
TaggedImage img;
// Grab the image, either directly from the queue to acqEng added it to,
// or from the output of the last image processor
if (imageProcessors_.isEmpty()) {
if (debugMode_) {
core_.logMessage("waiting for image to save" );
}
img = firstDequeue_.takeFirst();
if (debugMode_) {
core_.logMessage("got image to save" );
}
saveImage(img);
if (debugMode_) {
core_.logMessage("image saved; finished = " +
(img.pix == null && img.tags == null));
}
} else {
// get the last processor
LinkedBlockingDeque dequeue = processorOutputQueues_.get(
imageProcessors_.get(imageProcessors_.size() - 1));
img = dequeue.takeFirst();
if (dataSink_ != null) {
if (debugMode_) {
core_.logMessage("Saving image");
}
saveImage(img);
if (debugMode_) {
core_.logMessage("Finished saving image");
}
}
}
if (img.pix == null && img.tags == null) {
// Last image because acquisition is shutting down.
// Storage and image processors will also recieve this
// signal and shut down on their own
savingExecutor_.shutdown();
return;
}
}
} catch (InterruptedException e) {
//this should never happen
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
}
});
}
@Override
public void addImageProcessor(TaggedImageProcessor p) {
imageProcessors_.add(p);
processorOutputQueues_.put(p, new LinkedBlockingDeque(IMAGE_QUEUE_SIZE));
if (imageProcessors_.size() == 1) {
p.setAcqAndDequeues(this, firstDequeue_, processorOutputQueues_.get(p));
} else {
p.setAcqAndDequeues(this, processorOutputQueues_.get(imageProcessors_.size() - 2),
processorOutputQueues_.get(imageProcessors_.size() - 1));
}
}
@Override
public void addHook(AcquisitionHook h, int type) {
if (type == EVENT_GENERATION_HOOK) {
eventGenerationHooks_.add(h);
} else if (type == BEFORE_HARDWARE_HOOK) {
beforeHardwareHooks_.add(h);
} else if (type == AFTER_HARDWARE_HOOK) {
afterHardwareHooks_.add(h);
} else if (type == AFTER_CAMERA_HOOK) {
afterCameraHooks_.add(h);
}
}
@Override
/**
* Block until everything finished
*/
public void waitForCompletion() {
try {
//wait for event generation to shut down
while (!eventsFinished_) {
Thread.sleep(5);
}
// Waiting for saving to finish and all resources to complete
while (!savingExecutor_.isTerminated()) {
Thread.sleep(5);
}
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
/**
* 1) Get the names or core devices to be used in acquistion 2) Create
* Summary metadata 3) Initialize data sink
*/
protected void initialize() {
JSONObject summaryMetadata = AcqEngMetadata.makeSummaryMD(this);
addToSummaryMetadata(summaryMetadata);
try {
//keep local copy for viewer
summaryMetadata_ = new JSONObject(summaryMetadata.toString());
} catch (JSONException ex) {
System.err.print("Couldn't copy summaary metadata");
ex.printStackTrace();
}
if (dataSink_ != null) {
//It could be null if not using saving and viewing and diverting with custom processor
dataSink_.initialize(this, summaryMetadata);
startSavingExecutor();
}
}
/**
* Called by acquisition engine to save an image
*/
private void saveImage(TaggedImage image) {
if (image.tags == null && image.pix == null) {
dataSink_.finish();
eventsFinished_ = true; //should have already been done, but just in case
} else {
//Now that all data processors have run, the channel index can be inferred
//based on what channels show up at runtime
String channelName = AcqEngMetadata.getChannelName(image.tags);
if (!channelNames_.contains(channelName)) {
channelNames_.add(channelName);
}
AcqEngMetadata.setAxisPosition(image.tags, AcqEngMetadata.CHANNEL_AXIS,
channelNames_.indexOf(channelName));
//this method doesnt return until all images have been written to disk
dataSink_.putImage(image);
}
}
public long getStartTime_ms() {
return startTime_ms_;
}
public void setStartTime_ms(long time) {
startTime_ms_ = time;
}
public boolean isPaused() {
return paused_;
}
public synchronized void setPaused(boolean pause) {
paused_ = pause;
}
public JSONObject getSummaryMetadata() {
return summaryMetadata_;
}
public boolean anythingAcquired() {
return dataSink_ == null ? true : dataSink_.anythingAcquired();
}
@Override
public void addImageMetadataProcessor(Consumer processor) {
if (imageMetadataProcessor_ == null) {
imageMetadataProcessor_ = processor;
} else {
throw new RuntimeException("Multiple metadata processors not supported");
}
}
public Iterable getEventGenerationHooks() {
return eventGenerationHooks_;
}
public Iterable getBeforeHardwareHooks() {
return beforeHardwareHooks_;
}
public Iterable getAfterHardwareHooks() { return afterHardwareHooks_; }
public Iterable getAfterCameraHooks() { return afterCameraHooks_; }
public void addToOutput(TaggedImage ti) throws InterruptedException {
firstDequeue_.putLast(ti);
}
public void finish() {
Engine.getInstance().finishAcquisition(this);
}
public void eventsFinished() {
eventsFinished_ = true;
}
public boolean areEventsFinished() {
return eventsFinished_;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy