Your GenTL consumer just stopped with KeyError: 'BayerRG8' inside a pixel-format lookup keyed by integer. The property was declared to return an int, and it handed back the format's symbolic name instead. This guide is for engineers bringing up an area scan camera through a GenTL producer and a Python consumer. You will see why one format has two representations, which source returns which type, how to make the lookup accept both, and why the follow-up failure — BUFFER_INFO_WIDTH unavailable on a PAYLOAD_TYPE_CHUNK_DATA buffer — is the real root cause. You can then classify your own failure in one pass and apply the right fix.
The symptom: a type mismatch, not a bad format
The traceback ends in a lookup shaped like dict_by_ints[self.data_format_value], and the key in the exception is the format name — BayerRG8 — not a number. Nothing is wrong with the format. BayerRG8 is a standard 8-bit-per-sample Bayer arrangement, one byte per pixel, and normal output from a color sensor. The failure is the key type: the table is keyed by the format's registered integer, the value that arrived is the format's name, and Python does not coerce between them. An integer-keyed table raises KeyError on "BayerRG8" even though the format is valid. The reverse pairing — an integer pushed through a name-keyed table — fails the same way.
Two representations, two sources
Every pixel format carries two standard identifiers: a symbolic name and a registered integer, paired one to one in the pixel-format naming registry. Your code meets them through different sources, and the source decides the type:
| Source | Type you get | Content |
|---|---|---|
Device node map, PixelFormat .value
|
string | "BayerRG8" |
| Buffer pixel-format info on an image payload | int | the registered integer for BayerRG8
|
dict_by_names[name] |
int | name to registered integer |
dict_by_ints[integer] |
string | registered integer to name |
A GenICam enumeration holds its current value as a symbolic entry, so a node-map read answers with the name in most bindings; the integer sits behind the enumeration's integer value. That is the same numeric-to-symbolic pairing you handle when you set a GenICam enumeration by integer value. Buffer-side queries return the registered integer. Neither source is wrong — they follow different conventions, and your lookup must tolerate both.
Why the fallback returns a string
Consumers read the format through a chain: from the buffer part when the payload is multi-part, else from the buffer's pixel-format info, and only when those raise do they fall back to the remote device node map's PixelFormat. The fallback exists because buffer queries can legitimately fail — which is what happened here. The buffer query raised, the handler caught the GenericException, and the node map answered "BayerRG8". The integer-keyed table then raised the KeyError you saw. The string is not the camera breaking the standard: it is the expected behavior of the fallback path meeting an int-only table.
Size the payload with the format name
Once the payload is an image, the name alone tells you the byte depth, which gives you a check that needs no registry:
Worked example with a 256 × 16 test ROI on a camera set to BayerRG8:
- width 256 px, height 16 px, bytes per pixel 1 — the trailing
8in BayerRG8 is 8 bits - payload = 256 × 16 × 1 = 4,096 bytes
After the fix, len(component.data) must equal 4,096 for that ROI. If it does not, buffer and format disagree, and you have a buffer size versus pixel format mismatch rather than a lookup problem. Depth from the name suffix: Mono8 and BayerRG8 are 1 byte per pixel, Mono16 is 2, RGB8 is 3. For multi-component images, check the buffer size before processing with the same multiplication.
The resolver: accept both types
Fix the lookup once, in one function, and route every format read through it:
# Illustrative variable: fmt (int or str). Documented names:
# dict_by_ints, dict_by_names, PixelFormat.
def resolve_format(fmt):
"""Return the symbolic PixelFormat name from int or str input."""
if isinstance(fmt, str):
return fmt # e.g. fmt = "BayerRG8"
if isinstance(fmt, int):
return dict_by_ints[fmt] # registered integer -> name
raise TypeError("PixelFormat must be int or str, got %r" % (fmt,))
# Test: both input types resolve to the same name.
fmt_name = "BayerRG8" # str: what the node map returned
fmt_int = dict_by_names[fmt_name] # int: the registered integer
assert isinstance(fmt_int, int)
assert resolve_format(fmt_name) == "BayerRG8"
assert resolve_format(fmt_int) == "BayerRG8"
Two decisions are baked in. First, never cast with int(fmt): on a name it raises ValueError, the same crash one step later. Second, do not patch the library line in place — the value will reach you from both sources again, so the tolerant resolver is the durable fix. This lookup path was corrected in the consumer library after this failure was reported; a current release resolves both input types without local edits.
The second error is the real cause
Patch the lookup and the same set-up raised the next failure: the width query threw a NotAvailableException reporting that BUFFER_INFO_WIDTH is not available because the buffer's payload type is PAYLOAD_TYPE_CHUNK_DATA. That message is the root cause of the whole chain. GenTL payloads are typed, and a chunk-data payload carries metadata blocks only. Width, height and pixel format describe image payloads; on a chunk-only buffer those queries correctly answer not-available instead of inventing values.
Read the chain backwards and both symptoms collapse into one cause:
- The buffer's pixel-format query raised, because the payload was chunk-only.
- The consumer fell back to the node map and received the name string.
- The int-keyed table raised
KeyError: 'BayerRG8'. - With the lookup patched, the width query failed with the payload-type message, exposing step 1.
Fixes, best first:
- Turn chunk mode off on the remote device — the chunk enable feature sits in the device node map — so the producer marks payloads as image type and the image entries come back. Then repeat the byte-count check.
- If you need chunk data and images in the same stream, use a producer that packages both, as multi-part or chunk-with-image payloads. A plain chunk payload has no image component, so no consumer-side patch will produce pixels that were never transmitted.
- Update the producer along with the consumer. A producer that reports chunk-only payloads while the camera streams images misreports the payload; that belongs in the vendor's GenTL stack, and it changes independently of your code.
Chunks that arrive but parse empty are a different split — empty chunk data from a GigE camera isolates the device side from the host side.
Verify the fix, in order
- Print the payload type of one fetched buffer before touching width or format. A camera streaming images should report the image payload type; chunk-only here means the fix belongs on the device or producer side.
- Read
PixelFormatfrom the node map and confirm the name is in the feature's entry list. A format absent from the list cannot be set either — why camera pixel formats are fixed and how to set them covers that list. - Run the resolver asserts with a name and an integer.
- Fetch one buffer and compare
len(component.data)with width × height × bytes per pixel — 4,096 bytes for the 256 × 16 BayerRG8 ROI. - Select a different format from the entry list and repeat step 4; the byte count must track the new depth. If your capture API quietly degrades the format instead, request Bayer rather than let it fall back to mono8.
When you set a new format, check the allowed symbolic entries first — GenICam enumeration invalid values and the symbolic check is the mirror image of this read path.
When the usual answer is wrong
- Payload type is image and you still see a string: you are reading the node map directly. That is expected; use the resolver and stop treating it as a fault.
- The value is an integer and the table still raises KeyError: the device exposes a custom, non-registered format. Read the entry list, derive byte depth from the name, and size buffers by multiplication rather than the registry.
- A standard name raises in
dict_by_names: your consumer's tables predate that format. Update the library; patching the dict only hides the staleness until the next new format ships.
What to keep in the codebase
- One
resolve_formatfunction as the single path for every format read, unit-tested with both input types. - A payload-type guard in front of the width, height and pixel-format queries, so chunk-only buffers return a clean state instead of an exception mid-pipeline.
- A byte-count assert after the first fetch on every new camera: width × height × bytes per pixel.
- A pinned consumer-library version, re-run through the five steps above after any driver update. The producer installs with the interface card, so a card swap silently replaces your GenTL layer; the GenTL producer GigE Vision compatibility check is the first thing to re-run.
The producer ships with your interface hardware
The .cti you load belongs to the interface, not the camera: interface cards install their GenTL producers with the driver, which is why producer quirks change when the card changes. Start from the frame grabbers category or the interface finder, and keep the camera data cables matched to the connectors on both ends while you are in there. A dual-port PoE NIC such as the MV-GE2002P brings its own producer for GigE Vision devices:
- Interface
- GigE
- Ports
- 2 ports
For high-throughput and fiber links:
- Interface
- 10GigE
- Ports
- 4 ports
- Interface
- XoFLink
- Ports
- 4 ports
The MV-GT2104 is a quad-port copper 10GbE NIC, and the MV-GS1104F-DF adds an SFP+ slot with 40G total bandwidth. On the device side, a color camera such as the MV-CS200-10GC delivers exactly the Bayer mosaic the byte-count example above assumes:
- Sensor format
- 1"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 16-25 MP
Once the payload reads image and the resolver asserts pass, run the five steps end to end. If the producer still labels an image stream PAYLOAD_TYPE_CHUNK_DATA, capture the payload-type readout and both exception messages and send them through the contact page — we support what we sell. To assemble the rest of the chain, start at the system configurator.
Questions
Is the camera non-compliant when PixelFormat comes back as a string?
No. The GenICam feature model gives an enumeration both a current symbolic value and a registered integer, and a node-map read of PixelFormat legitimately answers with the symbolic name such as BayerRG8 in most language bindings. Non-compliance would look different: a producer reporting an image payload without width or pixel-format info, or chunk-only payloads while the camera streams images. Treat the string as valid input and resolve it through the by-name table.
Why does BUFFER_INFO_WIDTH fail with a PAYLOAD_TYPE_CHUNK_DATA buffer?
Because those entries describe image payloads, and a chunk-data payload has no image in it. The buffer carries metadata blocks that become readable through the device's chunk features after parsing; width and pixel format simply do not exist for it, so the producer answers not-available, which is the honest reply. Restore an image payload by switching chunk mode off on the device, or stream image and chunks through a producer that packages both.
How do I convert between BayerRG8 and its registered integer?
Through the table pair, never through casting: dict_by_names maps the name to its registered integer and dict_by_ints maps it back, one-to-one by registry definition. If a table is missing, rebuild the pairs from the enumeration's entry list in the node map, which lists every allowed name together with the feature's integer representation. For buffer sizing you often do not need the integer at all — the name's suffix gives the byte depth.
What if the format name is in neither lookup table?
Treat it as a custom, non-registered format: read the entry list so you know every allowed name, compute byte depth from the layout suffix, and allocate width times height times depth without touching the registry. Log the raw name with every frame, because custom formats travel with that device only. If a standard name such as Mono8 also misses the table, your consumer is outdated — update the library rather than extending the dict.
Do I hit the same problem when setting PixelFormat instead of reading it?
Yes, in mirror image. When you write, enumerate the feature's allowed symbolic values and assign a name from that list; assigning a raw integer only works after you map it to the symbolic entry the device knows, using the same table pair in reverse. Check the allowed entries before every set, because a name another camera accepted may not exist on this one.



