From d80d88c833d7951409b604b6bc34c5f2be8bcfe2 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 29 Jul 2026 17:21:13 -0700 Subject: [PATCH] add message about binning to camera roi mismatch error --- .../model/acquisitions/AcquisitionEngine.java | 2 +- .../model/devices/cameras/CameraBase.java | 47 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java index 8aee9fd..03425fa 100644 --- a/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java +++ b/src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java @@ -139,7 +139,7 @@ protected boolean validateCameraFrameSizes() { return true; } studio_.logs().showError("The imaging cameras have different frame sizes: " + mismatch - + "\n\nAcquiring with mismatched frame sizes crashes Micro-Manager outright, so this " + + ".\n\nAcquiring with mismatched frame sizes crashes Micro-Manager outright, so this " + "acquisition was not started.\n\nSet the same ROI and binning on every imaging " + "camera from the Camera tab, then try again."); return false; 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 21e7731..d1fc4c04 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 @@ -109,29 +109,72 @@ public boolean setROI(final Rectangle roi) { * {@link #getROI()} returns an empty rectangle when the read itself fails. Two unreadable * cameras would otherwise look like two matching ones and pass. * + *

Each camera's binning is reported alongside its frame size, and unequal binning is called + * out explicitly, because it is the usual cause and the one the ROI buttons cannot fix: the + * presets give every camera the largest ROI valid for its own binning, which is the same + * physical field of view but a different pixel count. No ROI makes two cameras match while + * their binning differs, so a user who just pressed a preset needs to be told to look at + * binning instead of pressing another one. + * * @param cameras the cameras that will image together - * @return a description of the disagreement, or null if every camera reports the same frame size + * @return a description of the disagreement with no trailing punctuation, so callers can embed + * it in a sentence, or null if every camera reports the same frame size */ public static String describeFrameSizeMismatch(final CameraBase[] cameras) { if (cameras == null || cameras.length < 2) { return null; // a single camera cannot disagree with itself } final Rectangle first = cameras[0].getROI(); + final int firstBinning = binningOrUnknown(cameras[0]); boolean disagree = false; + boolean binningDiffers = false; final StringBuilder sizes = new StringBuilder(); for (final CameraBase camera : cameras) { final Rectangle roi = camera.getROI(); + final int binning = binningOrUnknown(camera); if (roi.width <= 0 || roi.height <= 0 || roi.width != first.width || roi.height != first.height) { disagree = true; } + if (binning != UNKNOWN_BINNING && firstBinning != UNKNOWN_BINNING + && binning != firstBinning) { + binningDiffers = true; + } if (sizes.length() > 0) { sizes.append(", "); } sizes.append(camera.getDeviceName()) .append(" = ").append(roi.width).append("x").append(roi.height); + if (binning != UNKNOWN_BINNING) { + sizes.append(" at binning ").append(binning); + } + } + if (!disagree) { + return null; + } + if (binningDiffers) { + sizes.append(" (the binning differs, which is the usual cause; no ROI will make them " + + "match until every camera uses the same binning)"); + } + return sizes.toString(); + } + + private static final int UNKNOWN_BINNING = -1; + + /** + * Reads a camera's binning for reporting, or returns {@link #UNKNOWN_BINNING} if it cannot. + * + *

Binning is diagnostic here, so a camera that cannot report it must not be able to break the + * frame-size check that protects against a JVM kill. It can throw for ordinary reasons: + * {@code UnknownCamera} throws deliberately, and every vendor's {@code getBinning()} takes + * {@code substring(0, 1)} of a property read that yields {@code ""} when it fails. + */ + private static int binningOrUnknown(final CameraBase camera) { + try { + return camera.getBinning(); + } catch (Exception e) { + return UNKNOWN_BINNING; } - return disagree ? sizes.toString() : null; } public void setROI() {