From 536ccfd29e7cc95f54ebd47861282e9d424cbb2b Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 4 Aug 2026 23:30:56 -0700 Subject: [PATCH 1/4] explain the quarter millisecond timing quantization --- .../micromanager/lightsheetmanager/model/utils/NumberUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/utils/NumberUtils.java b/src/main/java/org/micromanager/lightsheetmanager/model/utils/NumberUtils.java index 5c7533b1..02bf1493 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/utils/NumberUtils.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/utils/NumberUtils.java @@ -48,6 +48,7 @@ public static double roundToPlace(final double value, final int place) { /** * Return the double value rounded up to the nearest increment of 0.25. *

Example: 0.0 goes to 0.0 but 0.01 goes to 0.25 + *

Quarter milliseconds are the timing quantum because the PLogic evaluates its cells at 4kHz. * * @param value the value to round up * @return the value rounded up @@ -58,6 +59,7 @@ public static double ceilToQuarterMs(final double value) { /** * Return the double value rounded to the nearest increment of 0.25. + *

Quarter milliseconds are the timing quantum because the PLogic evaluates its cells at 4kHz. * * @param value the value to round * @return the rounded value From 886bb53ab96cacee86c4fde7043d6f01f3c2a3b8 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 5 Aug 2026 02:33:26 -0700 Subject: [PATCH 2/4] match channels in dispim to scape --- .../acquisitions/AcquisitionEngineDispim.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 534528c1..43f3adae 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java @@ -633,7 +633,7 @@ private boolean doHardwareCalculations(PLogicDispim plc) { boolean changeChannelPerVolumeSoftware = false; boolean changeChannelPerVolumeDoneFirst = false; if (acqSettings_.channels().enabled()) { - if (acqSettings_.channels().count() > 1) { + if (acqSettings_.channels().count() == 0) { studio_.logs().showError("\"Channels\" is checked, but no channels are selected"); return false; // early exit } @@ -650,14 +650,14 @@ private boolean doHardwareCalculations(PLogicDispim plc) { // we have at least 2 channels // intentionally leave extraChannelOffset_ untouched so that it can be specified by user by choosing a preset // for the channel in the main Micro-Manager window + final boolean success = plc.setupHardwareChannelSwitching(acqSettings_); + if (!success) { + studio_.logs().showError("Couldn't set up slice hardware channel switching."); + return false; // early exit + } + nrChannelsSoftware = 1; + nrSlicesSoftware = acqSettings_.volume().slicesPerView() * acqSettings_.channels().count(); } - final boolean success = plc.setupHardwareChannelSwitching(acqSettings_); - if (!success) { - studio_.logs().showError("Couldn't set up slice hardware channel switching."); - return false; // early exit - } - nrChannelsSoftware = 1; - nrSlicesSoftware = acqSettings_.volume().slicesPerView() * acqSettings_.channels().count(); break; default: studio_.logs().showError( From 02443ec38da393a78861f976c275505655985b05 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 5 Aug 2026 07:32:48 -0700 Subject: [PATCH 3/4] remove redundant interleaved stage scan guard from scape engine --- .../model/acquisitions/AcquisitionEngineScape.java | 7 ------- 1 file changed, 7 deletions(-) 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 693d6c01..998e6845 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java @@ -174,13 +174,6 @@ boolean run() { studio_.logs().showError("Must have stage with scan-enabled firmware for stage scanning."); return false; } - if (acqSettings_.acquisitionMode() == AcquisitionMode.STAGE_SCAN_INTERLEAVED) { - if (acqSettings_.volume().numViews() < 2) { - studio_.logs().showError("Interleaved stage scan requires two sides."); - } - return false; - } - // second part: initialize stage scanning, so we can restore state later xyPosUm_ = xyStage.getXYPosition(); origSpeedX_ = xyStage.getSpeedX(); From 1e18bae5b840fd5dc3f55d22b3f47326cca7083b Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 5 Aug 2026 09:13:02 -0700 Subject: [PATCH 4/4] guarantee camera trigger mode is recorded with a template method --- .../acquisitions/AcquisitionEngineDispim.java | 2 +- .../acquisitions/AcquisitionEngineScape.java | 2 +- .../model/devices/cameras/AndorCamera.java | 3 +- .../model/devices/cameras/CameraBase.java | 33 ++++++++++++++++--- .../model/devices/cameras/DemoCamera.java | 2 +- .../devices/cameras/HamamatsuCamera.java | 3 +- .../model/devices/cameras/PcoCamera.java | 3 +- .../model/devices/cameras/PvCamera.java | 7 +--- .../model/devices/cameras/UnknownCamera.java | 5 +++ 9 files changed, 41 insertions(+), 19 deletions(-) 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 43f3adae..14b4ed50 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineDispim.java @@ -762,7 +762,7 @@ public void recalculateSliceTiming() { CameraBase camera = model_.devices().device("ImagingCamera"); camera.setTriggerMode(acqSettings_.cameraMode()); studio_.logs().logDebugMessage( - "camera \"" + camera.getDeviceName() + "\" set to mode: " + camera.getTriggerMode()); + "camera \"" + camera.getDeviceName() + "\" requested mode: " + camera.getTriggerMode()); } return; } 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 998e6845..214986e5 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java @@ -866,7 +866,7 @@ private boolean doHardwareCalculations(PLogicScape plc) { for (CameraBase camera : cameras) { camera.setTriggerMode(acqSettings_.cameraMode()); studio_.logs().logMessage("camera \"" + camera.getDeviceName() - + "\" set to mode: " + camera.getTriggerMode()); + + "\" requested mode: " + camera.getTriggerMode()); } // make sure slice timings are up-to-date diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/AndorCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/AndorCamera.java index 4aaa6f32..6b77bf5e 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/AndorCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/AndorCamera.java @@ -50,8 +50,7 @@ public AndorCamera(Studio studio, String deviceName) { } @Override - public void setTriggerMode(CameraMode cameraMode) { - mode_ = cameraMode; + protected void applyTriggerMode(CameraMode cameraMode) { // work-around a bug in SDK3 device adapter, can't switch from light sheet mode // to "normal" center out simultaneous but works if we always go through the in-between mode if (hasProperty(Properties.SENSOR_READOUT_MODE)) { diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/CameraBase.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/CameraBase.java index 5df1c6e3..936e2642 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/CameraBase.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/CameraBase.java @@ -11,8 +11,14 @@ * This is the base camera class. * *

Methods that need per-vendor knowledge are abstract on purpose: a camera class that - * forgets one fails to compile. Cameras whose device library resolves to - * {@code CameraLibrary.UNKNOWN} use {@link UnknownCamera}. + * forgets one fails to compile. + * + *

Trigger mode is the exception: implement {@link #applyTriggerMode}, which only has to write + * the mode to the device. {@link #setTriggerMode} is final and records it here for + * {@link #getTriggerMode}, so no vendor has to maintain it. + * + *

Cameras whose device library resolves to {@code CameraLibrary.UNKNOWN} use + * {@link UnknownCamera}. */ public abstract class CameraBase extends DeviceBase implements LightSheetCamera { @@ -239,13 +245,32 @@ public int roiReadoutRowsSplitReadout(Rectangle roi, Rectangle sensor) { // needed for subclasses + /** + * Sets the camera trigger mode and records it. + * + *

Final because the recorded mode is base state, which a vendor override would have to + * remember to maintain. Vendors implement {@link #applyTriggerMode} instead. + */ @Override - public void setTriggerMode(CameraMode cameraMode) { + public final void setTriggerMode(final CameraMode cameraMode) { + applyTriggerMode(cameraMode); mode_ = cameraMode; } + /** + * Writes the trigger mode to the device. + */ + protected abstract void applyTriggerMode(final CameraMode cameraMode); + + /** + * Returns the last mode requested through {@link #setTriggerMode}. + * + *

This is not a device readback because the vendor mapping from camera mode to device + * properties is not always reversible, so the requested mode is the only answer every camera + * can give. + */ @Override - public CameraMode getTriggerMode() { + public final CameraMode getTriggerMode() { return mode_; } diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/DemoCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/DemoCamera.java index ac899b16..e54955e4 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/DemoCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/DemoCamera.java @@ -25,7 +25,7 @@ public DemoCamera(Studio studio, String deviceName) { } @Override - public void setTriggerMode(final CameraMode cameraMode) { + protected void applyTriggerMode(final CameraMode cameraMode) { // do nothing - no camera trigger modes - always internal - log for debug convenience studio_.logs().logMessage( "setTriggerMode(" + cameraMode + ") called but the DemoCamera is always in internal mode."); diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/HamamatsuCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/HamamatsuCamera.java index f8004f84..fae55722 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/HamamatsuCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/HamamatsuCamera.java @@ -55,8 +55,7 @@ public HamamatsuCamera(final Studio studio, final String deviceName) { } @Override - public void setTriggerMode(final CameraMode cameraMode) { - mode_ = cameraMode; + protected void applyTriggerMode(final CameraMode cameraMode) { setProperty(Properties.TRIGGER_SOURCE, (cameraMode == CameraMode.INTERNAL) ? Values.INTERNAL : Values.EXTERNAL); setProperty(Properties.SENSOR_MODE, (cameraMode == CameraMode.VIRTUAL_SLIT) ? Values.PROGRESSIVE : Values.AREA); switch (cameraMode) { diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PcoCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PcoCamera.java index e055736d..39fb4e8a 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PcoCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PcoCamera.java @@ -38,8 +38,7 @@ public PcoCamera(Studio studio, String deviceName) { } @Override - public void setTriggerMode(CameraMode cameraMode) { - mode_ = cameraMode; + protected void applyTriggerMode(CameraMode cameraMode) { switch (cameraMode) { case EDGE: case PSEUDO_OVERLAP: diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PvCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PvCamera.java index a5d9623c..8daa2a08 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PvCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/PvCamera.java @@ -43,7 +43,7 @@ public PvCamera(final Studio studio, final String deviceName) { } @Override - public void setTriggerMode(final CameraMode cameraMode) { + protected void applyTriggerMode(final CameraMode cameraMode) { switch (cameraMode) { case EDGE: case PSEUDO_OVERLAP: @@ -58,11 +58,6 @@ public void setTriggerMode(final CameraMode cameraMode) { } } - @Override - public CameraMode getTriggerMode() { - return CameraMode.fromString(getProperty(Properties.TRIGGER_MODE)); - } - @Override public void setBinning() { diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/UnknownCamera.java b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/UnknownCamera.java index 90c32f8b..27aef8e4 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/UnknownCamera.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/devices/cameras/UnknownCamera.java @@ -26,6 +26,11 @@ private UnsupportedOperationException unsupported(final String what) { + " is unknown. Supported: AndorSDK3, HamamatsuHam, PCO_Camera, PVCAM, DemoCamera."); } + @Override + protected void applyTriggerMode(final CameraMode cameraMode) { + throw unsupported("trigger mode"); + } + @Override public void setBinning() { throw unsupported("binning");