From ec3e7c067920b45fab2d950c20fbc2834363946a Mon Sep 17 00:00:00 2001 From: x12sa Date: Fri, 11 Sep 2026 18:01:49 +0200 Subject: [PATCH] docs(ids-cameras): add plan for manual exposure/auto-gain control No code changes -- IDSCamera is in production use during beamtimes. Documents the intended USER_ACCESS additions (get/set_exposure_time, set_auto_exposure_enabled, set_auto_gain_enabled) for a future session. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014s92UHyMfov6Fb934pGqcU --- docs/plans/ids-camera-manual-exposure.md | 133 +++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/plans/ids-camera-manual-exposure.md diff --git a/docs/plans/ids-camera-manual-exposure.md b/docs/plans/ids-camera-manual-exposure.md new file mode 100644 index 00000000..b05cf79c --- /dev/null +++ b/docs/plans/ids-camera-manual-exposure.md @@ -0,0 +1,133 @@ +# Plan: Add manual exposure / auto-gain toggle control to IDSCamera + +Status: **planned, not yet implemented** (`IDSCamera` is used in production during +beamtimes; implementation should happen in its own change, reviewed and tested outside +a live beamtime). + +## Context + +`IDSCamera` (the cSAXS ophyd device wrapping IDS uEye cameras via the vendor's `pyueye` +SDK) currently gives operators no way to leave auto-exposure/auto-gain and drive the +camera manually. The underlying driver already has almost everything needed — it's just +not wired up or exposed to BEC clients: + +- `Camera.exposure_time` (get/set property) — `base_integration/camera.py:220-235` +- `Camera.set_auto_shutter(enable)` — `base_integration/camera.py:248-257` (auto-exposure toggle) +- `Camera.set_auto_gain(enable)` — `base_integration/camera.py:237-246` (auto-gain toggle) + +None of these three are called anywhere, and none are on `IDSCamera.USER_ACCESS`, so a +BEC client can't reach them today. The sibling `AlliedVisionAravisCamera` class already +has the equivalent pattern wired up for `exposure_time`/`gain` as plain +`get_x`/`set_x` USER_ACCESS methods (`allied_vision_aravis_camera.py:222-238`) — this +plan mirrors that same style for IDS, plus adds the auto-exposure/auto-gain enable +toggles IDS has (and Allied Vision doesn't). + +Per user decisions (2026-09-11): cover exposure control + the auto-gain enable/disable +toggle only (no manual gain *value* — the driver has no SDK call for that yet); expose +everything as plain `USER_ACCESS` methods (matching the existing Allied Vision pattern, +not a new `Signal`); and leave connect-time default behavior untouched (no new +constructor/config kwarg — the camera keeps whatever state `is_ResetToDefault()` leaves +it in unless a client explicitly calls the new methods). + +## Changes + +### 1. `csaxs_bec/devices/ids_cameras/ids_camera.py` + +Add a new `############## Exposure / Gain ##############` section (mirroring +`allied_vision_aravis_camera.py:222-238`), placed after the live-mode/ROI methods and +before `############## User Interface Methods ##############`: + +```python +def get_exposure_time(self) -> float: + """Get the current exposure time in milliseconds.""" + return self.cam.exposure_time + +def set_exposure_time(self, value: float) -> None: + """Set the exposure time in milliseconds. Does not itself disable auto-exposure -- + call set_auto_exposure_enabled(False) first, or the driver will keep overriding it.""" + self.cam.exposure_time = value + +def set_auto_exposure_enabled(self, enable: bool) -> None: + """Enable or disable the camera's auto-exposure (auto-shutter).""" + self.cam.set_auto_shutter(enable) + +def set_auto_gain_enabled(self, enable: bool) -> None: + """Enable or disable the camera's auto-gain.""" + self.cam.set_auto_gain(enable) +``` + +(Confirm the SDK's exposure unit — `is_Exposure`/`IS_EXPOSURE_CMD_*` is documented in +milliseconds; adjust the docstring if inspection shows otherwise.) + +Add the four new method names to `USER_ACCESS` (`ids_camera.py:67-76`): +`"get_exposure_time"`, `"set_exposure_time"`, `"set_auto_exposure_enabled"`, +`"set_auto_gain_enabled"`. + +No changes needed in `base_integration/camera.py` — `exposure_time`, `set_auto_shutter`, +`set_auto_gain` already exist there exactly as needed. + +### 2. `csaxs_bec/devices/sim/sim_cameras.py` + +`SimIDSCamera` replaces `self.cam` with `_SimIDSBackend` (line 211), which currently has +no `exposure_time` attribute and no `set_auto_shutter`/`set_auto_gain` methods — calling +the new `IDSCamera` methods against a sim device would raise `AttributeError`. Add to +`_SimIDSBackend` (mirroring `_SimAlliedVisionBackend.exposure_time`, lines 397-403): + +```python +def __init__(self, ...): + ... + self._exposure_time = 10000.0 # ms, matches _SimAlliedVisionBackend's default + self._auto_exposure = True + self._auto_gain = True + +@property +def exposure_time(self) -> float: + return self._exposure_time + +@exposure_time.setter +def exposure_time(self, value: float): + self._exposure_time = value + +def set_auto_shutter(self, enable: bool): + self._auto_exposure = bool(enable) + +def set_auto_gain(self, enable: bool): + self._auto_gain = bool(enable) +``` + +### 3. Tests — `tests/tests_devices/test_ids_camera.py` + +Mirror `test_allied_vision_aravis_camera.py:124-139`'s pass-through style (that file's +fixture mocks the whole `camera.cam`, same as `test_ids_camera.py`'s fixture), e.g.: + +```python +def test_get_set_exposure_time(ids_camera): + ids_camera.cam.exposure_time = 5000.0 + assert ids_camera.get_exposure_time() == 5000.0 + ids_camera.set_exposure_time(1234.5) + assert ids_camera.cam.exposure_time == 1234.5 + +def test_set_auto_exposure_enabled(ids_camera): + ids_camera.set_auto_exposure_enabled(False) + ids_camera.cam.set_auto_shutter.assert_called_once_with(False) + +def test_set_auto_gain_enabled(ids_camera): + ids_camera.set_auto_gain_enabled(False) + ids_camera.cam.set_auto_gain.assert_called_once_with(False) +``` + +## Verification + +- Run the existing + new unit tests: `pytest tests/tests_devices/test_ids_camera.py -v` + (and `test_allied_vision_aravis_camera.py` to confirm no regressions from the shared + pattern). +- Manual/integration check against real hardware (if available during a beamtime/test + session): from a BEC client, on a connected IDS camera device (e.g. `dev.cam_xeye`): + ```python + dev.cam_xeye.set_auto_exposure_enabled(False) + dev.cam_xeye.set_exposure_time(20000) # e.g. 20 ms + print(dev.cam_xeye.get_exposure_time()) + dev.cam_xeye.set_auto_gain_enabled(False) + ``` + and confirm the live image brightness responds to the manual exposure value instead of + drifting on its own.