Cameras & sensorsTroubleshooting

Auto exposure limit not working: check SDK version and interpreter

When an auto exposure limit is not working, the cap never reached the sensor: an old SDK runtime or a different interpreter. Test each cause in order.

LumaSpec Vision9 min read Share on LinkedIn

On this page 11 sections
  1. 01The symptom: two plateaus, one cap
  2. 02First test: read the exposure from the frames, not from your code
  3. 03Cause 1: the library in the process predates the fix
  4. 04Cause 2: you upgraded a different interpreter than the one you run
  5. 05Cause 3: the limit is applied once, then overwritten
  6. 06Cause 4: the unit is microseconds
  7. 07Worked example: set the cap from the blur budget
  8. 08Verify, then lock it down
  9. 09When the cap is the wrong fix
  10. 10Checklist before you blame the lighting
  11. 11Questions

You set an auto-exposure cap of 5000 µs, the call returns without an error, and the frames still report 30 ms. The first suspect is usually the lighting; it almost never is. In the case behind this checklist, the limit never reached the sensor's auto-exposure loop: the host library predates the fix, and the process ran under a different interpreter than the one that received the upgrade. What follows is the diagnostic sequence in order of probability, the per-frame telemetry that settles each test, and the blur arithmetic that tells you what the cap should be before you send it again.

The symptom: two plateaus, one cap

A PoE machine-vision camera was capped with initialControl.setAutoExposureLimit(maxExposureTimeUs=5000), and the same limit was re-sent on the control queue every 0.3 s while streaming at 15 fps. The per-frame exposure printout showed only two values, 29.995 ms and 4.996 ms, with nothing in between. Read those numbers before touching anything:

  • 4.996 ms is your 5000 µs cap taking hold. The small shortfall is the sensor's line-time quantization, not a different limit.
  • 29.995 ms is the roughly 30 ms ceiling the exposure loop runs to when nothing holds it down.
  • At 15 fps the frame period is 1 ÷ 15 = 66.7 ms, so both values fit inside the period. Frame rate is not the constraint.

Auto exposure is swinging between a cap that occasionally binds and a default ceiling it escapes to. That is a state and version problem, not a light problem.

First test: read the exposure from the frames, not from your code

The only evidence that counts is the exposure each frame says it used. In that SDK the frame object carries getExposureTime(), a timedelta you print as total_seconds() × 1000 in milliseconds. On GenICam cameras the same evidence is chunk data embedded with each image, timestamp included.

Decision rule: under steady illumination, any frame reporting more than your cap means the sensor never adopted the limit — stop adjusting the light and fix the delivery path. Frames pinned at the cap mean the limit works, and your real problem is brightness, gain, or the blur budget itself.

Cause 1: the library in the process predates the fix

Host-side SDKs carry bugs like any library, and a limit the API accepts without error while the sensor never receives it is a known bug class. In this case the vendor shipped the fix in 2.25.0.0, with 2.25.1.0 the current release at the time; older copies returned success and dropped the call. Test from inside the process, never from a second shell:

import importlib.metadata as md
import sys

PACKAGE: str = "your-camera-sdk"  # the pip name used to install it
print(sys.executable)       # interpreter this process runs on
print(md.version(PACKAGE))  # SDK version this process imports

Run the file with your real launch command. A version older than the release that carries the fix is your answer: upgrade, then repeat the telemetry test.

Cause 2: you upgraded a different interpreter than the one you run

This is what actually solved the case. pip installs into one interpreter's site-packages; python and python3 can be two separate installations, each holding its own copy of the SDK. The upgrade went to python3, the app was started with python, and the fault persisted on hardware that had already been fixed. The same trap appears as a service unit pointed at another binary, a virtual environment that never gets activated, a notebook kernel, or a container image built weeks earlier. The sys.executable line above, run under the real launch command, settles it; the fix is to start the app from the upgraded environment, or to install the SDK into the one you start.

Two live write-ups cover the pattern in depth: pinning one library version everywhere in the build and matching a GenICam package to your interpreter.

Cause 3: the limit is applied once, then overwritten

The exposure loop re-initializes when streaming starts and when resolution, frame rate, or ISP settings change; a limit set only in initial control can be lost along that path. The case code re-sent the limit every 0.3 s, which is why the cap kept reappearing between excursions to the ceiling. Two better patterns exist: register the limit as a persistent startup setting on the device, or send it after the first frames arrive and re-send after every mode change. Expect a one-frame delay between a control write and its effect on the telemetry — setting exposure every frame measures it, and startup settings overwriting an upper exposure limit is the same failure seen from the device side.

Cause 4: the unit is microseconds

The parameter name carries the unit: maxExposureTimeUs takes microseconds, so 5000 means 5.000 ms. Write 5 and you have asked for 5 µs, a value no scene can reach, so the loop saturates at its ceiling and the symptom looks identical to a broken limit. The telemetry tells you which one you have: 4.996 ms reported against a 5000 µs cap is a working limit. Read the current limits back before you change them; querying exposure limits in microseconds shows the check.

Worked example: set the cap from the blur budget

The 5 ms cap in the case was a round number, not a requirement. Derive it instead. The example camera is the LS-CMR-0166, a 12 MP USB3 camera with 4000 × 3000 resolution at 30 fps.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

