Repository navigation
feat(toupcam): global reset mode (ENABLE_TOUPCAM_GLOBAL_RESET) and packed 12-bit (MONO12PACKED) - #705
Merged
Alpaca233 merged 5 commits intoOct 17, 2026
Conversation
…cked 12-bit (MONO12PACKED) Global reset: every row starts exposing at once, so triggered frames need no rolling-shutter strobe delay. Opt-in through the new ENABLE_TOUPCAM_GLOBAL_RESET flag in _def (settable from the ini). The driver turns it on in the trigger modes and off in continuous mode, where it would halve the frame rate and shade the image under steady light. A camera that rejects the option is not asked again. A resolution without a rolling-shutter timing entry (the ITR10CMOS26000KMA's 6208x4168) now gets a zero strobe with a warning instead of a division by zero. MONO12PACKED: 12-bit pixels packed on the wire (TOUPCAM_PIXELFORMAT_RAW12PACK) to save USB bandwidth, on cameras with TOUPCAM_FLAG_RAW12PACK. The SDK unpacks them to uint16 and the driver shifts them up 4 bits so frames sit on the same left-aligned scale as MONO12. The mode is only valid at 1x1 binning, so 12-bit resolves to MONO12PACKED at 1x1 and MONO12 at any other binning; set_binning leaves the packed mode before resizing and re-enters it after. Also adds ToupcamCameraModel.ITR10CMOS26000KMA and the PIXEL_FORMAT / GLOBAL_RESET_MODE options to the startup option dump. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2PACK capability probe The ITR10CMOS26000KMA joins _DELIVERED_PIXEL_FORMAT as (MONO12PACKED, MONO12), so above 8 bits it offers and runs only the packed mode unbinned and the 16-bit mode binned, the same way the ITR3 relabels MONO16 to MONO12 when binned. The SDK flag check and _pixel_format_for_binning go away; other models keep master's format list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fter review - The strobe calculation resets the precise framerate once, on the rolling-shutter path only; under global reset it no longer touches the SDK on every exposure change. - Global reset state starts unknown so the first write always lands, replacing the startup probe of the option. _set_global_reset takes the acquisition mode, so the trigger-modes-only policy lives in one place. - The packed 12-bit frame is shifted in place on the processed copy instead of allocating the full frame a second time per frame. - _refresh_pixel_format was dead (every writer already stores the delivered format); _configure_camera reuses _change_pixel_format; set_binning tests both conditions on locals rather than on a side effect. - The ToupCam tests share one camera skeleton, capabilities builder and frame-callback harness (tests/control/toupcam_test_utils.py) instead of three copies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Software-trigger illumination, black-level preservation, and failed global-reset transitions need correction.
3 open issues
What changed in this PR
Adds optional ToupCam global reset and ITR10 packed 12-bit support to the camera driver.
Changes:
- Adds configurable global reset and unknown-resolution timing fallback.
- Adds packed-format selection, binning transitions, and frame scaling.
- Expands driver tests and shares camera test helpers.
| File | Description |
|---|---|
| software/tests/control/toupcam_test_utils.py | Adds shared fake-camera builders. |
| software/tests/control/test_camera_toupcam_pixel_format.py | Tests packed formats, binning, and scaling. |
| software/tests/control/test_camera_toupcam_global_reset.py | Tests reset configuration and timing behavior. |
| software/tests/control/test_camera_toupcam_buffer_race.py | Reuses shared callback helpers. |
| software/squid/config.py | Adds ITR10 and MONO12PACKED definitions. |
| software/control/camera_toupcam.py | Implements global reset and packed-format handling. |
| software/control/_def.py | Adds the opt-in global-reset flag. |
Review effort: Balanced
| the frame rate (measured on an ITR10CMOS26000KMA). The option is changed with the stream stopped, so a | ||
| change costs a stream restart. | ||
| """ | ||
| enabled = acquisition_mode != CameraAcquisitionMode.CONTINUOUS and ENABLE_TOUPCAM_GLOBAL_RESET |
Comment on lines
+598
to
+604
| except toupcam.HRESULTException as ex: | ||
| self._log.info( | ||
| f"Global reset mode not supported by this camera: {control.toupcam_exceptions.explain(ex)}" | ||
| ) | ||
| self._global_reset_supported = False | ||
| self._global_reset_enabled = False | ||
| return |
Comment on lines
+833
to
+841
| old_format = self._pixel_format | ||
| new_format = self._delivered_format(old_format, (binning_factor_x, binning_factor_y)) | ||
| # MONO12PACKED only works at 1x1, so leave it before resizing and enter it after. | ||
| if old_format == CameraPixelFormat.MONO12PACKED and new_format != old_format: | ||
| self._change_pixel_format(new_format) | ||
| self._raw_set_resolution(width, height) | ||
| if new_format == CameraPixelFormat.MONO12PACKED and new_format != old_format: | ||
| self._change_pixel_format(new_format) | ||
| self._pixel_format = new_format |
…on failed writes, keep black level Addresses the Copilot review on Cephla-Lab#705. - Global reset is only enabled in hardware trigger mode. In software trigger mode the illumination stays on until the frame has been read (and between frames in live), so the rows read out last integrate longer and the image shades top to bottom, the same as in continuous mode. Only hardware trigger pulses the light for exactly the exposure. - A global reset write that fails after a successful one no longer marks the option unsupported and the mode off: only E_NOTIMPL does, any other error keeps the last known state (which still matches the camera) and raises. The first write at startup stays forgiving so a camera that answers an unknown option with another code can still open. - A pixel format change, including the ITR10's binning transitions, keeps the current black level instead of re-applying the config default: it is read back in user units before the SDK mode switch and re-sent on the new scale after. The default is applied at startup only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n a mode switch - The driver keeps the black level last set (the config default at startup) on the 8-bit scale, the way it already keeps the exposure time and pixel format, and re-sends it after an SDK mode switch. This drops the read-back, the startup-only parameter on _change_pixel_format and one SDK call per switch. - The reason global reset is hardware-trigger only is explained once, in _set_global_reset; the _def comment, the strobe comment and the tests state the policy only. - The two first-write-failure tests are one parametrized test; the constructor comment on when the option is marked unsupported is brought up to date. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Ports the ToupCam global reset mode and packed 12-bit (MONO12PACKED) support that was developed on the bench (against an older master, on an ITR10CMOS26000KMA) onto current master, with a
_defswitch for global reset.Global reset mode
ENABLE_TOUPCAM_GLOBAL_RESET = Falseincontrol/_def.py(setenable_toupcam_global_reset = Trueunder[GENERAL]in the machine ini to turn it on). Default off, so existing ToupCam setups are unchanged.TOUPCAM_OPTION_GLOBAL_RESET_MODEon in hardware trigger mode and off otherwise. With global reset every row starts exposing at once, so the strobe time and trigger delay are 0, but the rows keep integrating until read out, so the image is only flat if the light is off before the readout starts. Only hardware trigger mode does that (the microcontroller pulses the illumination for exactly the exposure); software trigger and continuous mode keep the light on through the readout, where global reset would shade the image top to bottom and halve the frame rate._pause_streaming, which also gates triggers). A camera that rejects the option (E_NOTIMPL, or any error on the startup write) logs it once and is not asked again; a later write that fails for any other reason leaves the driver's state matching the camera's and raises.ZeroDivisionError._reset_precise_framerateso both early returns still reset it.MONO12PACKED (ITR10CMOS26000KMA only)
CameraPixelFormat.MONO12PACKED(bit depth 12) andToupcamCameraModel.ITR10CMOS26000KMA._DELIVERED_PIXEL_FORMATtable as(MONO12PACKED, MONO12): above 8 bits it offers and runs only the packed 12-bit mode unbinned (the camera has RAW8, RAW16 and RAW12PACK, no plain RAW12) and the 16-bit mode carrying 12-bit data when binned, where the packed mode is not available. Any mono request above 8 bits on the ITR10 resolves to those, the same way MONO16 resolves to MONO12 when binned on the ITR3. The mode is tied to the model; no SDK capability flag is consulted.TOUPCAM_OPTION_PIXEL_FORMAT = RAW12PACK; setting BITDEPTH takes the camera back to RAW8/RAW16.set_binningleaves the packed mode before resizing and re-enters it after.PullImageV2unpacks to[0, 4095]; the frame callback shifts up 4 bits so frames sit on the same left-aligned scale as MONO12 (max_value= 65520), so intensities don't jump when binning changes.No GUI changes: the camera settings widget already re-lists and re-selects the delivered format after a pixel-format or binning change.
Not ported (out of scope here, in the same bench file)
The bench driver also carried ITR10 trigger-robustness changes: waiting for an in-flight frame before exposure/gain changes, restarting the stream when a trigger times out, pausing the stream on every acquisition-mode change, and re-pointing the trigger source at software. Those are separate behaviour changes and are not planned for this PR.
Verification
tests/control/test_camera_toupcam_global_reset.py(strobe shortcut, unknown-resolution fallback, the_defgate, hardware trigger only, stream pause ordering, unsupported camera, failed later writes) and additions totests/control/test_camera_toupcam_pixel_format.py(ITR10 rows in the delivered-format and offered-format tables, the SDK mode a 12-bit request selects per binning, the leave/enter ordering around a binning change, black level factor, black level kept across binning and pixel-format changes, packed frame scaling through the real_on_frame_callback).pytest tests/control/test_camera_toupcam_*.py tests/control/test_multi_camera_models.py tests/squid tests/control/test_def.py tests/control/test_gui_camera_settings_restore.py: 441 pass, 1 skip.tests/control/test_widgets.py: 160 pass; the one failure (test_check_ram_available_with_error_dialog_insufficient_ram) fails identically on clean master in this environment.🤖 Generated with Claude Code