Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,39 @@
import java.util.Arrays;

public enum GeometryType {
UNKNOWN("Unknown"),
DISPIM("diSPIM"),
ISPIM("iSPIM"),
OSPIM("oSPIM"),
MESOSPIM("mesoSPIM"),
SCAPE("SCAPE");
UNKNOWN("Unknown", 45.0),
DISPIM("diSPIM", 45.0),
ISPIM("iSPIM", 45.0),
OSPIM("oSPIM", 60.0),
MESOSPIM("mesoSPIM", 45.0),
SCAPE("SCAPE", 50.0);

private final String label_;
private final double firstViewAngle_;

GeometryType(final String label) {
GeometryType(final String label, final double firstViewAngle) {
label_ = label;
firstViewAngle_ = firstViewAngle;
}

@Override
public String toString() {
return label_;
}

/**
* Returns the nominal angle in degrees between the objective and the stage.
*
* <p>Each geometry is built to a different angle, so this is only a starting point
* for a new install. Every build is aligned by hand, and the angle a user measures
* on their own microscope is the one that belongs in the settings.
*
* @return the angle in degrees
*/
public double defaultFirstViewAngle() {
return firstViewAngle_;
}

public static GeometryType fromString(final String propertyValue) {
if (propertyValue == null || propertyValue.isEmpty()) {
return UNKNOWN;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void createUserInterface() {

spnScanFirstViewAngle_ = Spinner.createDoubleSpinner(
settings.stageScan().firstViewAngle(),
1.0, 89.0, 1.0);
1.0, 89.0, 0.1);

// Scan CheckBoxes
cbxScanFromCurrentPosition_ = new CheckBox("Scan from current position instead of center",
Expand Down Expand Up @@ -133,13 +133,20 @@ private void createUserInterface() {
pnlScanSettings.add(spnScanOvershootDist_, "wrap");
pnlScanSettings.add(lblScanRetraceSpeed, "");
pnlScanSettings.add(spnScanRetraceSpeed_, "wrap");
pnlScanSettings.add(lblScanAngleFirstView, "");
pnlScanSettings.add(spnScanFirstViewAngle_, "wrap");
}

// the grid slice step size is derived from the angle as well, so it stays
// available even when the stage cannot scan
pnlScanSettings.add(lblScanAngleFirstView, "");
pnlScanSettings.add(spnScanFirstViewAngle_, "wrap");

if (isUsingScanSettings_) {
pnlScanSettings.add(cbxScanFromCurrentPosition_, "span 2, wrap");
pnlScanSettings.add(cbxScanNegativeDirection_, "span 2, wrap");
pnlScanSettings.add(cbxReturnToStart_, "span 2, wrap");
} else {
pnlScanSettings.add(new JLabel("Stage scanning not supported by your firmware."), "");
pnlScanSettings.add(new JLabel("Stage scanning not supported by your firmware."),
"span 2");
}

// light sheet scanner settings panel
Expand Down Expand Up @@ -183,9 +190,6 @@ private void createEventHandlers() {
spnScanRetraceSpeed_.registerListener(
() -> model_.acquisitions().settingsBuilder().stageScanBuilder()
.retraceSpeed(spnScanRetraceSpeed_.getDouble()));
spnScanFirstViewAngle_.registerListener(
() -> model_.acquisitions().settingsBuilder().stageScanBuilder()
.firstViewAngle(spnScanFirstViewAngle_.getDouble()));

cbxScanFromCurrentPosition_.registerListener(
() -> model_.acquisitions().settingsBuilder().stageScanBuilder()
Expand All @@ -198,6 +202,11 @@ private void createEventHandlers() {
.returnToStart(cbxReturnToStart_.isSelected()));
}

// registered outside the block above because the angle spinner is always shown
spnScanFirstViewAngle_.registerListener(
() -> model_.acquisitions().settingsBuilder().stageScanBuilder()
.firstViewAngle(spnScanFirstViewAngle_.getDouble()));

// ASIScanner Filter Freq
if (isUsingPLogic_) {
final ASIScanner scanner = model_.devices().device("IllumSlice");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,7 @@ public abstract class AcquisitionEngine implements AcquisitionManager, MMAcquist
* Validates that the acquisition can actually be written to disk, before anything is acquired.
* <p>
* The images are written by {@code finish()}, i.e. only AFTER the run completes, so without this
* check an unusable save location is discovered at the very end and the data is lost. Observed
* 2026-07-27: a 41 s dual-camera run ended in "could not save the acquisition data to:
* D:\SCOPE\test\Test" because {@code D:\SCOPE\test} did not exist. The same path also silently
* drops {@code acq_settings.json} and {@code position_list.pos}, which are written up front.
* check an unusable save location is discovered at the very end and the data is lost.
* <p>
* Called from both geometry engines' {@code setup()} before any hardware is touched, so a failure
* costs nothing and leaves the microscope untouched.
Expand Down Expand Up @@ -125,8 +122,6 @@ protected boolean validateSaveLocation() {
* <p>Cameras that disagree overrun the shared Core circular buffer and take the whole JVM with
* them: {@code EXCEPTION_ACCESS_VIOLATION} inside {@code popNextImageMD}, no Java exception, no
* recovery, no data. Refusing to arm is the only place this can be stopped from inside LSM.
* Observed in the field 2026-07-28 on a dual-Kinetix rig where a partly-applied ROI left one
* camera at 1200x1200 and the other at 600x600.
*
* <p>Called from both geometry engines' {@code setup()} before any hardware is touched, so a
* failure costs nothing and leaves the microscope untouched.
Expand Down Expand Up @@ -223,6 +218,10 @@ public AcquisitionEngine(final LightSheetManager model) {

// default settings
asb_ = ScapeAcquisitionSettings.builder();
// seeded from the geometry because the angle has no single sensible default;
// a saved profile replaces it later when UserSettings loads
asb_.stageScanBuilder().firstViewAngle(
model.devices().adapter().geometry().defaultFirstViewAngle());
acqSettings_ = asb_.build();
}

Expand Down
Loading