fetch_buffer() dies with NotImplementedException, GC_ERR_NOT_IMPLEMENTED (ID: -1003), and the traceback stops at a line adding padding_y to a byte count. The camera itself looks fine: the vendor tool shows live frames. The fault sits one layer down, between the GenTL producer that delivered the buffer and the acquisition library that turns it into an image array. This guide decodes the error, shows why frames can arrive while conversion still crashes, gives the stride math that rebuilds the image — 1920 bytes per row and 2,304,000 bytes total for the 1920 × 1200 Mono8 case — and ends with the decision: which side to update, and what to change in your reshape code today.
What the error is telling you
Read the exception literally. GC_ERR_NOT_IMPLEMENTED (ID: -1003) is the GenTL status for a query this producer never implemented — the message from the source says "I do not offer that", not "the camera failed". The traceback shows exactly where it fires: inside the library's array-conversion step (_to_np_array), at the line nr_bytes += self._buffer.padding_y. Delivery has already succeeded by then. Three facts follow:
- Frames are arriving. The buffer was delivered and queued before anything crashed.
- The query for the end-of-buffer padding value was refused by the producer.
- Harvester 1.2.3 reads that value unconditionally while sizing the NumPy array, so the refusal becomes a hard exception instead of a zero.
The camera working in the vendor tool confirms the split: sensor and transport are healthy, and the gap is one optional query. For contrast, a delivery problem looks different — the call hangs, then times out; that path is covered in Frame didn't arrive within timeout: one wait call per frame. And if the producer never loads at all, you are fighting a path problem, not a buffer one — start with GenTL XML file location errors: diagnose camera description file paths.
Why a producer may skip the padding query
Two padding values surround a GenTL image buffer. Per-row padding is extra bytes appended after each line so rows land on an alignment boundary; end-of-buffer padding (padding_y in the traceback) is extra bytes after the last row. A producer may implement either, both, or neither — and when a consumer asks for one that is missing, the designed answer is exactly the status you saw. Well-behaved consumers treat "not implemented" as "assume zero", which is what the library fix does.
The failure feels sudden because a working stack hides the gap until something moves:
- An acquisition-library update that newly reads the query. That is the 1.2.3 pattern: older releases never asked, so the same producer "worked".
- A producer or driver package update on the camera or frame-grabber side.
- A pixel-format change that routes the frame through a different buffer path in the producer.
Log the pixel format at every session start; a silent switch to Mono8 changes the byte count as well, and that trap has its own write-up: Camera image encoding falls back to Mono8: request Bayer instead. The gap is not unique to one pairing either — producer reports describe buffer-information queries refused outright, or the same not-implemented status raised on every fetch, across different camera families. More GenTL bring-up faults, from discovery to XML paths, sit on the systems topic page.
Fix it: update one side
Decision first: update the acquisition library. The maintainers added a fallback for producers that refuse the padding query — in the reported case it went in on branch _issue_172 and was then merged to master — so any release after that merge converts the refusal into the zero-padding assumption for you. On the case's Python 3.7 / Windows 10 setup that is one command: pip install -U harvesters. Pin the version you validated afterwards.
If the library cannot move — a validated production line, a frozen environment — update the other side: the producer. The camera or frame-grabber vendor ships its GenTL producer inside its SDK or driver package, and a newer package may answer the query. Get it from the vendor or its distributor, never from a copy found elsewhere. If your frames come through a frame grabber, the producer travels with that card's driver, so the card vendor is the address.
If neither side can move, compute the stride yourself; the next two sections give the math and the worked case.
Recover the frame with stride math
The reshape you can defend in code review sizes the array from the geometry you know and treats both paddings as zero bytes when their queries are refused:
Here width and height are in pixels and rows, and every padding term is in bytes. Bytes per pixel for the common unpacked formats: Mono8 and the 8-bit Bayer variants carry 1 byte per pixel, Mono16 carries 2, RGB8 carries 3, YUV422 carries 2. Packed formats need the bit count: bytes per row = width × bits per pixel ÷ 8, rounded up. And remember the bytes you receive are not pixels until the layout is decoded — that distinction is the subject of Image bytes vs pixel data: decode before processing.
The code, with the test that proves the result:
# Documented names, from the payload component: component.width,
# component.height and component.data, as used in the traceback.
# The remaining variables are ours: ints, initial values shown.
width_px: int = int(component.width) # 1920 in the worked case
height_px: int = int(component.height) # 1200
bytes_per_px: int = 1 # Mono8
row_padding: int = 0 # query refused -> assume 0
end_padding: int = 0 # query refused -> assume 0
row_stride = width_px * bytes_per_px + row_padding # 1920 bytes
frame_bytes = height_px * row_stride + end_padding # 2304000 bytes
assert len(component.data) >= frame_bytes # delivered count backs it
img = component.data.reshape(height_px, row_stride)[:, :width_px]
assert img.shape == (1200, 1920)
assert img.nbytes == 2304000 # 1200 * 1920 * 1
With both paddings genuinely zero this is the same reshape as component.data.reshape(component.height, component.width) — the assert is what makes the assumption visible. Hand the result to functions that need contiguous memory through numpy.ascontiguousarray(img).
Worked example: a 1920 × 1200 Mono8 frame
Inputs, with their sources: width 1920 px and height 1200 px come from the frame the code reshapes in the reported case; Mono8 gives 1 byte per pixel — a 2-D reshape into height × width only lands on that value for the delivered count, and Mono8 is what these stacks commonly negotiate; both padding values are unknown, because the query is the thing being refused, so they enter as 0 bytes.
Rule, in words: multiply width by bytes per pixel for the row stride, multiply by rows for the frame size, then compare against the delivered count. Arithmetic:
- Row stride: 1920 px × 1 B/px = 1920 B.
- Frame size: 1200 rows × 1920 B = 2,304,000 B, about 2.2 MiB (2,304,000 ÷ 1,048,576).
- Test: the delivered count must equal 2,304,000 B. If it came back 2,313,600 B instead, the real stride is 2,313,600 ÷ 1200 = 1928 B, meaning 8 B of per-row padding the producer never reported — reshape to (1200, 1928) and slice
[:, :1920].
Decision: a refused query means reshape straight to (1200, 1920) when delivered bytes equal 2,304,000. Any excess means the hardware pads after all, so divide the delivered count by the row count and slice. Never add an unreported padding to the byte count before reshaping — that is the original failure mode, just with your guess in place of the producer's answer.
When the image comes out sheared
The visual signature of a stride mismatch is a diagonal tear: each row lands one padding-width further out of place than the row above. Reshape with 1920 bytes per row when delivery actually carries 1928, and row 100 is 800 px (100 × 8) off. The count check in the worked example catches this before you ever see the tear, which is why it belongs in code rather than in your eyes.
If a shear does slip through, the repair takes one line of arithmetic: take the delivered byte count, divide by the row count, reshape with that stride, slice off the padding columns, and wrap with numpy.ascontiguousarray.
Which side to fix: the decision table
| Symptom | Test | Action |
|---|---|---|
Vendor tool shows frames; traceback dies at padding_y
|
Query refused, delivery fine | Update the acquisition library; its fallback assumes 0 |
| Library pinned, newer producer offered | Version list from the vendor | Update the producer package from the vendor or distributor |
| Both sides pinned | Delivered count vs computed count | Stride fallback in your reshape with the byte-count assert |
| Sheared image after the fallback | Delivered ÷ rows vs your stride | Reshape with the real stride and slice the padding off |
| Call hangs, then times out | No delivery at all | Treat as a delivery fault, not a padding fault |
Checks before you redeploy
Each item earns its place by preventing a repeat:
- Pin both versions — the acquisition library and the producer package. "Worked yesterday" only means something when you can name yesterday's versions.
- Log the pixel format at session start. The stride is a function of it, and silent fallbacks change it.
- Assert delivered bytes against computed bytes once per session, on the first frame. One comparison catches format switches and hidden padding for the whole run.
- Query optional buffer information once, not per frame. If refused, lock in the zero assumption for the session.
- Allocate image buffers once per session instead of per frame; per-frame allocation is its own slowdown, covered in Reuse image buffers instead of per-frame allocation.
- Wrap camera teardown in a finally block so a mid-run crash cannot leak device handles — the pattern is in Camera handle cleanup after Python script errors.
- After any update on either side, run the reshape asserts against one real frame before trusting the stream.
When the capture side is the suspect
If several producers on the same machine refuse basic queries, or you are specifying hardware for a rebuild anyway, keep the bug in proportion: it is not hardware-specific, because any producer can lag on an optional query. What shortens the fix cycle is current, vendor-shipped driver packages and a consumer that tolerates refusals.
For a rebuild, size the camera side with the camera finder, and if the plan includes a capture card, start the configurator at the extras step. The two categories below cover the capture path end to end.
GigE, USB3, 10GigE and CoaXPress
1,287 models · from $149.00CoaXPress, Camera Link and GigE cards
58 models · from $189.00If a traceback still refuses to make sense, contact us; expert support is free with every order.
Questions
Does GC_ERR_NOT_IMPLEMENTED mean the camera is broken?
No. The status code says the producer refused one optional query, not that the sensor failed. If the vendor tool shows live frames and the traceback dies after delivery, the camera and the transport are healthy. Treat it as a version mismatch between the acquisition library and the producer, and close it by updating one of the two sides.
What is the difference between per-row and end-of-buffer padding?
Per-row padding is extra bytes appended after each image line so every row starts on an alignment boundary. End-of-buffer padding is extra bytes after the last row. Producers are not obliged to report either, which is why a consumer must handle the refusal. Assume 0 bytes, then prove the assumption by comparing your computed frame size against the delivered byte count.
How do I size a row for a packed 10- or 12-bit format?
Multiply width by bits per pixel and divide by 8: Mono10p packs 10 bits per pixel, so a 1920 px row is 1920 × 10 ÷ 8 = 2400 bytes and a 1200-row frame is 2,880,000 bytes; Mono12p gives 2880 bytes per row, 3,456,000 bytes per frame. Round up per row, then check the delivered count before reshaping.
Why did fetch_buffer work yesterday and fail today?
Something in the software stack moved, not the camera. The usual triggers are an acquisition-library update that newly reads the padding query, a producer or driver package update, or a pixel-format change that routes frames through a different buffer path. Pin the working pair of versions, and test any update against one real frame before the line sees it.
Is this failure specific to Python?
No. The refused query belongs to the GenTL interface every producer exposes, so a C++ or C# consumer that reads padding hits the same refusal. Python surfaces it earlier because building a NumPy array demands an exact byte count up front. The fix is identical everywhere: tolerate the refusal, assume zero padding, and check the delivered byte count.