diff --git a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java index 85ef4585..8d24b24d 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java +++ b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java @@ -14,7 +14,7 @@ public class LightSheetManagerPlugin implements MenuPlugin, SciJavaPlugin { public static final String copyright = "Applied Scientific Instrumentation (ASI), 2022-2026"; public static final String description = "A plugin to control various types of light sheet microscopes."; public static final String menuName = "Light Sheet Manager"; - public static final String version = "0.6.1"; + public static final String version = "0.6.3"; private Studio studio_; private LightSheetManager model_; 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 1a90a6b7..01b6fbf1 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java @@ -177,8 +177,11 @@ private void createUserInterface() { settings.acquisitionMode(), 180, 24); + final boolean isUsingAdvancedTiming = settings.isUsingAdvancedTiming(); cbxUseAdvancedTiming_ = new CheckBox("Use advanced timing settings", - 12, settings.isUsingAdvancedTiming(), CheckBox.RIGHT); + 12, isUsingAdvancedTiming, CheckBox.RIGHT); + // initial enabled or disabled state + swapTimingSettingsPanels(isUsingAdvancedTiming); btnRunOverviewAcq_.setEnabled(false); // TODO: re-enable when these features are put in btnTestAcquisition_.setEnabled(false); @@ -275,9 +278,11 @@ private void createEventHandlers() { }); // select the acquisition mode - cmbAcquisitionModes_.registerListener( - () -> model_.acquisitions().settingsBuilder() - .acquisitionMode(cmbAcquisitionModes_.getSelected())); + cmbAcquisitionModes_.registerListener(() -> { + model_.acquisitions().settingsBuilder() + .acquisitionMode(cmbAcquisitionModes_.getSelected()); + model_.acquisitions().updateDurationLabels(); + }); // switches timing panels based on check box cbxUseAdvancedTiming_.registerListener(() -> { diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/SettingsTab.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/SettingsTab.java index 8c4e6764..17d19ce7 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/SettingsTab.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/SettingsTab.java @@ -156,10 +156,10 @@ private void createUserInterface() { add(pnlScanSettings, "wrap"); if (isUsingPLogic_) { - add(pnlLightSheet, "growx"); + add(pnlLightSheet, "growx, wrap"); } - add(btnCreateConfigGroup_, "gaptop 40"); + add(btnCreateConfigGroup_, ""); } private void createEventHandlers() { diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java index 038cedc4..2f805969 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/acquisition/SavePanel.java @@ -118,6 +118,13 @@ public void createUserInterface() { btnLoadSettings_ = new Button("Load", 60, 20); btnConvertSettings_ = new Button("Convert", 72, 20); + btnBrowse_.setToolTipText("Select the save directory with the file browser."); + btnOpen_.setToolTipText("Open the file explorer to the save directory."); + btnSaveSettings_.setToolTipText("Save the current acquisition settings to JSON."); + btnLoadSettings_.setToolTipText("Load acquisitions settings from a JSON file."); + btnConvertSettings_.setToolTipText("Convert \"AcqSettings.txt\" from the " + + "Micro-Manager 1.4 plugin into Light Sheet Manager settings."); + add(lblSaveDirectory, ""); add(txtSaveDirectory_, ""); add(btnBrowse_, ""); diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/PiezoCalibrationPanel.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/PiezoCalibrationPanel.java index 10e2e289..40822a79 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/PiezoCalibrationPanel.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/PiezoCalibrationPanel.java @@ -173,6 +173,8 @@ private void createEventHandlers() { .sliceCalibrationBuilder().offset(newOffset); model_.studio().logs().logMessage("updated offset for view " + pathNum_ + "; new value is " + newOffset + " (with channel offset of " + channelOffset + ")"); + } else { + model_.studio().logs().showError("The beam must be enabled to update the offset.", btnUpdate_); } }); } diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/SetupPanel.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/SetupPanel.java index cefa73ed..e5027cdb 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/SetupPanel.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/setup/SetupPanel.java @@ -7,8 +7,6 @@ import java.util.Objects; -// Note: changes based on camera trigger mode - /** * A setup panel for diSPIM. */ diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/PLogicScape.java b/src/main/java/org/micromanager/lightsheetmanager/model/PLogicScape.java index 587f0da3..60f5babd 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/PLogicScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/PLogicScape.java @@ -4,7 +4,6 @@ import mmcorej.Configuration; import org.micromanager.Studio; import org.micromanager.lightsheetmanager.LightSheetManager; -import org.micromanager.lightsheetmanager.api.TimingSettings; import org.micromanager.lightsheetmanager.api.data.AcquisitionMode; import org.micromanager.lightsheetmanager.api.data.CameraMode; import org.micromanager.lightsheetmanager.api.data.GeometryType; @@ -193,7 +192,7 @@ public boolean prepareControllerForAcquisition( final boolean isInterleaved = (settings.acquisitionMode() == AcquisitionMode.STAGE_SCAN_INTERLEAVED); // figure out the speed we should be going according to slice period, slice spacing, geometry, etc. - final double requestedMotorSpeed = computeScanSpeed(settings, scanner_.getSPIMNumScansPerSlice()); // in mm/sec + final double requestedMotorSpeed = computeScanSpeed(settings); // in mm/sec final double maxSpeed = xyStage_.getMaxSpeedX(); if (requestedMotorSpeed > (maxSpeed * 0.8)) { @@ -265,10 +264,8 @@ public boolean prepareControllerForAcquisition( } // Compute appropriate motor speed in mm/s for the given stage scanning settings - public double computeScanSpeed(ScapeAcquisitionSettings settings, final int numScansPerSlice) { - //double sliceDuration = settings.timingSettings().sliceDuration(); - // TODO: getSliceDuration only used here, but maybe should be computed elsewhere, and get with method above? - double sliceDuration = getSliceDuration(settings.timing(), numScansPerSlice); + public double computeScanSpeed(final ScapeAcquisitionSettings settings) { + double sliceDuration = settings.timing().sliceDuration(); if (settings.acquisitionMode() == AcquisitionMode.STAGE_SCAN_INTERLEAVED) { // pretend like our slice takes twice as long so that we move the correct speed // this has the effect of halving the motor speed, but keeping the scan distance the same @@ -291,25 +288,10 @@ private int computeScanChannelsPerPass(ScapeAcquisitionSettings settings) { * @param motorSpeed * @return */ - public double computeScanAcceleration(final double motorSpeed, ScapeAcquisitionSettings settings) { + public double computeScanAcceleration(final double motorSpeed, final ScapeAcquisitionSettings settings) { return (10 + 100 * (motorSpeed / xyStage_.getMaxSpeedX())) * settings.stageScan().accelerationFactor(); } - // TODO: scanNum was part of SliceSettings (now TimingSettings) - // scanNum was populated from numScansPerSlice_ which is the scanner SPIM_NUM_SCANSPERSLICE("SPIMNumScansPerSlice") - // labeled "Lines scans per slice:" in advanced timing tab - // * gets the correct value for the slice timing's sliceDuration field based on other values of slice timing - - // slice duration is the max out of the scan time, laser time, and camera time - public double getSliceDuration(final TimingSettings s, final int scanNum) { - return Math.max(Math.max( - s.delayBeforeScan() + (s.scanDuration() * scanNum), // scan time - s.delayBeforeLaser() + s.laserTriggerDuration() // laser time - ), - s.delayBeforeCamera() + s.cameraTriggerDuration() // camera time - ); - } - /** * Compute appropriate acceleration time in ms for the specified motor speed. * Set to be 10ms + 0-100ms depending on relative speed to max, all scaled by factor specified on the settings panel @@ -487,7 +469,8 @@ public boolean prepareControllerForAcquisitionSide( //final double slope2 = settings.sliceCalibration(2).sliceSlope(); double sliceRate = settings.sliceCalibration().slope();//(view == 1) ? slope1 : slope2; if (NumberUtils.doublesEqual(sliceRate, 0.0)) { - studio_.logs().showError("Calibration slope for view " + view + " cannot be zero. Re-do calibration on Setup tab."); + studio_.logs().showError("The \"Galvo constant\" is not set, it must not be 0.\n" + + "Please update the value on the setup tab."); return false; } //final double offset1 = settings.sliceCalibration(1).sliceOffset() + channelOffset; diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java index 8eda8767..73f049d9 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java @@ -885,17 +885,6 @@ public double getSliceDuration( ); } - private double getSliceDuration(DefaultTimingSettings.Builder tsb) { - DefaultTimingSettings s = tsb.build(); - // slice duration is the max out of the scan time, laser time, and camera time - return Math.max(Math.max( - s.delayBeforeScan() + (s.scanDuration() * s.scansPerSlice()), // scan time - s.delayBeforeLaser() + s.laserTriggerDuration() // laser time - ), - s.delayBeforeCamera() + s.cameraTriggerDuration() // camera time - ); - } - private double computeTimePointDuration() { final double volumeDuration = computeActualVolumeDuration(acqSettings_); if (acqSettings_.isUsingMultiplePositions()) { 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 7db63fc2..f7a15170 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java @@ -21,6 +21,7 @@ import org.micromanager.lightsheetmanager.api.data.CameraMode; import org.micromanager.lightsheetmanager.api.data.ChannelMode; import org.micromanager.lightsheetmanager.api.internal.DefaultTimingSettings; +import org.micromanager.lightsheetmanager.api.internal.ScapeAcquisitionSettings; import org.micromanager.lightsheetmanager.gui.utils.DialogUtils; import org.micromanager.lightsheetmanager.model.DataStorage; import org.micromanager.lightsheetmanager.LightSheetManager; @@ -36,6 +37,7 @@ import org.micromanager.lightsheetmanager.model.devices.vendor.ASIScanner; import org.micromanager.lightsheetmanager.model.devices.vendor.ASIXYStage; import org.micromanager.lightsheetmanager.model.utils.FileUtils; +import org.micromanager.lightsheetmanager.model.utils.GeometryUtils; import org.micromanager.lightsheetmanager.model.utils.NumberUtils; import javax.swing.JLabel; @@ -754,11 +756,14 @@ boolean finish() { studio_.logs().logError("Couldn't restore shutter to original state"); } - // Check if acquisition ended due to an exception and show error to user if it did - try { - currentAcquisition_.checkForExceptions(); - } catch (Exception e) { - studio_.logs().logError(e); + // check if acquisition ended due to an exception and show error + // currentAcquisition_ can be null if an error occurred during setup + if (currentAcquisition_ != null) { + try { + currentAcquisition_.checkForExceptions(); + } catch (Exception e) { + studio_.logs().logError(e); + } } // TODO: execute any end-acquisition runnables @@ -992,8 +997,7 @@ private boolean doHardwareCalculations(PLogicScape plc) { } double extraChannelOffset = 0.0; - plc.prepareControllerForAcquisition(acqSettings_, extraChannelOffset); - return true; + return plc.prepareControllerForAcquisition(acqSettings_, extraChannelOffset); } private void doHardwareCalculationsNIDAQ() { @@ -1125,7 +1129,7 @@ public DefaultTimingSettings.Builder getTimingFromExposure() { // only true when user has specified period that is unattainable if (globalDelay < 0) { globalDelay = 0; - studio_.logs().showError("Increasing slice period to meet laser exposure constraint\n" + studio_.logs().logDebugMessage("Increasing slice period to meet laser exposure constraint\n" + "(time required for camera readout; readout time depends on ROI)."); } delayBeforeCamera += globalDelay; @@ -1310,7 +1314,7 @@ public DefaultTimingSettings.Builder getTimingFromPeriodAndLightExposure() { // only true when user has specified period that is unattainable if (globalDelay < 0) { globalDelay = 0; - studio_.logs().showError("Increasing slice period to meet laser exposure constraint\n" + studio_.logs().logDebugMessage("Increasing slice period to meet laser exposure constraint\n" + "(time required for camera readout; readout time depends on ROI)."); } delayBeforeCamera += globalDelay; @@ -1432,8 +1436,8 @@ public double computeVolumeDuration() { final double stackDuration = numCameraTriggers * acqSettings_.timing().sliceDuration(); if (acqSettings_.stageScan().enabled()) { - final double rampDuration = 1; //getStageRampDuration(acqSettings); - final double retraceTime = 1; //getStageRetraceDuration(acqSettings); + final double rampDuration = getStageRampDuration(acqSettings_); + final double retraceTime = getStageRetraceDuration(acqSettings_); // TODO(Jon): double-check these calculations below, at least they are better than before ;-) if (acqSettings_.acquisitionMode() == AcquisitionMode.STAGE_SCAN) { if (channelMode == ChannelMode.SLICE_HW) { @@ -1468,4 +1472,42 @@ public double computeVolumeDuration() { } } + private double getStageRampDuration(final ScapeAcquisitionSettings settings) { + final double rampDuration = settings.volume().delayBeforeView() + getScanStageAcceleration(settings); + model_.studio().logs().logDebugMessage("stage ramp duration is " + rampDuration + " milliseconds"); + return rampDuration; + } + + private double getScanStageAcceleration(final ScapeAcquisitionSettings settings) { + // TODO: remove this and find a better way + if (controller_ == null) { + controller_ = new PLogicScape(model_); + } + // extra 1 for rounding up that often happens in controller + return controller_.computeScanAcceleration(controller_.computeScanSpeed(settings), settings) + 1; + } + + private double getStageRetraceDuration(final ScapeAcquisitionSettings settings) { + final ASIXYStage stage = model_.devices().device("SampleXY"); + if (stage == null) { + studio_.logs().showError("could not find XY stage!"); + return 0.0; // early exit => error + } + final double retraceRelativeSpeedPercent; + if (stage.hasProperty(ASIXYStage.Properties.SCAN_RETRACE_SPEED)) { + // this added in firmware v3.30; if not present then we set to firmware default hardcoded previously + retraceRelativeSpeedPercent = stage.getScanRetraceSpeed(); + } else { + retraceRelativeSpeedPercent = 67.0; + } + final double retraceSpeed = retraceRelativeSpeedPercent / 100 * stage.getMaxSpeedX(); + final double speedFactor = GeometryUtils.getStageGeometricSpeedFactor( + settings.stageScan().firstViewAngle(), settings.volume().firstView() == 1); + final double scanDistance = settings.volume().slicesPerView() * settings.volume().sliceStepSize() * speedFactor; + final double accelerationX = getScanStageAcceleration(settings); + final double retraceDuration = scanDistance / retraceSpeed + accelerationX * 2; + studio_.logs().logDebugMessage("stage retrace duration is " + retraceDuration + " milliseconds"); + return retraceDuration; + } + } diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/vendor/ASIXYStage.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/vendor/ASIXYStage.java index f5fe38ed..635d2fa5 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/vendor/ASIXYStage.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/vendor/ASIXYStage.java @@ -166,6 +166,11 @@ public double getScanSettlingTime() { return getPropertyFloat(Properties.SCAN_SETTLING_TIME); } + // TODO: should this be an int? + public double getScanRetraceSpeed() { + return getPropertyFloat(Properties.SCAN_RETRACE_SPEED); + } + public static class Properties { public static final String AXIS_POLARITY_X = "AxisPolarityX"; public static final String AXIS_POLARITY_Y = "AxisPolarityY"; @@ -182,7 +187,7 @@ public static class Properties { public static final String SCAN_PATTERN = "ScanPattern"; public static final String SCAN_STATE = "ScanState"; public static final String SCAN_SETTLING_TIME = "ScanSettlingTime(ms)"; - + public static final String SCAN_RETRACE_SPEED = "ScanRetraceSpeedPercent(%)"; public static final String SCAN_FAST_AXIS_START_POSITION = "ScanFastAxisStartPosition(mm)"; public static final String SCAN_FAST_AXIS_STOP_POSITION = "ScanFastAxisStopPosition(mm)"; public static final String SCAN_SLOW_AXIS_START_POSITION = "ScanSlowAxisStartPosition(mm)";