From 59e71f6e3dfe8cc4b4d4e41ff3df0be925ab6c64 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 26 Sep 2026 17:59:58 -0700 Subject: [PATCH] open the acquisition window from LSM instead of MMAcquisition --- .../model/acquisitions/AcquisitionEngine.java | 10 +-- .../acquisitions/AcquisitionEngineScape.java | 64 ++++++++++++++++++- 2 files changed, 68 insertions(+), 6 deletions(-) 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 1f444c8..a37da75 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java @@ -66,7 +66,8 @@ public abstract class AcquisitionEngine implements AcquisitionManager, MMAcquist private final AutofocusAdapter autofocus_; - protected Datastore datastore_; + // visibility: written on the acquisition thread, read on the edt when a window asks to close + protected volatile Datastore datastore_; protected Pipeline curPipeline_; protected long nextWakeTime_ = -1; @@ -640,10 +641,9 @@ public boolean abortRequest() { if (acq == null) { return true; // nothing is running, so there is nothing to protect } - // always refuse while a run is live: Micro-Manager vetoes the close, and that veto is what - // keeps it from racing the abort into finish(), whose save closes the same datastore from - // the acquisition thread. answering yes only aborts, so the close succeeds on the next - // attempt. the display's abort button also lands here and ignores the result. + // always refuse while a run is live: closing the live window would close the datastore + // the run is still writing. answering yes only aborts, so the close succeeds on the next + // attempt. if (model_.logging().confirmOrDefault("Abort Acquisition", "Abort the current acquisition task?", false)) { acq.abort(); diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java index 79377c3..bfce09d 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java @@ -16,6 +16,13 @@ import org.micromanager.data.SummaryMetadata; import org.micromanager.data.internal.DefaultDatastore; import org.micromanager.data.internal.DefaultSummaryMetadata; +import org.micromanager.data.internal.PropertyKey; +import org.micromanager.display.DataViewer; +import org.micromanager.display.DataViewerListener; +import org.micromanager.display.DisplaySettings; +import org.micromanager.display.DisplayWindow; +import org.micromanager.display.internal.DefaultDisplayManager; +import org.micromanager.display.internal.RememberedDisplaySettings; import org.micromanager.lightsheetmanager.api.data.AcquisitionMode; import org.micromanager.lightsheetmanager.api.data.CameraLibrary; import org.micromanager.lightsheetmanager.api.data.CameraMode; @@ -64,6 +71,20 @@ public class AcquisitionEngineScape extends AcquisitionEngine { private double scanAccelX_; private boolean isPolling_; // true if polling was enabled at the start of an acquisition + // Vetoes closing the running acquisition's window unless the user confirms an abort. Otherwise + // the display manager closes the store under the running acquisition and every later image + // is dropped without an abort. + private final DataViewerListener liveWindowCloseGuard_ = new DataViewerListener() { + @Override + public boolean canCloseViewer(final DataViewer viewer) { + final Datastore live = datastore_; + if (live == null || viewer.getDataProvider() != live) { + return true; // not the running acquisition's window + } + return abortRequest(); + } + }; + public AcquisitionEngineScape(final LightSheetManager model) { super(Objects.requireNonNull(model)); } @@ -307,7 +328,8 @@ boolean run() { // TODO(Brandon): where should i get this from? SequenceSettings.Builder sequenceSettingsBuilder = new SequenceSettings.Builder(); - sequenceSettingsBuilder.shouldDisplayImages(true); + // LSM opens the window itself, see createAcquisitionDisplay() + sequenceSettingsBuilder.shouldDisplayImages(false); // Write images to disk as they arrive instead of accumulating the run in memory. // MMAcquisition swaps StorageRAM for the preferred save mode set above only when both // save() and root() are set, so setting them here is what selects streaming. This is what @@ -359,6 +381,8 @@ && new File(saveDir + File.separator + datasetName).isDirectory()) { } } + createAcquisitionDisplay(dsmd); + studio_.events().registerForEvents(this); // commented because this is prob specific to MM MDAs // studio_.events().post(new DefaultAcquisitionStartedEvent(datastore_, this, @@ -855,6 +879,44 @@ private Double readBaseFocusPosition(final ScapeAcquisitionSettings settings) { } } + /** + * Opens the acquisition's window, with the settings and position MMAcquisition would use. + * MMAcquisition's own window is not used because its abort and pause buttons are never + * unsubscribed after an LSM acquisition, so every window it opens stays in memory after it is + * closed. + * + * @param summary the summary metadata the dataset was created with, which names its channels + */ + private void createAcquisitionDisplay(final SummaryMetadata summary) { + // before the window: closing the last window closes the store only if it is managed + studio_.displays().manage(datastore_); + + // start from the settings of the last acquisition window that was closed + final String profileKey = PropertyKey.ACQUISITION_DISPLAY_SETTINGS.key(); + final DisplaySettings remembered = + studio_.displays().displaySettingsFromProfile(profileKey); + final DisplaySettings.Builder builder = remembered != null + ? remembered.copyBuilder() + : studio_.displays().displaySettingsBuilder(); + final List channelNames = summary.getChannelNameList(); + if (channelNames.size() == 1) { + builder.colorModeGrayscale(); + } else if (channelNames.size() > 1) { + builder.colorModeComposite(); + } + for (int i = 0; i < channelNames.size(); i++) { + builder.channel(i, RememberedDisplaySettings.loadChannel(studio_, + summary.getChannelGroup(), channelNames.get(i), null)); + } + + final DisplayWindow display = + studio_.displays().createDisplay(datastore_, null, builder.build()); + display.setWindowPositionKey(DefaultDisplayManager.MDA_DISPLAY); + display.setDisplaySettingsProfileKey(profileKey); + // ahead of the display manager's listener at 100, which is the one that closes the store + display.addListener(liveWindowCloseGuard_, 1); + } + @Override void finish() { // finish() runs on EVERY path (the requestRun finally), even a setup-abort where the