docs(ids-cameras): add plan for manual exposure/auto-gain control
CI for csaxs_bec / test (push) Failing after 2m5s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s92UHyMfov6Fb934pGqcU
This commit is contained in:
x12saandClaude Sonnet 5 committed 2026-09-11 18:01:49 +02:00
1 parent 0cadd8582e
commit ec3e7c0679
1 file changed
+133
+133
View File
@@ -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.