Over a 300 mm wide field of view, the object-side pixel size is 300 mm ÷ 4000 px = 0.075 mm/px. The part moves at 200 mm/s. Blur is speed × exposure:

GSD = field of view width / pixels across it t_max = blur_px × GSD / part_speed
  • Allowed blur of 1 px: t_max = 1 × 0.075 mm ÷ 200 mm/s = 0.000375 s = 375 µs.
  • The case's 5 ms cap: blur = 200 mm/s × 0.005 s = 1.0 mm, which is 1.0 ÷ 0.075 ≈ 13 px of smear. Unusable.
  • Decision: cap at 400 µs. Blur = 200 mm/s × 0.0004 s = 0.08 mm ≈ 1 px, and the exposure occupies 400 µs of the 33.3 ms frame period at 30 fps, about 1.2 %.

Dropping from 5000 µs to 400 µs costs 5000 ÷ 400 = 12.5× the light, or log2(12.5) ≈ 3.6 stops. Opening the lens from f/4 to f/2 recovers 4× of that, two stops; the remaining 12.5 ÷ 4 ≈ 3× must come from the light source or from gain. At these speeds, shutter type matters as much as the cap: each row of a rolling shutter exposes at a slightly different instant, so fast parts shear even at a correct exposure time. The MV-CH120-11UC, a 12 MP USB3 camera with the IMX304 sensor and a global shutter, is the class to use.

Area scan camerasHikrobot MV-CH120-11UC 12 MP Area Scan Camera, USB3, IMX304, High-speed version, ColorMV-CH120-11UC
Sensor format
1.1"
Lens mount
C-mount
Interface
USB3
Resolution
12 MP
$2,339.00 · quotedView and quoteBuild a system with it

The general method is in capping the maximum auto exposure time; when your smallest feature needs more pixels than this example, size the sensor first:

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

Verify, then lock it down

Re-run the telemetry for 60 s while the scene changes: lights on and off, target moved through the field. Every frame must report at or below the cap, plus the quantization you already know (4.996 ms against a 5.000 ms cap). Then keep the fix: the exact interpreter path in the service definition, the SDK version in the lockfile, and the two-line version print at startup. On GenICam cameras the exposure-auto mode and its upper limit live in the feature interface — setting exposure through a GenICam API in Python shows the call pattern — and writing sensor registers directly is the fallback when no API exposes the limit.

When the cap is the wrong fix

If the blur budget demands 400 µs but the image only reaches usable contrast at 5 ms, do not raise the cap — add photons. Raise the light output by the missing factor, move the light closer, or strobe the source and gate the camera inside the pulse: a 100 µs strobe freezes motion better than any continuous exposure cap. On smart cameras with integrated lighting, raising the light is the first move, because the exposure loop and the source are one device. With a constant scene, fixed manual exposure beats a capped loop for repeatability — why manual exposure looks darker than auto exposure explains the gain change you will see.

Checklist before you blame the lighting

  • Print the per-frame exposure; find the cap value and the default ceiling in the numbers.
  • Print sys.executable and the SDK version from inside the process that talks to the camera.
  • Check the release notes for an auto-exposure fix newer than the version you print.
  • Start the app under the environment you upgraded; pin interpreter and library version in deployment.
  • Re-send the limit after any change to resolution, frame rate, or ISP settings.
  • Confirm the unit suffix: Us in a parameter name means microseconds.
  • Compute the cap as blur pixels × pixel size ÷ speed, then confirm it in 60 s of telemetry.
  • If light is short, add output, open the aperture, or strobe — keep the cap.

When the camera side is settled, size the rest of the cell with the camera configurator, compare area scan cameras in the camera finder, or read more set-up pieces on the cameras topic page.

Questions

Why does the reported exposure jump between my cap and about 30 ms?

The two plateaus are the limit you sent and the ceiling the exposure loop runs to when nothing holds it down. The jump means the limit is not persistent in the sensor's state: each control write clamps exposure for a while, the state is then lost, and the loop climbs back to the ceiling. A persistent startup setting, a newer runtime library, or a re-send after every mode change stops the jumping.

How do I tell which Python interpreter is running my camera code?

Print sys.executable from inside the running process, not from a second shell. On many Linux installs python and python3 are different installations, and each holds its own copy of every package, so an upgrade under one leaves the other untouched. Printing the SDK version with importlib.metadata in the same process exposes the mismatch in two lines.

How short an exposure cap does a moving part need?

Set it from the blur budget: exposure no longer than allowed blur in pixels times object size per pixel, divided by speed. Example: 1 mm per pixel and a part at 250 mm/s allows 1 × 1 mm ÷ 250 mm/s = 0.004 s, so a 4 ms cap holds blur near one pixel. Double the speed and the cap must halve; halve the pixel size and it must halve too.

Does updating the camera SDK also update the camera firmware?

Often, yes: embedded-camera SDKs bundle matching device firmware and flash it when the device opens, which is why host and device versions must move together. The failure described here was host-side only, so starting the app under the interpreter that held the new library fixed it without any firmware change on the camera.

Should I cap auto exposure or switch to fixed manual exposure?

Keep a capped auto exposure when scene brightness changes part to part: the loop adapts and still respects your blur budget. Switch to fixed manual exposure when scene and lighting are constant — it removes the hunting and makes brightness repeatable for threshold tools. Leave the cap set either way, so any fallback to auto cannot reintroduce blur.