You save a user set with your region of interest, start the application, and OffsetX and OffsetY read zero. Exposure and gain from the set arrive correctly, the stream runs, but the image sits at the top-left corner of the sensor. The camera is not broken and the set is not lost: a size write happened after the offsets. This guide gives you the mechanism, the write order that keeps the ROI, the valid offset ranges with the arithmetic, and the checks that prove the window stays where you put it.
Why size writes erase your offsets
GenICam cameras expose the capture window through four standard features: Width, Height, OffsetX and OffsetY, with the sensor limits published alongside as WidthMax and HeightMax. The allowed offset range depends on the size you chose. An offset runs from 0 up to the sensor dimension minus the window dimension, so the two sizes are coupled, and the camera re-validates the offsets whenever either one changes.
On most GigE Vision and USB3 Vision cameras, a write to Width or Height clamps both offsets back toward zero, because the previous values may fall outside the new allowed range. Anything that writes size after your offsets therefore wipes them. The usual suspects: a pipeline framework that negotiates stream properties and applies the negotiated resolution to the camera, an SDK parameter file applied at stream start, and your own code writing Width before OffsetX.
A user set has no special protection here. It is a snapshot applied at one instant, and whatever the camera receives afterwards wins. Two timing rules complete the picture: Width and Height normally accept writes only while acquisition is stopped, and many cameras take OffsetX and OffsetY even while streaming. If OffsetX reads 0 after the first frame, something wrote size after your offset write.
The write order that keeps your ROI
- Stop acquisition. Size features need the stream stopped on most cameras.
- Load the user set that holds your tuned settings; a user set that loads on start-up restores every feature it saved, offsets included.
- Write Width, then Height, with exactly the size your pipeline expects: the negotiated value, not an approximation.
- Write OffsetX, then OffsetY, last, on the camera's increment and inside sensor size minus window size, as worked out below.
- Start acquisition.
- Read all four values back and check that the first delivered buffer is Width × Height pixels.
If you control the pipeline, invert the responsibility instead: negotiate the stream to the size stored in the user set, so the framework never writes size at all. The camera leads, the application follows, and nothing can clamp the offsets. If the pipeline refuses that size, fix the negotiation first; the same mismatches also stop acquisition from starting, as in pipeline format negotiation errors.
If you configure from your own code instead, keep the same order inside the script; setting a camera ROI in Python walks through size, offsets and persistence in that sequence.
Worked example: 2000 × 1500 ROI on a 4000 × 3000 sensor
The inputs come from three places: the sensor readout of 4000 × 3000 px from the camera's data sheet, the target window of 2000 × 1500 px from your inspection field, and the offset increment of 4 px from the camera's feature list. A concrete camera for this example is the 12 MP USB3 model LS-CMR-0166 with the IMX226 sensor.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
The rule in words: each offset runs from 0 up to the sensor dimension minus the window dimension, in steps of the increment.
The horizontal limit is 4000 - 2000 = 2000 px and the vertical limit is 3000 - 1500 = 1500 px. Both sit on the 4 px increment: 2000 / 4 = 500 and 1500 / 4 = 375, both whole. The usable ranges are therefore OffsetX 0-2000 px and OffsetY 0-1500 px.
Say the inspection needs the window at OffsetX 1000 and OffsetY 750. Horizontal: 1000 / 4 = 250, an exact multiple, so 1000 px is valid. Vertical: 750 / 4 = 187.5, not a whole number, so round down to 748 px, and 748 / 4 = 187. The delivered window covers pixels x = 1000-2999 and y = 748-2247.
Decision: write Width 2000, Height 1500, OffsetX 1000, OffsetY 748, in that order, then read back before the first frame. If the read-back shows OffsetX 1000 and OffsetY 748, the ROI holds. If either reads 0, a size write comes later in your chain, usually the negotiated-resolution push at stream start, and the offset write must move behind it.
Likely causes in order of probability
The first two rows explain almost every occurrence of this fault.
| Likelihood | Cause | Test | Fix |
|---|---|---|---|
| High | Framework or SDK applies negotiated Width and Height after the user set loads | Read OffsetX after the first frame: it reads 0 | Move the offset write behind the size write, or negotiate the stream to the saved size |
| High | Your code writes offsets before size | Reorder once and re-run | Width, Height, then OffsetX and OffsetY |
| Medium | Offset off the increment or beyond sensor size minus window size | Read back right after the write | Round down to the increment and recompute the limit |
| Medium | User set saved while offsets were still zero | Load the set manually and read the offsets | Save the set again with the final values |
| Low | Power-on default is the factory set, not yours | Power-cycle and inspect the first image | Select your set as the one loaded at reset |
| Low | Features write-protected after a reconnect | Watch the write's error status | Close and re-open the device, then rewrite; see write-protect errors after reconnection |
Checks before you call it fixed
- Read back Width, Height, OffsetX and OffsetY after configuration and before streaming; all four must match the plan.
- Check the first buffer length in your code: Width × Height × bytes per pixel, not just the displayed image.
- Confirm both offsets divide evenly by the increment and stay at or below sensor size minus window size.
- Power-cycle the camera once if the ROI must survive reboots; settings that survive power cycles live in the power-on user set.
- To move the window at runtime, write only OffsetX and OffsetY while streaming; the new position appears on the next delivered frame.
When the usual answer is wrong
Some cameras never clamp: they keep OffsetX and OffsetY across a size write as long as the old offsets still fit the new window. On such a camera, a reset to zero points at a direct offset write somewhere in the application, so search for OffsetX in your code and in every configuration file, not only for Width.
Some sensors separate the readout window from the exposure region: a feature such as ExposureRegionOffsetX moves where light is integrated without cropping the delivered image. Writing it does not resize the frame.
Line scan sensors have no vertical offset in the area-scan sense; OffsetY does not exist there or means something else, so check the feature list before you script it.
Prevention: one configuration path
- Configure in one function, in one order, every start: stop, user set load, Width, Height, OffsetX, OffsetY, start. The same initialization order discipline that keeps manual exposure from being overwritten applies here.
- Save the tuned ROI into a user set and make it the power-on default, so a cold start begins with the window already in place.
- Keep capture frameworks and SDK runtimes current: recent releases treat values from a user set or parameter file as defaults instead of overwriting them.
Pick the sensor, then apply this order
The four window features are standard across GigE Vision and USB3 Vision cameras, so the order above works on any of them. Choose sensor format, resolution and interface in the camera finder, compare area scan cameras side by side, or step through the whole chain in the camera configurator. For embedded builds, board-level cameras expose the same features over USB3 and GigE. If the offsets still read zero after the reorder, contact us with the camera model and your start-up sequence.
Questions
Why does the viewer keep my ROI but my own application does not?
The viewer writes Width, Height, OffsetX and OffsetY from its own panel, in its own order, after it configures the camera, so its size write lands before its offset write. Your application loads the user set and then negotiates a resolution, so its size write lands last and clamps the offsets to zero. Same camera, different order: make your application write the offsets last, exactly as the viewer effectively does.
Is the user set corrupted when the offsets come back as zero?
Almost never. Load the set and read OffsetX and OffsetY immediately, before streaming starts: if they read as saved, the set is intact and holds your values. A zero that appears after streaming starts points to the later size write from stream negotiation, not to the set. Fix the write order instead of recreating the set.
What step size do ROI offsets use?
The camera publishes an increment for OffsetX and OffsetY, and it is small: commonly 2, 4 or 8 px depending on the sensor. A value that is not a multiple of the increment is rejected or rounded down to the previous multiple, which shifts the window by a pixel or two. Read the increment from the feature list and divide each target offset by it before you write.
Do OffsetX and OffsetY affect frame rate?
No. Data volume per frame follows the window size: Width × Height × bytes per pixel. Offsets only move that window across the sensor, so a 2000 × 1500 window delivers the same volume from any position. Frame rate changes only when Width or Height changes, because the camera then reads out fewer or more pixels.
How do I keep the ROI across power cycles?
Save the complete configuration, Width, Height, OffsetX, OffsetY and exposure, into a user set with the camera's save command, then select that set as the one the camera loads at power-on. After the next reboot, read the four values back once. If they hold, the application only has to match its stream to the camera's size.
