From 9e75c1b46e676bac78c382497278bf9bea7ff938 Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 16 Feb 2026 15:54:47 -0800 Subject: [PATCH] use a single array of objects for camera order --- .../LightSheetManagerPlugin.java | 2 +- .../api/AcquisitionSettingsSCAPE.java | 19 +--- .../api/data/CameraData.java | 38 ++++++++ .../DefaultAcquisitionSettingsSCAPE.java | 34 ++----- .../lightsheetmanager/gui/tabs/CameraTab.java | 90 ++++++++++++------- .../model/DeviceManager.java | 20 ++--- .../acquisitions/AcquisitionEngineSCAPE.java | 16 ++-- 7 files changed, 120 insertions(+), 99 deletions(-) create mode 100644 src/main/java/org/micromanager/lightsheetmanager/api/data/CameraData.java diff --git a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java index fa42694..75163f8 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java +++ b/src/main/java/org/micromanager/lightsheetmanager/LightSheetManagerPlugin.java @@ -12,7 +12,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.5.7"; + public static final String version = "0.5.8"; private Studio studio_; private LightSheetManager model_; diff --git a/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionSettingsSCAPE.java b/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionSettingsSCAPE.java index 281c3a3..058bacb 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionSettingsSCAPE.java +++ b/src/main/java/org/micromanager/lightsheetmanager/api/AcquisitionSettingsSCAPE.java @@ -1,6 +1,7 @@ package org.micromanager.lightsheetmanager.api; import org.micromanager.lightsheetmanager.api.data.AcquisitionMode; +import org.micromanager.lightsheetmanager.api.data.CameraData; import org.micromanager.lightsheetmanager.api.data.CameraMode; import org.micromanager.lightsheetmanager.api.internal.DefaultChannelSettings; import org.micromanager.lightsheetmanager.api.internal.DefaultScanSettings; @@ -37,14 +38,7 @@ interface Builder> extends AcquisitionS * * @param cameraOrder the imaging camera order */ - T imagingCameraOrder(final String[] cameraOrder); - - /** - * Sets the active imaging cameras. - * - * @param camerasActive the active imaging cameras - */ - T imagingCamerasActive(final boolean[] camerasActive); + T imagingCameraOrder(final CameraData[] cameraOrder); /** * Sets the acquisition to acquire from multiple simultaneous cameras. @@ -213,14 +207,7 @@ interface Builder> extends AcquisitionS * * @return the imaging camera order */ - String[] imagingCameraOrder(); - - /** - * Returns an array of active imaging cameras. - * - * @return an array of active imaging cameras - */ - boolean[] imagingCamerasActive(); + CameraData[] imagingCameraOrder(); /** * Returns true if acquiring from both imaging cameras. diff --git a/src/main/java/org/micromanager/lightsheetmanager/api/data/CameraData.java b/src/main/java/org/micromanager/lightsheetmanager/api/data/CameraData.java new file mode 100644 index 0000000..9f7abe0 --- /dev/null +++ b/src/main/java/org/micromanager/lightsheetmanager/api/data/CameraData.java @@ -0,0 +1,38 @@ +package org.micromanager.lightsheetmanager.api.data; + +// Used to track imaging camera order for simultaneous imaging cameras. +public class CameraData { + + private String name_; + private boolean isActive_; + + public CameraData(final String name, final boolean isActive) { + name_ = name; + isActive_ = isActive; + } + + public String name() { + return name_; + } + + public void name(final String name) { + name_ = name; + } + + public boolean isActive() { + return isActive_; + } + + public void isActive(final boolean isActive) { + isActive_ = isActive; + } + + public static boolean isCameraActive(final CameraData[] cameras, final String cameraName) { + for (CameraData camera : cameras) { + if (camera.name().equals(cameraName)) { + return camera.isActive(); + } + } + return false; + } +} diff --git a/src/main/java/org/micromanager/lightsheetmanager/api/internal/DefaultAcquisitionSettingsSCAPE.java b/src/main/java/org/micromanager/lightsheetmanager/api/internal/DefaultAcquisitionSettingsSCAPE.java index 4b09163..e6823bb 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/api/internal/DefaultAcquisitionSettingsSCAPE.java +++ b/src/main/java/org/micromanager/lightsheetmanager/api/internal/DefaultAcquisitionSettingsSCAPE.java @@ -2,6 +2,7 @@ import org.micromanager.lightsheetmanager.api.AcquisitionSettingsSCAPE; import org.micromanager.lightsheetmanager.api.data.AcquisitionMode; +import org.micromanager.lightsheetmanager.api.data.CameraData; import org.micromanager.lightsheetmanager.api.data.CameraMode; public class DefaultAcquisitionSettingsSCAPE extends DefaultAcquisitionSettings implements AcquisitionSettingsSCAPE { @@ -19,8 +20,7 @@ public static class Builder extends DefaultAcquisitionSettings.Builder private AcquisitionMode acquisitionMode_ = AcquisitionMode.NO_SCAN; private CameraMode cameraMode_ = CameraMode.EDGE; - private String[] imagingCameraOrder_ = {}; - private boolean[] imagingCamerasActive_ = {}; + private CameraData[] imagingCameraOrder_ = {}; private boolean useSimultaneousCameras_ = true; private boolean useChannels_ = false; @@ -58,7 +58,6 @@ public Builder(final DefaultAcquisitionSettingsSCAPE acqSettings) { acquisitionMode_ = acqSettings.acquisitionMode_; cameraMode_ = acqSettings.cameraMode_; imagingCameraOrder_ = acqSettings.imagingCameraOrder_; - imagingCamerasActive_ = acqSettings.imagingCamerasActive_; useSimultaneousCameras_ = acqSettings.useSimultaneousCameras_; useChannels_ = acqSettings.useChannels_; useTimePoints_ = acqSettings.useTimePoints_; @@ -107,22 +106,11 @@ public Builder cameraMode(final CameraMode cameraMode) { * @param cameraOrder the imaging camera order */ @Override - public Builder imagingCameraOrder(final String[] cameraOrder) { + public Builder imagingCameraOrder(final CameraData[] cameraOrder) { imagingCameraOrder_ = cameraOrder; return this; } - /** - * Sets the active imaging cameras. - * - * @param camerasActive the active imaging cameras - */ - @Override - public Builder imagingCamerasActive(final boolean[] camerasActive) { - imagingCamerasActive_ = camerasActive; - return this; - } - /** * Sets the acquisition to acquire from multiple simultaneous cameras. * @@ -322,8 +310,7 @@ public String toString() { private final AcquisitionMode acquisitionMode_; private final CameraMode cameraMode_; - private final String[] imagingCameraOrder_; - private final boolean[] imagingCamerasActive_; + private final CameraData[] imagingCameraOrder_; private final boolean useSimultaneousCameras_; private final boolean useChannels_; @@ -356,7 +343,6 @@ private DefaultAcquisitionSettingsSCAPE(Builder builder) { acquisitionMode_ = builder.acquisitionMode_; cameraMode_ = builder.cameraMode_; imagingCameraOrder_ = builder.imagingCameraOrder_; - imagingCamerasActive_ = builder.imagingCamerasActive_; useSimultaneousCameras_ = builder.useSimultaneousCameras_; useChannels_ = builder.useChannels_; useTimePoints_ = builder.useTimePoints_; @@ -506,20 +492,10 @@ public CameraMode cameraMode() { * @return the imaging camera order */ @Override - public String[] imagingCameraOrder() { + public CameraData[] imagingCameraOrder() { return imagingCameraOrder_; } - /** - * Returns an array of active imaging cameras. - * - * @return an array of active imaging cameras - */ - @Override - public boolean[] imagingCamerasActive() { - return imagingCamerasActive_; - } - /** * Returns true if acquiring from all active imaging cameras on a single view. * diff --git a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/CameraTab.java b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/CameraTab.java index 8451f8f..b1c5dfc 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/CameraTab.java +++ b/src/main/java/org/micromanager/lightsheetmanager/gui/tabs/CameraTab.java @@ -1,5 +1,6 @@ package org.micromanager.lightsheetmanager.gui.tabs; +import org.micromanager.lightsheetmanager.api.data.CameraData; import org.micromanager.lightsheetmanager.api.data.CameraLibrary; import org.micromanager.lightsheetmanager.api.data.CameraMode; import org.micromanager.lightsheetmanager.gui.components.Button; @@ -31,7 +32,7 @@ public class CameraTab extends Panel implements ListeningPanel { private ComboBox cmbCameraTriggerMode_; private RadioButton radPrimaryCamera_; private CheckBox cbxUseSimultaneousCameras_; - private List cbxActiveCameras_; + private List cbxCameras_; private final TabPanel tabPanel_; private final LightSheetManager model_; @@ -63,22 +64,30 @@ private void createUserInterface() { btnCustomROI_ = new Button("Custom", 140, 30); btnGetCurrentROI_ = new Button("Get Current ROI", 140, 30); + // TODO: use optional here for camera? + // get the imaging camera library + String[] modes = {""}; final CameraBase camera = model_.devices().firstImagingCamera(); - final CameraLibrary camLib = CameraLibrary.fromString(camera.getDeviceLibrary()); + if (camera != null) { + final CameraLibrary camLib = CameraLibrary.fromString(camera.getDeviceLibrary()); + modes = CameraMode.getAvailableModes(camLib); + } - cmbCameraTriggerMode_ = new ComboBox(CameraMode.getAvailableModes(camLib), + cmbCameraTriggerMode_ = new ComboBox(modes, model_.acquisitions().settings().cameraMode().toString()); // validate that the logical device name exists final String[] cameraNames = model_.devices().imagingCameraNames(); - cbxActiveCameras_ = new ArrayList<>(cameraNames.length); + cbxCameras_ = new ArrayList<>(cameraNames.length); // the first element of the list is the primary camera String primaryCamera = ""; - final String[] cameraOrder = model_.acquisitions().settings().imagingCameraOrder(); + final CameraData[] cameraOrder = model_.acquisitions().settings().imagingCameraOrder(); if (cameraOrder.length > 0) { - primaryCamera = cameraOrder[0]; + primaryCamera = cameraOrder[0].name(); + } else { + primaryCamera = cameraNames[0]; // default to first camera name } // simultaneous camera settings @@ -94,11 +103,12 @@ private void createUserInterface() { "[]6[]" ); - final boolean[] activeCameras = model_.acquisitions().settings().imagingCamerasActive(); - for (int i = 0; i < cameraNames.length; i++) { - final CheckBox checkBox = new CheckBox("Active", activeCameras[i]); + // we already have the camera order array + for (String cameraName : cameraNames) { + final boolean isActive = CameraData.isCameraActive(cameraOrder, cameraName); + final CheckBox checkBox = new CheckBox("Active", isActive); pnlCheckboxes.add(checkBox, "wrap"); - cbxActiveCameras_.add(checkBox); + cbxCameras_.add(checkBox); } final Panel pnlCameraSelectionRow = new Panel(); @@ -142,30 +152,12 @@ private void createEventHandlers() { tabPanel_.swapSetupPathPanels(cameraMode); }); - // TODO(Brandon): uses simple ordering that works for 2 cameras, - // but needs additional work to support 4. Use a reorderable JTable? - // select primary camera - radPrimaryCamera_.registerListener(e -> { - final String selected = radPrimaryCamera_.getSelectedButtonText(); - final String[] cameraNames = model_.devices().imagingCameraNames(); - final List cameraOrder = new ArrayList<>(); - // the first array index is the primary camera - cameraOrder.add(selected); - for (String cameraName : cameraNames) { - if (!selected.equals(cameraName)) { - cameraOrder.add(cameraName); - } - } - model_.acquisitions().settingsBuilder() - .imagingCameraOrder(cameraOrder.toArray(String[]::new)); - }); + radPrimaryCamera_.registerListener(e -> computeCameraOrder()); // active camera check boxes - for (CheckBox cbx : cbxActiveCameras_) { - cbx.registerListener(e -> - model_.acquisitions().settingsBuilder() - .imagingCamerasActive(activeCameras())); + for (CheckBox cbx : cbxCameras_) { + cbx.registerListener(e -> computeCameraOrder()); } // use all active cameras @@ -177,13 +169,43 @@ private void createEventHandlers() { } private boolean[] activeCameras() { - boolean[] active = new boolean[cbxActiveCameras_.size()]; - for (int i = 0; i < cbxActiveCameras_.size(); i++) { - active[i] = cbxActiveCameras_.get(i).isSelected(); + boolean[] active = new boolean[cbxCameras_.size()]; + for (int i = 0; i < cbxCameras_.size(); i++) { + active[i] = cbxCameras_.get(i).isSelected(); } return active; } + private void computeCameraOrder() { + // change the order of the imaging camera array + final String selected = radPrimaryCamera_.getSelectedButtonText(); + final String[] cameraNames = model_.devices().imagingCameraNames(); + final ArrayList cameraData = new ArrayList<>(cameraNames.length); + + // check if the selected primary camera is active before changing the array order + boolean isSelectedActive = true; + final boolean[] active = activeCameras(); + for (int i = 0; i < cameraNames.length; i++) { + if (cameraNames[i].equals(selected)) { + isSelectedActive = active[i]; + break; + } + } + + // the first array index is the primary camera + cameraData.add(new CameraData(selected, isSelectedActive)); + // TODO(Brandon): uses simple ordering that works for 2 cameras, + // but needs additional work to support 4. Use a reorderable JTable? + for (int i = 0; i < cameraNames.length; i++) { + if (!selected.equals(cameraNames[i])) { + System.out.println("cameraNames[i] " + cameraNames[i] + "active[i] " + active[i]); + cameraData.add(new CameraData(cameraNames[i], active[i])); + } + } + model_.acquisitions().settingsBuilder() + .imagingCameraOrder(cameraData.toArray(CameraData[]::new)); + } + @Override public void selected() { diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/DeviceManager.java b/src/main/java/org/micromanager/lightsheetmanager/model/DeviceManager.java index cb26a54..5931e71 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/DeviceManager.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/DeviceManager.java @@ -3,6 +3,7 @@ import mmcorej.Configuration; import mmcorej.StrVector; import org.micromanager.lightsheetmanager.LightSheetManager; +import org.micromanager.lightsheetmanager.api.data.CameraData; import org.micromanager.lightsheetmanager.api.data.CameraLibrary; import mmcorej.CMMCore; import mmcorej.DeviceType; @@ -297,14 +298,12 @@ public CameraBase firstImagingCamera() { return (CameraBase) deviceMap_.get(deviceKey); } - // TODO: active needs to be synchronized with the order, since order changes but active does not // For simultaneous cameras public String firstActiveCameraName() { - final String[] cameras = model_.acquisitions().settings().imagingCameraOrder(); - final boolean[] active = model_.acquisitions().settings().imagingCamerasActive(); - for (int i = 0; i < cameras.length; i++) { - if (active[i]) { - return cameras[i]; + final CameraData[] cameras = model_.acquisitions().settings().imagingCameraOrder(); + for (CameraData camera : cameras) { + if (camera.isActive()) { + return camera.name(); } } return ""; @@ -348,11 +347,10 @@ public CameraBase[] imagingCameras() { String[] cameraNames; if (model_.acquisitions().settings().isUsingSimultaneousCameras()) { ArrayList names = new ArrayList<>(); - final String[] cameras = model_.acquisitions().settings().imagingCameraOrder(); - final boolean[] active = model_.acquisitions().settings().imagingCamerasActive(); - for (int i = 0; i < cameras.length; i++) { - if (active[i]) { - names.add(cameras[i]); + final CameraData[] cameras = model_.acquisitions().settings().imagingCameraOrder(); + for (CameraData camera : cameras) { + if (camera.isActive()) { + names.add(camera.name()); } } cameraNames = names.toArray(String[]::new); 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 1ec0ccf..42bdfa4 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineSCAPE.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineSCAPE.java @@ -86,14 +86,14 @@ boolean setup() { // } // we must have an active camera if we are using simultaneous cameras - if (model_.acquisitions().settings().isUsingSimultaneousCameras()) { - final boolean[] active = model_.acquisitions().settings().imagingCamerasActive(); - if (IntStream.range(0, active.length).noneMatch(i -> active[i])) { - studio_.logs().showError("Using simultaneous cameras and no cameras are active!"); - return false; - } - // TODO: primary camera must be active - } +// if (model_.acquisitions().settings().isUsingSimultaneousCameras()) { +// final boolean[] active = model_.acquisitions().settings().imagingCamerasActive(); +// if (IntStream.range(0, active.length).noneMatch(i -> active[i])) { +// studio_.logs().showError("Using simultaneous cameras and no cameras are active!"); +// return false; +// } +// // TODO: primary camera must be active +// } // this is needed for LSMAcquisitionEvents to work with multiple positions if (core_.getFocusDevice().isEmpty()