From cd58f6aac898bbce20da90a949e7ea0ce7550c50 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 17 Feb 2026 16:11:59 -0800 Subject: [PATCH] add folder icon button to open save data directory --- .../lightsheetmanager/gui/data/Icons.java | 2 + .../gui/tabs/acquisition/SaveDataPanel.java | 37 ++++++- .../acquisitions/AcquisitionEngineSCAPE.java | 99 +++++++------------ 3 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/data/Icons.java b/src/main/java/org/micromanager/lightsheetmanager/gui/data/Icons.java index 59c09298..e9726e50 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/data/Icons.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/data/Icons.java @@ -21,6 +21,7 @@ public final class Icons { private static final URL ARROW_DOWN_PATH = Studio.class.getResource("/org/micromanager/icons/arrow_down.png"); private static final URL ARROW_RIGHT_PATH = Studio.class.getResource("/org/micromanager/icons/arrow_right.png"); private static final URL MICROSCOPE_PATH = Studio.class.getResource("/org/micromanager/icons/microscope.gif"); + private static final URL FOLDER_PATH = Studio.class.getResource("/org/micromanager/icons/folder.png"); public static final ImageIcon PAUSE = new ImageIcon(Objects.requireNonNull(PAUSE_PATH)); public static final ImageIcon PLAY = new ImageIcon(Objects.requireNonNull(PLAY_PATH)); @@ -31,5 +32,6 @@ public final class Icons { public static final ImageIcon ARROW_DOWN = new ImageIcon(Objects.requireNonNull(ARROW_DOWN_PATH)); public static final ImageIcon ARROW_RIGHT = new ImageIcon(Objects.requireNonNull(ARROW_RIGHT_PATH)); public static final ImageIcon MICROSCOPE = new ImageIcon(Objects.requireNonNull(MICROSCOPE_PATH)); + public static final ImageIcon FOLDER = new ImageIcon(Objects.requireNonNull(FOLDER_PATH)); } \ No newline at end of file diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SaveDataPanel.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SaveDataPanel.java index b8cd73a6..e986ab9b 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SaveDataPanel.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SaveDataPanel.java @@ -9,11 +9,14 @@ import org.micromanager.lightsheetmanager.gui.components.ComboBox; import org.micromanager.lightsheetmanager.gui.components.Panel; import org.micromanager.lightsheetmanager.gui.components.TextField; +import org.micromanager.lightsheetmanager.gui.data.Icons; import org.micromanager.lightsheetmanager.model.DataStorage; import javax.swing.JLabel; import java.awt.Color; +import java.awt.Desktop; import java.io.File; +import java.io.IOException; import java.util.EventObject; import java.util.Objects; @@ -23,6 +26,8 @@ public class SaveDataPanel extends Panel { private TextField txtSaveFileName_; private Button btnBrowse_; + private Button btnOpen_; + private ComboBox cbxSaveMode_; private CheckBox cbxSaveWhileAcquiring_; @@ -69,6 +74,7 @@ public void createUserInterface() { txtSaveFileName_.setText(acqSettings.saveNamePrefix()); btnBrowse_ = new Button("...", 26, 20); + btnOpen_ = new Button(Icons.FOLDER, 26, 20); cbxSaveMode_ = new ComboBox(DataStorage.SaveMode.toArray(), acqSettings.saveMode().toString(), 110, 20); @@ -80,14 +86,15 @@ public void createUserInterface() { add(txtSaveDirectory_, ""); add(btnBrowse_, "wrap"); add(lblSaveFileName, ""); - add(txtSaveFileName_, "wrap"); + add(txtSaveFileName_, ""); + add(btnOpen_, "wrap"); add(lblSaveMode, ""); - add(cbxSaveMode_, "span 2, wrap"); + add(cbxSaveMode_, "split 2, wrap"); add(cbxSaveWhileAcquiring_, "span 2, wrap"); } public void createEventHandlers() { - btnBrowse_.registerListener((EventObject e) -> { + btnBrowse_.registerListener(e -> { final File result = FileDialogs.openDir(frame_, "Please select the directory to save images to...", directorySelect_ @@ -98,6 +105,10 @@ public void createEventHandlers() { } }); + // use the text field so we don't need to update settings + btnOpen_.registerListener(e -> + openDirectory(txtSaveDirectory_.getText().trim())); + cbxSaveWhileAcquiring_.registerListener(e -> model_.acquisitions().settingsBuilder().saveImagesDuringAcquisition( cbxSaveWhileAcquiring_.isSelected())); @@ -111,4 +122,24 @@ public void createEventHandlers() { } + private void openDirectory(final String path) { + final File directory = new File(path); + if (directory.exists()) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.open(directory); + } catch (IOException e) { + model_.studio().logs().logError( + "Could not open the save directory."); + } + } else { + model_.studio().logs().logError( + "Desktop is not supported on this platform."); + } + } else { + model_.studio().logs().logError("Directory does not exist."); + } + } + } 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 a4734469..90bc5139 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineSCAPE.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineSCAPE.java @@ -855,7 +855,7 @@ private boolean doHardwareCalculations(PLogicSCAPE plc) { // nrSlicesSoftware *= 2; // } - // TODO: make this more robust + // TODO: make this more robust, should this be the first imaging camera? String cameraName; if (model_.devices().adapter().numSimultaneousCameras() > 1) { cameraName = "ImagingCamera1"; @@ -1478,84 +1478,53 @@ private double computeTimePointDuration2() { public double computeVolumeDuration(final DefaultAcquisitionSettingsSCAPE acqSettings) { final MultiChannelMode channelMode = acqSettings.channelSettings().channelMode(); - final int numChannels = acqSettings.channelSettings().numChannels(); final int numViews = acqSettings.volumeSettings().numViews(); + final int numChannels = acqSettings.channelSettings().numChannels(); final double delayBeforeView = acqSettings.volumeSettings().delayBeforeView(); + int numCameraTriggers = acqSettings.volumeSettings().slicesPerView(); if (acqSettings.cameraMode() == CameraMode.OVERLAP) { numCameraTriggers += 1; } - //System.out.println(acqSettings.getTimingSettings().sliceDuration()); - - // stackDuration is per-side, per-channel, per-position + // stackDuration is per-view, per-channel, per-position final double stackDuration = numCameraTriggers * acqSettings.timingSettings().sliceDuration(); - //System.out.println("stackDuration: " + stackDuration); - //System.out.println("numViews: " + numViews); - //System.out.println("numCameraTriggers: " + numCameraTriggers); + if (acqSettings.isUsingStageScanning()) { - return 1.0; - } else { - double channelSwitchDelay = 0; - if (channelMode == MultiChannelMode.VOLUME) { - channelSwitchDelay = 500; // estimate channel switching overhead time as 0.5s - // actual value will be hardware-dependent + final double rampDuration = 1; //getStageRampDuration(acqSettings); + final double retraceTime = 1; //getStageRetraceDuration(acqSettings); + // TODO(Jon): double-check these calculations below, at least they are better than before ;-) + if (acqSettings.acquisitionMode() == AcquisitionMode.STAGE_SCAN) { + if (channelMode == MultiChannelMode.SLICE_HW) { + return retraceTime + (numViews * ((rampDuration * 2) + (stackDuration * numChannels))); + } else { + // "normal" stage scan with volume channel switching + if (numViews == 1) { + // single-view so will retrace at beginning of each channel + return ((rampDuration * 2) + stackDuration + retraceTime) * numChannels; + } else { + // will only retrace at very start/end + return retraceTime + (numViews * ((rampDuration * 2) + stackDuration) * numChannels); + } + } + } else { + // TODO: do i need this case? is it correct? + // catch-all for NO_SCAN, etc + return (rampDuration * 2) + (stackDuration * numChannels * numViews) + retraceTime; } + } else { + // GALVO_SCAN (piezo-like logic for SCAPE) + // estimate channel switching overhead time as 0.5s, actual value will be hardware-dependent + final double channelSwitchDelay = (channelMode == MultiChannelMode.VOLUME) ? 500.0 : 0.0; if (channelMode == MultiChannelMode.SLICE_HW) { - return numViews * (delayBeforeView + stackDuration * numChannels); // channelSwitchDelay = 0 - } else { - return numViews * numChannels - * (delayBeforeView + stackDuration) + // channels switched per slice + return numViews * (delayBeforeView + stackDuration * numChannels); // channelSwitchDelay = 0 + } else { // VOLUME and VOLUME_HW + // channels switched per volume + return numViews * numChannels * (delayBeforeView + stackDuration) + (numChannels - 1) * channelSwitchDelay; } } - // TODO: stage scanning still needs to be taken into consideration -// if (acqSettings.isStageScanning || acqSettings.isStageStepping) { -// final double rampDuration = getStageRampDuration(acqSettings); -// final double retraceTime = getStageRetraceDuration(acqSettings); -// // TODO double-check these calculations below, at least they are better than before ;-) -// if (acqSettings.spimMode == AcquisitionModes.Keys.STAGE_SCAN) { -// if (channelMode == MultichannelModes.Keys.SLICE_HW) { -// return retraceTime + (numSides * ((rampDuration * 2) + (stackDuration * numChannels))); -// } else { // "normal" stage scan with volume channel switching -// if (numSides == 1) { -// // single-view so will retrace at beginning of each channel -// return ((rampDuration * 2) + stackDuration + retraceTime) * numChannels; -// } else { -// // will only retrace at very start/end -// return retraceTime + (numSides * ((rampDuration * 2) + stackDuration) * numChannels); -// } -// } -// } else if (acqSettings.spimMode == AcquisitionModes.Keys.STAGE_SCAN_UNIDIRECTIONAL -// || acqSettings.spimMode == AcquisitionModes.Keys.STAGE_STEP_SUPPLEMENTAL_UNIDIRECTIONAL -// || acqSettings.spimMode == AcquisitionModes.Keys.STAGE_SCAN_SUPPLEMENTAL_UNIDIRECTIONAL) { -// if (channelMode == MultichannelModes.Keys.SLICE_HW) { -// return ((rampDuration * 2) + (stackDuration * numChannels) + retraceTime) * numSides; -// } else { // "normal" stage scan with volume channel switching -// return ((rampDuration * 2) + stackDuration + retraceTime) * numChannels * numSides; -// } -// } else { // interleaved mode => one-way pass collecting both sides -// if (channelMode == MultichannelModes.Keys.SLICE_HW) { -// // single pass with all sides and channels -// return retraceTime + (rampDuration * 2 + stackDuration * numSides * numChannels); -// } else { // one-way pass collecting both sides, then rewind for next channel -// return ((rampDuration * 2) + (stackDuration * numSides) + retraceTime) * numChannels; -// } -// } -// } else { // piezo scan -// double channelSwitchDelay = 0; -// if (channelMode == MultichannelModes.Keys.VOLUME) { -// channelSwitchDelay = 500; // estimate channel switching overhead time as 0.5s -// // actual value will be hardware-dependent -// } -// if (channelMode == MultichannelModes.Keys.SLICE_HW) { -// return numSides * (delayBeforeSide + stackDuration * numChannels); // channelSwitchDelay = 0 -// } else { -// return numSides * numChannels -// * (delayBeforeSide + stackDuration) -// + (numChannels - 1) * channelSwitchDelay; -// } -// } } }