Your camera's node map says 320 × 240. Your loop reads 320 × 240 on the first frame and 320 × 246 on every frame after it, and the consumer dies on a buffer-size error although no setting ever changed. That extra height is the GenTL padding problem: the buffer metadata describes six lines the image does not contain. This page gives you the arithmetic that decides which number is telling the truth, the array-sizing pattern that survives it, and the checks that separate padding from a real geometry change. Everything is worked on the case that keeps recurring: a 320 × 240 Mono16 thermal camera behind a third-party GenTL producer.
The symptom: 240 lines on frame one, 246 after it
The camera reports Width 320, Height 240, PixelFormat Mono16 — 16 bits, 2 bytes per pixel. Frame 0 arrives described as 320 × 240. From frame 1 onward the same kind of buffer reports a height of 246: six extra lines, width and pixel format unchanged. The payload does not grow either. It stays at the size the node map implies, 320 × 240 × 2 = 153,600 bytes. A consumer that sizes its array from the metadata asks for 320 × 246 × 2 = 157,440 bytes from that 153,600-byte buffer and gets:
ValueError: buffer is smaller than requested size
Two details narrow the diagnosis. The sensor is innocent: the fault report covers the same camera behind two different third-party GenTL producers, which points at producer metadata rather than at the camera. And the change happens with no node written and no ROI moved — only the buffer's description moves. The camera here is a thermal infrared model, but the numbers are identical for silicon sensors; camera sensor technologies by spectral range: CMOS to microbolometer explains that difference. When the crash instead names the padding field itself, the nearest cousin case is GenTL buffer padding_y not implemented: fix the fetch crash.
Likely causes, ranked by probability
- The producer reports transport-layer padding in the height. The explanation offered on the producer side for this exact case: the image carries padding lines at the bottom to satisfy a transport-layer requirement. The metadata describes them; the payload, at 153,600 bytes, does not contain them.
- The consumer trusts that metadata blindly. Building an array from the reported component height without checking delivered bytes turns padding into a hard failure. This is a code defect with a producer trigger, and the fix below removes it permanently.
- A real geometry change. If the delivered byte count grows with the reported height, rows genuinely arrive: an ROI node moved, the pixel format changed, or the payload now carries several components. The extra height is then image data, not something to clamp away.
One line of arithmetic separates them — the byte test in the next section.
| Reported height | Delivered bytes | Verdict | What to do |
|---|---|---|---|
| Node map Height | Node map math | Clean | process as usual |
| Node map Height + padding | Node map math | Metadata-only padding | clamp to Height, slice, keep streaming |
| Taller | Taller by the same lines | Real geometry change | re-read Width, Height, PixelFormat |
| Taller | Flat, but not node map math | Stale node read or row pitch | re-read after start; run the test per axis |
Worked example: a 320 × 240 Mono16 buffer that reports 246 lines
Inputs, and where each comes from:
- Width = 320 px and Height = 240 lines — from the camera's node map, written once by the acquisition code
- PixelFormat = Mono16, so 2 bytes per pixel — the same node map
- Reported buffer height from frame 1 on: 246 lines — read from the fetched buffer's component
- Delivered payload size: 153,600 bytes, unchanged across frames — read from the buffer
The rule in words: one line is Width pixels, each bytes-per-pixel wide. Multiply to get the bytes a real image needs, then divide what actually arrived by the bytes per line to get the lines that exist.
Arithmetic:
- 320 px × 2 B/px = 640 B per line
- 640 B × 240 lines = 153,600 B — exactly what every buffer delivered
- 640 B × 246 lines = 157,440 B — what the consumer demanded on the strength of the metadata
- 157,440 B − 153,600 B = 3,840 B = 6 × 640 B — six lines of description for bytes that are not there
Result: 153,600 B ÷ 640 B = 240 lines delivered. The metadata's 246 describes rows the buffer does not contain.
Decision: when delivered bytes ÷ bytes per line equals the node map Height, the surplus height is metadata padding — size from the node map, clamp, keep streaming. When it does not equal it, stop and reconcile before reshaping: the geometry really moved.
The fix: size the array from the node map, then slice
- Start the stream, then read Width, Height and PixelFormat from the remote device's node map once — these are GenICam standard feature names — and compute the expected byte count from them. Here: 320 × 240 × 2 = 153,600 bytes.
- On every buffer, read the delivered byte count and compare it with that expected count. Equality means the picture is complete, whatever the metadata claims.
- Clamp the reported rows to the node map Height before reshaping, and take the first Height × Width values of the flat data. Rows beyond Height are padding, whether they exist on the wire or only in the description.
- Copy those values out before the buffer returns to the producer's queue — it is already being recycled; see fetch buffer queue: back to the producer and copy data first.
- If the delivered count does not match, do not reshape. Re-read the three nodes and judge the frame under the new geometry.
Code, in the shape the failing consumer used:
n_frames = 60 # int, frames to acquire
width = 320 # int, from remote_device.node_map.Width.value
height = 240 # int, from remote_device.node_map.Height.value
bytes_per_pixel = 2 # int, Mono16 is 16 bits
expected_bytes = width * height * bytes_per_pixel # 320 * 240 * 2 = 153600
for _ in range(n_frames):
with ia.fetch_buffer() as buffer:
component = buffer.payload.components[0]
delivered_bytes = buffer_size(buffer) # int, the buffer's byte count
rows = min(component.height, height) # 246 reported -> 240 kept
frame = component.data[: rows * width].reshape(rows, width)
assert delivered_bytes == expected_bytes # 153600: geometry intact
assert frame.shape == (240, 320) # image rows, not padding
buffer_size stands in for your consumer's buffer-size accessor; the fetch and payload-component calls are shown as the failing consumer used them, and attribute names differ between libraries. With the clamp in place the assertions hold: the metadata says 246, the array comes out 240 × 320, and nothing asks for 157,440 bytes. Remove the clamp and you are back to the original crash.
When the extra height is a real geometry change
If the delivered bytes grow with the metadata, the rows are real, and three things produce them:
- An ROI or test-image node moved. Re-read Width and Height; the new values are the truth, and the byte arithmetic should match them again.
- The pixel format changed, which rescales the same arithmetic at the bytes-per-pixel factor. Re-read PixelFormat before reshaping.
- The payload now carries several components, for example depth plus intensity. Size each component from its own description; check multi channel image buffer size before processing walks that count.
The clamp-and-slice pattern still applies once nodes and bytes agree: take the first Height rows of a row-major, zero-offset image. If the width comes back padded too, run the arithmetic per axis and stride through the lines — Width pixels from the start of each line, pitch skipped. Where your node map comes from matters here as well; GenTL node maps: when only the remote device has an XML covers the case where no local description file exists to cross-check against. And if the camera is the thermal model of the worked example, its 16-bit values need their own handling once the geometry is right — see export high bitdepth grayscale images from thermal cameras.
Prevention: pin the geometry, test the bytes, update the producer
- Read Width, Height and PixelFormat after acquisition starts, not only at start-up, and recompute the expected byte count from what they say then.
- Assert delivered bytes against expected bytes on every frame of a test run; in production, log a warning once per session with both values.
- Install the camera vendor's own GenTL producer before keeping a workaround. On this case's camera the vendor publishes one in its own SDK; if its buffers pass the byte test, retire the clamp rather than carrying it.
- Report the mismatch to the producer's maintainer with the four numbers — width, height, delivered bytes, reported height. That report is what identified the padding in the original case.
- Re-run the byte test after any producer, firmware or pixel-format change; the padding policy travels with the producer, not with the camera.
Which fix applies to your case
- Delivered bytes match the node map math while the metadata reads taller — the 240-versus-246 case: clamp to Height, slice, keep streaming. Nothing is wrong with the camera or the cable.
- Delivered bytes and metadata grow together: the geometry really changed. Re-read the three nodes and adopt the new values.
- Delivered bytes match neither: your node read is stale or the rows carry pitch. Re-read after start and run the arithmetic on the width axis as well.
- Two producers disagree on the same camera: the byte test referees. Use the producer whose numbers survive it and report the one that fails.
Where to go next
If the mismatch follows you into hardware you are choosing rather than fixing, geometry you can trust is part of what you buy: area scan cameras across GigE, USB3, 10GigE and CoaXPress, infrared cameras for the thermal class in the worked example, or the camera finder filtered by interface and resolution. The camera configurator steps through camera, lens and cable as one chain before you order. For a producer-behavior question this page does not settle, contact us with the four numbers from the byte test — they usually settle it quickly.
Questions
Why does the first frame report the correct height and later frames do not?
The best-supported explanation for this case: the first buffer is described from the node map geometry, and once streaming is under way later buffers carry the transport layer's own description, padding lines included. Nothing you set changed between frame 0 and frame 1 — Width, Height and PixelFormat still read 320, 240 and Mono16. Only the buffer's description moved, which is why the node map wins whenever the delivered byte count agrees with it.
Are the six extra lines real pixels I should keep?
No — in this case they never existed. Each buffer delivered 153,600 bytes, exactly 320 × 240 × 2, so there is nothing beyond line 240 to save; the 246 was description only. When a producer genuinely pads, the delivered count grows by the same amount, 3,840 bytes for six lines here, and the padded rows sit below the last image line. Either way, the first Height rows of a row-major, zero-offset frame are the picture.
Does this happen on GigE Vision and USB3 Vision cameras alike?
The interface is not the variable. A GigE Vision device and a USB3 Vision device both reach your code through a GenTL producer, and it is the producer that writes the height metadata. The fault report here covers the same camera behind two different third-party producers while the payload stayed at 153,600 bytes throughout. Whatever the transport, judge a producer by one test: delivered bytes divided by bytes per line against the node map Height.
Should I update the GenTL producer or keep the clamp?
Update first, keep the check either way. The camera vendor in this case publishes its own producer inside its SDK, so the third-party pair is not your only option; if its buffers pass the byte test, retire the clamp. Do not remove the delivered-bytes comparison, though: it costs one integer check per frame, and if a future producer or firmware revision reintroduces padded metadata, you get a logged warning instead of a crash mid-run.
Can the width carry padding too?
Yes, and the same arithmetic catches it. Bytes per line is what a producer must account for; if a buffer's delivered bytes per line exceed width × bytes per pixel, the surplus is row pitch. Reshape with the stride instead of the bare width: take Width pixels from the start of each line and skip the rest. On the 320-pixel case above the width never moved, but the test runs in one line either way.