Skip to content

Camera plugin base independent of pylablib, with a driver contract - #11

Merged
seb5g merged 5 commits into
5.0.xfrom
feat/camera-driver-interface
Oct 26, 2026
Merged

seb5g merged 5 commits into
5.0.xfrom
feat/camera-driver-interface

Conversation

@seb5g

@seb5g seb5g commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Before: the camera plugin base (CameraBasePyLabLib) imported pylablib and opencv at module level, so a camera that pylablib does not drive could not reuse its acquisition thread, viewer ROI and binning. Changing the binning of a ROI that does not start at 0 enlarged the ROI, the viewer axes ignored the binning, and changing the ROI during a live acquisition could block the plugin on closing.

After: CameraBase is the generic plugin class (no pylablib import, opencv only for the colour conversion) and CameraBasePyLabLib is a thin subclass of it. CameraDriverBase documents the methods the plugin calls on its driver (those of the pylablib cameras, which satisfy it unchanged); a driver written against it just reimplements the methods that raise NotImplementedError. The ROI, binning and live bugs are fixed and the viewer axes can be in micrometers.

What changes, in four commits:

  1. Split: camera_base.py (CameraBase, Grab, CameraCallback, cam_params) and camera_driver.py (CameraDriverBase). camera_base_pylablib keeps exporting CameraBasePyLabLib, cam_params, CameraCallback and Grab, so the Thorlabs and Andor plugins are untouched. No behaviour change.
  2. ROI and binning fix: the binning branch read get_roi() as (x, width, y, height) while pylablib returns (hstart, hend, vstart, vend, hbin, vbin). The ROI is now always handled in pylablib order and clipped to the sensor (this also makes the "ROI unchanged" test in update_rois effective), the viewer axis size comes from the image shape, the ROI string is read-only instead of being evaled, and the first image sent to set up the viewer is a DataToExport.
  3. Pixel size: an optional get_pixel_size() on the driver gives axes in µm (scaled by the binning), and a ROI drawn in the viewer is converted back to sensor pixels. Drivers without it keep pixel axes.
  4. Live ROI change: the loop of CameraCallback ended only on an event it never handled once the acquisition was cleared under it (pylablib returns from wait_for_frame at once when stopped), so closing the plugin blocked. It now ends when a read gives no frame and the acquisition is stopped; update_rois stops the acquisition before set_roi (most cameras refuse to change the ROI otherwise) and restarts the live afterwards.

How: tests/test_camera_base.py drives the plugin with a fake driver through pymodaq.utils.plugin_testing (26 tests, no pylablib or opencv needed); the binning test fails on the previous code. The blocking case of commit 4 is reproduced by the live test of the camera recipe in pymodaq_plugins_mockexamples (a driver with a real thread); the fake driver of this repo does not reproduce it. Tested against fakes only: not on a real camera, and the Thorlabs and Andor plugins could not be run here (no pylablib, no hardware), only their imports from camera_base_pylablib were checked. The CookbookCamera recipe of pymodaq_plugins_mockexamples builds on this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg

CameraBase (new module camera_base) holds the acquisition thread, the ROI shared
with the viewer, the binning, the exposure and the FPS. It does not import
pylablib, and opencv is only needed for the colour conversion. CameraBasePyLabLib
becomes a thin subclass that adds the serial number setting, and the names the
plugins import from camera_base_pylablib (cam_params, CameraCallback, Grab) are
kept.

CameraDriverBase documents the methods the plugin calls on its controller,
those of the pylablib cameras, so that the driver of any other camera can be
written against it. The behaviour is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg
The binning branch of commit_settings read get_roi() as (x, width, y, height)
while pylablib returns (hstart, hend, vstart, vend, hbin, vbin), so changing the
binning of a ROI that does not start at 0 enlarged it. The ROI is now always
passed in pylablib order, which also makes the "unchanged ROI" comparison of
update_rois effective, and it is clipped to the sensor.

The size of the viewer axes comes from the image shape (it ignored the binning),
the ROI string is read-only and no longer evaluated, the first image emitted to
set up the viewer is a DataToExport, and the generic plugin does not name it
Thorlabs. Tests drive the plugin with a fake driver.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg
When the driver has a get_pixel_size method returning (x, y) in micrometers, the
axes of the viewer are calibrated in micrometers, scaled by the binning, and a
ROI drawn in the viewer is converted back to sensor pixels. Without the method
(the pylablib cameras) the axes stay in pixels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg
Changing the ROI or the binning while live cleared the acquisition under the
thread that waits for the frames. pylablib then returns from wait_for_frame at
once and read_multiple_images gives no frame, so the loop of CameraCallback spun
without handling the event that stops it, and closing the plugin blocked on the
thread. The loop now ends when a read gives no frame and the acquisition is
stopped.

update_rois stops the acquisition before it sets the ROI (most cameras refuse
to change it while a buffer exists) and restarts the live afterwards. stop()
now resets is_live.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tdiso1u1ZhikSutj2Qs5Cg
@seb5g
seb5g merged commit 3cc0977 into 5.0.x Oct 26, 2026
6 of 7 checks passed
@seb5g
seb5g deleted the feat/camera-driver-interface branch October 26, 2026 20:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants