diff --git a/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionManager.java b/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionManager.java index 7c0ef59..8b058a4 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionManager.java +++ b/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionManager.java @@ -22,6 +22,16 @@ public interface AcquisitionManager { */ Future> requestRun(boolean speedTest); + /** + * Request that a test acquisition is run. + * + *
A test acquisition runs a single time point and does not save anything to disk. The + * settings shown in the user interface are left unchanged. + * + * @return a future that completes when the acquisition finishes + */ + Future> requestTestAcquisition(); + /** * Request the running acquisition to stop. */ diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java index 8cf6c3d..cd01f0c 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java @@ -184,7 +184,6 @@ private void createUserInterface() { swapTimingSettingsPanels(isUsingAdvancedTiming); btnRunOverviewAcq_.setEnabled(false); // TODO: re-enable when these features are put in - btnTestAcquisition_.setEnabled(false); // set ui sizes, should match the MigLayout constraints pnlChannelTable_.setAbsoluteSize(280, 400); @@ -257,6 +256,7 @@ private void createEventHandlers() { btnOpenPlaylist_.setEnabled(false); // TODO: enable when playlist is implemented btnSpeedTest_.registerListener(() -> runAcquisition(true)); + btnTestAcquisition_.registerListener(this::runTestAcquisition); btnRunOverviewAcq_.registerListener(() -> { // TODO: run the overview acq }); @@ -326,6 +326,7 @@ private void acqFinishedCallback() { btnRunAcquisition_.setState(false); btnPauseAcquisition_.setEnabled(false); btnSpeedTest_.setEnabled(true); + btnTestAcquisition_.setEnabled(true); }); } catch (InterruptedException e) { model_.studio().logs().logError("Acquisition was interrupted!"); @@ -337,7 +338,21 @@ private void acqFinishedCallback() { private void runAcquisition(boolean speedTest) { btnPauseAcquisition_.setEnabled(true); btnSpeedTest_.setEnabled(false); - Future> acqFinished = model_.acquisitions().requestRun(speedTest); + btnTestAcquisition_.setEnabled(false); + waitForAcquisition(model_.acquisitions().requestRun(speedTest)); + } + + private void runTestAcquisition() { + // this run is started from a button that is not the toggle, so select the toggle here + // or it reads "Start Acquisition" for as long as the test acquisition is running + btnRunAcquisition_.setState(true); + btnPauseAcquisition_.setEnabled(true); + btnSpeedTest_.setEnabled(false); + btnTestAcquisition_.setEnabled(false); + waitForAcquisition(model_.acquisitions().requestTestAcquisition()); + } + + private void waitForAcquisition(final Future> acqFinished) { // Launch new thread to update the button when the acquisition is complete new Thread(() -> { try { diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/UserSettings.java b/src/main/java/org/micromanager/lightsheetmanager/model/UserSettings.java index 71ad000..d8948aa 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/UserSettings.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/UserSettings.java @@ -134,17 +134,21 @@ public void loadFromJson(final String json, final boolean notify) { * Save user settings. */ public void save() { - // make settings current before saving - model_.acquisitions().updateSettings(); - // settings key based on geometry type final GeometryType geometryType = model_.devices().adapter().geometry(); final String key = SETTINGS_PREFIX + geometryType.toString().toUpperCase(); + // Build from the builder, which holds what the user set in the user interface. The + // acquisition settings can carry run scoped overrides, for example a test acquisition + // turning saving off, and writing one of those to the profile would change the user's + // settings for every later session. Building here also means saving no longer rebuilds + // the settings a running acquisition is reading. + final ScapeAcquisitionSettings settings = model_.acquisitions().settingsBuilder().build(); + // save acquisition settings - settings_.putString(key, model_.acquisitions().settings().toJson()); + settings_.putString(key, settings.toJson()); model_.studio().logs().logDebugMessage("saved JSON to " + key + ": " - + model_.acquisitions().settings().toPrettyJson()); + + settings.toPrettyJson()); // save plugin settings settings_.putString(SETTINGS_PLUGIN, model_.pluginSettings().toJson()); diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java index 9e146c4..bd73d54 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java @@ -52,6 +52,9 @@ public abstract class AcquisitionEngine implements AcquisitionManager, MMAcquist // cannot answer "is a run in flight?": the engines only assign it partway through run(), so it // stays null across all of setup() and the arming that follows. private volatile boolean acquisitionRequested_ = false; + // true while a test acquisition is in flight, read by updateSettings() + private volatile boolean testAcquisition_; + // a stop asked for before the acquisition was started, acted on by the checks in requestRun() // and in each engine's run() private volatile boolean stopRequested_ = false; @@ -282,7 +285,20 @@ public void updateSettings(final ScapeAcquisitionSettings acqSettings) { * Build the {@code DefaultAcquisitionSettingsSCAPE} with the builder and update settings. */ public void updateSettings() { - acqSettings_ = asb_.build(); + // Build fully before assigning: acqSettings_ is read from other threads, and a second + // assignment here would briefly publish the user's settings during a test acquisition. + ScapeAcquisitionSettings settings = asb_.build(); + // Re-apply here and not at the call site: acqSettings_ is rebuilt from the builder at + // several points during a run, and the builder holds the user's settings, so an override + // applied once is discarded by the next rebuild. + if (testAcquisition_) { + settings = settings.copyBuilder() + .saveImagesDuringAcquisition(false) + .useTimePoints(false) + .numTimePoints(1) + .build(); + } + acqSettings_ = settings; } public Future> requestRun() { @@ -291,6 +307,15 @@ public Future> requestRun() { @Override public Future> requestRun(boolean speedTest) { + return requestRun(speedTest, false); + } + + @Override + public Future> requestTestAcquisition() { + return requestRun(false, true); + } + + private Future> requestRun(boolean speedTest, boolean testAcquisition) { // set here and not inside the task: a Stop clicked while the task is still queued, or // anywhere inside setup(), must find a run in flight acquisitionRequested_ = true; @@ -308,6 +333,12 @@ public Future> requestRun(boolean speedTest) { long startNs = 0; // set alongside runId at START try { + // set inside the task and not at request time: the executor runs one task at a + // time, so this cannot reach a run that is already in flight, and the finally + // below only ever clears the request it belongs to. Set before updateSettings() + // so the first rebuild already carries the override. + testAcquisition_ = testAcquisition; + updateSettings(); // make sure settings are current if (speedTest) { @@ -363,6 +394,8 @@ public Future> requestRun(boolean speedTest) { // cleared last of the run-state flags: while it is set, requestStop() treats // a stop as something to act on rather than an error acquisitionRequested_ = false; + // cleared here so the next run rebuilds from the user's own settings + testAcquisition_ = false; // free the datastore so a large store isn't kept in memory (matches MM's // AcqEngJAdapter.onAcquisitionEnded); also what the save guard checks to skip aborted/empty runs datastore_ = null;