diff --git a/photon-client/src/components/cameras/CameraCalibrationCard.vue b/photon-client/src/components/cameras/CameraCalibrationCard.vue index f217462640..0594b97de3 100644 --- a/photon-client/src/components/cameras/CameraCalibrationCard.vue +++ b/photon-client/src/components/cameras/CameraCalibrationCard.vue @@ -393,8 +393,8 @@ const setSelectedVideoFormat = (format: VideoFormat) => { :disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure" label="Exposure (μS)" tooltip="Directly controls how long the camera shutter remains open (in microseconds)" - :min="1" - :max="80000" + :min="useCameraSettingsStore().minExposureUs" + :max="useCameraSettingsStore().maxExposureUs" :slider-cols="8" :step="1" @input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureUs: args }, false)" diff --git a/photon-client/src/components/dashboard/tabs/InputTab.vue b/photon-client/src/components/dashboard/tabs/InputTab.vue index 6090ced75c..b0468e5348 100644 --- a/photon-client/src/components/dashboard/tabs/InputTab.vue +++ b/photon-client/src/components/dashboard/tabs/InputTab.vue @@ -78,8 +78,8 @@ const interactiveCols = computed(() => :disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure" label="Exposure (μS)" tooltip="Directly controls how long the camera shutter remains open (in microseconds)" - :min="1" - :max="80000" + :min="useCameraSettingsStore().minExposureUs" + :max="useCameraSettingsStore().maxExposureUs" :slider-cols="interactiveCols" :step="1" @input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureUs: args }, false)" diff --git a/photon-client/src/stores/settings/CameraSettingsStore.ts b/photon-client/src/stores/settings/CameraSettingsStore.ts index e0b362264d..1965b55cf6 100644 --- a/photon-client/src/stores/settings/CameraSettingsStore.ts +++ b/photon-client/src/stores/settings/CameraSettingsStore.ts @@ -68,6 +68,12 @@ export const useCameraSettingsStore = defineStore("cameraSettings", { }, isCSICamera(): boolean { return this.currentCameraSettings.isCSICamera; + }, + minExposureUs(): number { + return this.currentCameraSettings.minExposureUs; + }, + maxExposureUs(): number { + return this.currentCameraSettings.maxExposureUs; } }, actions: { @@ -102,6 +108,8 @@ export const useCameraSettingsStore = defineStore("cameraSettings", { })), completeCalibrations: d.calibrations, isCSICamera: d.isCSICamera, + minExposureUs: d.minExposureUs, + maxExposureUs: d.maxExposureUs, pipelineNicknames: d.pipelineNicknames, currentPipelineIndex: d.currentPipelineIndex, pipelineSettings: d.currentPipelineSettings, diff --git a/photon-client/src/types/PipelineTypes.ts b/photon-client/src/types/PipelineTypes.ts index 51592b53e8..b9e068d021 100644 --- a/photon-client/src/types/PipelineTypes.ts +++ b/photon-client/src/types/PipelineTypes.ts @@ -65,6 +65,8 @@ export interface PipelineSettings { outputShowMultipleTargets: boolean; contourSortMode: number; cameraExposureUs: number; + cameraMinExposureUs: number; + cameraMaxExposureUs: number; offsetSinglePoint: { x: number; y: number }; cameraBrightness: number; offsetDualPointAArea: number; diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts index 87f954ce6c..1ab9fa3f70 100644 --- a/photon-client/src/types/SettingTypes.ts +++ b/photon-client/src/types/SettingTypes.ts @@ -189,6 +189,9 @@ export interface CameraSettings { cameraQuirks: QuirkyCamera; isCSICamera: boolean; + + minExposureUs: number; + maxExposureUs: number; } export interface CameraSettingsChangeRequest { @@ -287,7 +290,9 @@ export const PlaceholderCameraSettings: CameraSettings = { StickyFPS: false } }, - isCSICamera: false + isCSICamera: false, + minExposureUs: 1, + maxExposureUs: 100 }; export enum CalibrationBoardTypes { diff --git a/photon-client/src/types/WebsocketDataTypes.ts b/photon-client/src/types/WebsocketDataTypes.ts index de209e7f9d..ac4b8d95d4 100644 --- a/photon-client/src/types/WebsocketDataTypes.ts +++ b/photon-client/src/types/WebsocketDataTypes.ts @@ -58,6 +58,8 @@ export interface WebsocketCameraSettingsUpdate { pipelineNicknames: string[]; videoFormatList: WebsocketVideoFormat; cameraQuirks: QuirkyCamera; + minExposureUs: number; + maxExposureUs: number; } export interface WebsocketNTUpdate { connected: boolean; diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java index dd1eabe3b7..285ce85464 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java @@ -178,5 +178,7 @@ public static class UICameraConfiguration { public boolean isFovConfigurable = true; public QuirkyCamera cameraQuirks; public boolean isCSICamera; + public double minExposureUs; + public double maxExposureUs; } } diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java b/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java index 5475263e9e..55c86a8e5b 100644 --- a/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java +++ b/photon-core/src/main/java/org/photonvision/vision/camera/FileVisionSource.java @@ -122,5 +122,15 @@ protected void setVideoModeInternal(VideoMode videoMode) { public HashMap getAllVideoModes() { return videoModes; } + + @Override + public double getMinExposureUs() { + return 1f; + } + + @Override + public double getMaxExposureUs() { + return 100f; + } } } diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java b/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java index 05b12021d2..e76611dee2 100644 --- a/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java +++ b/photon-core/src/main/java/org/photonvision/vision/camera/LibcameraGpuSettables.java @@ -40,6 +40,9 @@ public class LibcameraGpuSettables extends VisionSourceSettables { private final LibCameraJNI.SensorModel sensorModel; + private double minExposure = 1; + private double maxExposure = 80000; + private ImageRotationMode m_rotationMode = ImageRotationMode.DEG_0; public final Object CAMERA_LOCK = new Object(); @@ -100,6 +103,12 @@ public LibcameraGpuSettables(CameraConfiguration configuration) { // TODO need to add more video modes for new sensors here currentVideoMode = (FPSRatedVideoMode) videoModes.get(0); + + if (sensorModel == LibCameraJNI.SensorModel.OV9281) { + minExposure = 7; + } else if (sensorModel == LibCameraJNI.SensorModel.OV5647) { + minExposure = 560; + } } @Override @@ -123,15 +132,6 @@ public void setExposureUs(double exposureUs) { // Store the exposure for use when we need to recreate the camera. lastManualExposure = exposureUs; - // Minimum exposure can't be below 1uS cause otherwise it would be 0 and 0 is auto exposure. - double minExposure = 1; - double maxExposure = 80000; - - if (sensorModel == LibCameraJNI.SensorModel.OV9281) { - minExposure = 7; - } else if (sensorModel == LibCameraJNI.SensorModel.OV5647) { - minExposure = 560; - } // 80,000 uS seems like an exposure value that will be greater than ever needed while giving // enough control over exposure. exposureUs = MathUtils.limit(exposureUs, minExposure, maxExposure); @@ -246,4 +246,14 @@ public HashMap getAllVideoModes() { public LibCameraJNI.SensorModel getModel() { return sensorModel; } + + @Override + public double getMinExposureUs() { + return this.minExposure; + } + + @Override + public double getMaxExposureUs() { + return this.maxExposure; + } } diff --git a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java index 84831b558b..8cc8189fb9 100644 --- a/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java +++ b/photon-core/src/main/java/org/photonvision/vision/camera/USBCameraSource.java @@ -212,13 +212,13 @@ private void printCameraProperaties() { private void setAllCamDefaults() { // Common settings for all cameras to attempt to get their image // as close as possible to what we want for image processing - // softSet("image_stabilization", 0); // No image stabilization, as this will throw off odometry - // softSet("power_line_frequency", 2); // Assume 60Hz USA - // softSet("scene_mode", 0); // no presets - // softSet("exposure_metering_mode", 0); - // softSet("exposure_dynamic_framerate", 0); - // softSet("focus_auto", 0); - // softSet("focus_absolute", 0); // Focus into infinity + softSet("image_stabilization", 0); // No image stabilization, as this will throw off odometry + softSet("power_line_frequency", 2); // Assume 60Hz USA + softSet("scene_mode", 0); // no presets + softSet("exposure_metering_mode", 0); + softSet("exposure_dynamic_framerate", 0); + softSet("focus_auto", 0); + softSet("focus_absolute", 0); // Focus into infinity } public QuirkyCamera getCameraQuirks() { @@ -244,11 +244,23 @@ public class USBCameraSettables extends VisionSourceSettables { // changing exposure private int lastBrightness = -1; + double minExposure = 1; + double maxExposure = 80000; + protected USBCameraSettables(CameraConfiguration configuration) { super(configuration); getAllVideoModes(); if (!configuration.cameraQuirks.hasQuirk(CameraQuirk.StickyFPS)) if (!videoModes.isEmpty()) setVideoMode(videoModes.get(0)); // fixes double FPS set + + this.minExposure = exposureAbsProp.getMin(); + this.maxExposure = exposureAbsProp.getMax(); + + if (getCameraConfiguration().cameraQuirks.hasQuirk(CameraQuirk.ArduOV2311)) { + // Property limits are incorrect + this.minExposure = 1; + this.maxExposure = 75; + } } public void setAutoExposure(boolean cameraAutoExposure) { @@ -256,12 +268,12 @@ public void setAutoExposure(boolean cameraAutoExposure) { if (!cameraAutoExposure) { // Pick a bunch of reasonable setting defaults for vision processing - // softSet("auto_exposure_bias", 0); - // softSet("iso_sensitivity_auto", 0); // Disable auto ISO adjustment - // softSet("iso_sensitivity", 0); // Manual ISO adjustment - // softSet("white_balance_auto_preset", 2); // Auto white-balance disabled - // softSet("white_balance_automatic", 0); - // softSet("white_balance_temperature", 4000); + softSet("auto_exposure_bias", 0); + softSet("iso_sensitivity_auto", 0); // Disable auto ISO adjustment + softSet("iso_sensitivity", 0); // Manual ISO adjustment + softSet("white_balance_auto_preset", 2); // Auto white-balance disabled + softSet("white_balance_automatic", 0); + softSet("white_balance_temperature", 4000); autoExposureProp.set(PROP_AUTO_EXPOSURE_ENABLED); // Most cameras leave exposure time absolute at the last value from their AE algorithm. @@ -270,32 +282,34 @@ public void setAutoExposure(boolean cameraAutoExposure) { } else { // Pick a bunch of reasonable setting to make the picture nice-for-humans - // softSet("auto_exposure_bias", 12); - // softSet("iso_sensitivity_auto", 1); - // softSet("iso_sensitivity", 1); // Manual ISO adjustment by default - // softSet("white_balance_auto_preset", 1); // Auto white-balance enabled - // softSet("white_balance_automatic", 1); + softSet("auto_exposure_bias", 12); + softSet("iso_sensitivity_auto", 1); + softSet("iso_sensitivity", 1); // Manual ISO adjustment by default + softSet("white_balance_auto_preset", 1); // Auto white-balance enabled + softSet("white_balance_automatic", 1); autoExposureProp.set(PROP_AUTO_EXPOSURE_DISABLED); camera.setExposureAuto(); // belt-and-suspenders with cscore's call too. } } + @Override + public double getMinExposureUs() { + return this.minExposure; + } + + @Override + public double getMaxExposureUs() { + return this.maxExposure; + } + @Override public void setExposureUs(double exposureUs) { if (exposureUs >= 0.0) { try { - autoExposureProp.set(PROP_AUTO_EXPOSURE_DISABLED); - var propMin = exposureAbsProp.getMin(); - var propMax = exposureAbsProp.getMax(); - - if (getCameraConfiguration().cameraQuirks.hasQuirk(CameraQuirk.ArduOV2311)) { - // Property limits are incorrect - propMin = 1; - propMax = 75; - } + autoExposureProp.set(PROP_AUTO_EXPOSURE_DISABLED); - int propVal = (int) MathUtils.limit(exposureUs, propMin, propMax); + int propVal = (int) MathUtils.limit(exposureUs, this.minExposure, this.maxExposure); if (getCameraConfiguration().cameraQuirks.hasQuirk(CameraQuirk.LifeCamExposure)) { // Lifecam only allows certain settings for exposure diff --git a/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java b/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java index 360262b40d..c8c92ce6b2 100644 --- a/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java +++ b/photon-core/src/main/java/org/photonvision/vision/pipeline/CVPipelineSettings.java @@ -43,6 +43,8 @@ public class CVPipelineSettings implements Cloneable { public boolean cameraAutoExposure = false; // manual exposure only used if cameraAutoExposure is false public double cameraExposureUs = 20; + public double cameraMinExposureUs = 1; + public double cameraMaxExposureUs = 100; public int cameraBrightness = 50; // Currently only used by a few cameras (notably the zero-copy Pi Camera driver) with the Gain // quirk @@ -63,6 +65,8 @@ public boolean equals(Object o) { CVPipelineSettings that = (CVPipelineSettings) o; return pipelineIndex == that.pipelineIndex && Double.compare(that.cameraExposureUs, cameraExposureUs) == 0 + && Double.compare(that.cameraMinExposureUs, cameraMinExposureUs) == 0 + && Double.compare(that.cameraMaxExposureUs, cameraMaxExposureUs) == 0 && Double.compare(that.cameraBrightness, cameraBrightness) == 0 && Double.compare(that.cameraGain, cameraGain) == 0 && Double.compare(that.cameraRedGain, cameraRedGain) == 0 @@ -85,6 +89,8 @@ public int hashCode() { inputImageRotationMode, pipelineNickname, cameraExposureUs, + cameraMinExposureUs, + cameraMaxExposureUs, cameraBrightness, cameraGain, cameraRedGain, diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java index be354bc169..0036205191 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -511,6 +511,8 @@ public PhotonConfiguration.UICameraConfiguration toUICameraConfig() { ret.currentPipelineIndex = pipelineManager.getRequestedIndex(); ret.pipelineNicknames = pipelineManager.getPipelineNicknames(); ret.cameraQuirks = visionSource.getSettables().getConfiguration().cameraQuirks; + ret.maxExposureUs = visionSource.getSettables().getMaxExposureUs(); + ret.minExposureUs = visionSource.getSettables().getMinExposureUs(); // TODO refactor into helper method var temp = new HashMap>(); diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java index 43523f34cd..a692ca9b2c 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionSourceSettables.java @@ -44,6 +44,10 @@ public CameraConfiguration getConfiguration() { public abstract void setExposureUs(double exposureUs); + public abstract double getMinExposureUs(); + + public abstract double getMaxExposureUs(); + public abstract void setAutoExposure(boolean cameraAutoExposure); public abstract void setBrightness(int brightness); diff --git a/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java b/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java index fe91189b2a..4766eb8936 100644 --- a/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java +++ b/photon-core/src/test/java/org/photonvision/vision/processes/VisionModuleManagerTest.java @@ -108,6 +108,16 @@ public HashMap getAllVideoModes() { @Override public void setAutoExposure(boolean cameraAutoExposure) {} + + @Override + public double getMinExposureUs() { + return 1; + } + + @Override + public double getMaxExposureUs() { + return 1234; + } } private static class TestDataConsumer implements CVPipelineResultConsumer {