Your set_brightness call keeps answering with a nine-digit negative number and success: False. The image looks fine to the eye, the exposure and gain read back plausibly, and nothing you change in the scene helps. On current camera generations the auto-exposure target is not an 8-bit gray value: it is a normalized float the driver expects between 0.0 and 1.0. Send 120 and your request sits far above what the feature accepts. This guide gives you the conversion arithmetic, the way to tell which scale your camera speaks, and what to do when success stays False anyway.
Why does set_brightness return a huge negative number?
Because the brightness target on current-generation cameras is a normalized float between 0.0 and 1.0, and you sent 120 — an 8-bit gray value that sits 120 times above the accepted maximum, so the read-back crosses a broken type boundary and prints garbage near -1.0 billion instead of a gray level.
A failing session looks like this, using the field names the driver's service definition declares:
set_brightness(target_brightness=120, exposure_auto=true, gain_auto=false)
reached_brightness: -997042304
reached_exposure_time: 4000.0
reached_gain_value: 0.5000000596046448
success: False
Three more calls returned -1001849984, -973736896 and -993516864, while exposure sat at 4000.0 and gain at 0.5 every time. No sensor measures brightness as a negative nine-digit integer, so that value is not a reading — it is a corrupted read-back, and it appears exactly when the request lies outside the feature's range. The mismatch lives between the two sides of the driver: the wrapper's service definition types target_brightness as an integer, the camera side expects a float, and values that cross that boundary in the wrong representation come back mangled. The gain field carries the same fingerprint — 0.5000000596046448 is a number that passed through a 32-bit float, not a clean 0.5.
Is the brightness target 0-255 or 0.0-1.0 on my camera?
Query the feature's minimum and maximum before your first call: a maximum of 1.0 means a normalized float target, a maximum of 255 means the old 8-bit gray convention — one query settles it for the camera in your hands.
GenICam-based cameras publish that range in the feature tree next to the value, so print it during driver bring-up instead of assuming either scale. The split runs along camera generation, not sensor size or interface: 8-bit-era drivers took an integer gray target, current firmware publishes the target as a float. The pattern spans area scan cameras, smart cameras and 3D sensors, so make the range query part of bring-up on every new head. On 3D sensors, read the exposure feature's range the same way — querying a 3D camera's exposure range from the API before you set it walks through that case. If you have not chosen a target value at all yet, setting an auto exposure brightness target for inspection covers picking the number; this article is about why the number you send is refused.
Any current camera in the families below can carry the normalized convention, so query the range before you configure:
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Sensor format
- 1/1.7"
- Interface
- USB3
Smart cameras and profilers follow the same pattern:
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Type
- Laser profiler
How do I convert a 0-255 target to the normalized scale?
Divide the gray value by 255 and send the result as a float: a 120-gray recipe becomes 120 ÷ 255 = 0.4706, so you send 0.47 — and to read a value back in familiar units, multiply and round.
| Old 8-bit recipe | Normalized target | Read-back check |
|---|---|---|
| 64 | 0.25 | 0.25 × 255 = 63.75 → 64 |
| 120 | 0.47 | 0.47 × 255 = 119.85 → 120 |
| 128 | 0.50 | 0.50 × 255 = 127.5 → 128 |
| 192 | 0.75 | 0.75 × 255 = 191.25 → 191 |
| 230 | 0.90 | 0.90 × 255 = 229.5 → 230 |
A porting example: your previous camera was tuned to 120 gray on a matte-metal surface. On the new head send 120 ÷ 255 = 0.4706, stored as 0.47 once the feature snaps it to its increment — camera float values are silently snapped to the nearest increment explains that behavior. The 0.3 default you see in the viewer is 0.3 × 255 = 76.5, about 77 gray — a darker recipe than your 120. PLC programmers hit the identical conversion when a control loop wants a normalized setpoint; converting an integer PID setpoint to a 0.0-1.0 range is the same arithmetic on the controls side.
Why does a value like 1.5 fail the same way as 120?
Because the ceiling of the accepted range is 1.0, and 1.5 is as far outside it as 120 was: the failing session returned reached_brightness: -1029930048.0 for 1.5, the same garbage family as the integer calls, while a legal 0.5 returned a sane 83.0.
The rule is short: keep the target inside 0.0 to 1.0. When 1.0 — full scale — still gives you a dark image, the target is the wrong lever; the scene needs more light, a wider aperture, or a higher exposure ceiling. Steering the loop toward the top of the range also spends your highlight headroom on reflective parts.
What does success: False mean when the read-back looks sane?
It means the auto-exposure loop ran out of headroom before the image reached your target: against a request of 0.5, the driver reported reached_brightness 83.0 with exposure at 5000 µs and gain 0.0 — a usable image, a failed call.
The numbers tell the story. 83 gray on a 255 scale is 83 ÷ 255 = 0.33, short of the 0.5 target of 128 gray. Closing that gap with exposure alone takes 128 ÷ 83 = 1.54 times the light: 1.54 × 5000 µs = 7700 µs, so the exposure ceiling must sit above roughly 7.7 ms. The alternatives are about 55% more illumination — (128 − 83) ÷ 83 = 0.54 — or some gain and the noise it brings. Raise the right lever and the next call succeeds. Auto exposure limits are set in microseconds, so query before you set, and manual exposure comes out darker than auto exposure explains why the loop spends gain before you would. For ground truth while tuning, read the exposure time from frame metadata under auto exposure rather than trusting the service reply.
How do I fix the call for both camera generations?
Branch on the queried maximum: when it reads 1.0, send target_gray ÷ 255 as a float; when it reads 255, send the integer unchanged — one wrapper then serves both camera generations.
- Print the minimum and maximum of the exposure-target feature when the driver connects.
- Decide the scale from the maximum: 1.0 means normalized float, 255 means 8-bit gray.
- Convert the recipe before the call, in one place:
int target_gray = 120 // old recipe, 8-bit gray
float target = target_gray / 255.0f // 0.470588, inside the 0.0-1.0 range
- Read the feature back after the call: a correct run returns the value you sent, to the feature's increment, and reports reached_brightness in small magnitudes — the healthy run above read 83.0 against a 0.5 request.
- If the wrapper's service definition still types the field as an integer, change the field to a float and rebuild. That is a driver-side type fix, not a camera setting, and it is the durable one — per-call casts leave the trap in place for the next integrator.
What else should I check before blaming the camera?
Run four checks in order — exposure-limit units, metadata read-back, increment snapping and gain headroom — because each produces a success: False that imitates a units bug without being one.
- Exposure ceiling: a 5000 µs cap cannot reach a 0.5 target on a dim scene; the call fails with sane numbers, as the arithmetic above shows.
- Metadata: the service reply echoes what the loop last tried, while frame metadata carries the exposure actually used — compare the two before judging the loop.
- Increment: a stored 0.47 when you sent 0.4706 is snapping, not rejection; compare against the feature's increment.
- Gain headroom: in the failing runs gain parked at 0.5 with exposure at its limit — a loop with nothing left to spend, not a camera fault.
After any driver or firmware update, run the range query once more: the convention has already changed between camera generations once, and your bring-up printout is what catches the next change.
Pick the next camera in the camera finder, build the whole chain with the system configurator, starting at the camera step, and browse the cameras topic page for more integration fixes. For a driver question specific to your line, the contact page reaches our applications team.
Questions
Does the normalized target change with 10-bit or 12-bit pixel data?
No. A normalized target is a fraction of full scale, so 0.5 means half of whatever the sensor ships — 512 of 1023 on 10-bit data, 2048 of 4095 on 12-bit. That is the point of the convention: the old 0-255 integer assumed 8-bit pixels and broke the moment bit depth changed. Divide by 255 only when porting an old 8-bit recipe.
Why did the same integer call work on my previous camera?
Its driver generation spoke the old convention: an integer gray target on an 8-bit scale. Current camera firmware publishes the target as a float feature whose range tops out at 1.0, and the wrapper must match that type. Keep old recipes working by dividing by 255 at the boundary — a one-line shim — so callers elsewhere in the plant keep their behavior unchanged.
What should reached_brightness look like on a healthy call?
A small number on the scale you set: the working run in this story reported 83.0 against a 0.5 target, meaning the loop reached roughly 83 gray before its exposure ceiling stopped it. Magnitude is the tell — anything near ±1.0 billion is a corrupted read-back from an out-of-range request, not a measurement. A sane value with success: False is a range problem, not a units problem.
Do smart cameras and 3D sensors use the 0 to 1 target too?
Many current ones do, but the camera answers this better than any rule of thumb: read the min and max of the exposure-target feature and branch on the maximum. On 3D sensors also read the exposure range, quoted in microseconds, because longer exposure buys depth precision at the cost of frame rate — the target is only one of the three levers you balance.




