You hand add_cti_file a path that does not exist, and the call comes back without a whisper of an error. That is not a bug in your script, and it is not a bug in the library either: GenTL consumers deliberately record any path you give them and defer every check to the enumeration step. This guide shows where the failure actually surfaces, how to validate a CTI file before you add it, and how to read len(device_info_list) after update() so a bad path or a dead producer costs you seconds instead of an afternoon.
Why the call never raises
In the GenICam GenTL model your acquisition program is the consumer, and every transport ships a producer library — the .cti file — that knows how to drive that kind of camera. add_cti_file, and its alias add_file, do exactly one thing: they append your path string to the consumer's list. No disk access, no library load, no validation. The work happens in update(): the consumer loads each registered producer, asks it to enumerate the devices it can see, and collects the answers in device_info_list.
That separation is deliberate. Blocking enumeration on every unusual path would break setups that add files speculatively, so the contract is: add anything, then judge by the device count. len(device_info_list) returning 0 after update() is the failure signal for all of it — wrong path, unloadable library, unreachable camera.
The gap was closed on request with two keyword arguments, check_existence and check_validity, accepted by add_cti_file and add_file. They default to False so older scripts keep the behavior they were written against; pass them as True and a missing or unusable file raises at add time.
The three failure layers behind the silence
When no exception comes and the count reads 0, exactly one of three layers failed. Ordered by how often each turns out to be the cause:
- Path layer — most likely. The string points at nothing: a typo, a file moved by an SDK update, a relative path resolved against a different working directory, or a plain Python string whose backslash sequence ate a character. The GenTL CTI file path errors: check Python escaping article covers the string side in detail.
-
Producer layer. The file exists but the library never comes up: a 32-bit producer in a 64-bit process, a missing runtime dependency beside it, or a DLL that is not a GenTL producer at all — the GenTL producer not listed: check Windows DLL paths article is the reference. Bitness announces itself loudly when you import a library directly, as the Python wrapper ImportError DLL load failed: fix camera SDK bitness fix shows; inside
update()the same fault stays quiet. A producer that loads but exits on a call your consumer makes has its own walkthrough under GenTL producer unimplemented functions: fix the silent exit. - Device layer. The producer loads, runs and finds nothing to report: camera unpowered, link down, camera parked on a subnet the NIC cannot reach, or the transport's driver stack missing on the host.
Every layer produces the same observation — update() returns normally and the count reads 0 — which is why you test them in this order, cheapest first.
| Layer | Observation | Test that separates it | Typical fix |
|---|---|---|---|
| Path | add is silent, count 0 |
os.path.isfile(path) before adding |
absolute path, raw string, correct folder |
| Producer | count 0 with the file present | one CTI alone, then update and count | match bitness, restore the missing runtime, use the SDK-shipped CTI |
| Device | count below expected once the CTI is proven | one known camera connected, recount | power, link, subnet, driver |
Worked example: one missing path on a Windows 10 bench
The classic case runs Windows 10, Python 3.8 and a 1.0.4-era GenTL consumer, with a call that passes a filename that does not exist and a single GigE camera expected on the NIC. The rule to apply: add_cti_file only records, update() loads, and the device count is the verdict.
import os
from harvesters.core import Harvester
path = r"C:\genlt_producers\gev_producer.cti" # this file does not exist
print(os.path.isfile(path)) # False: stop before adding
h = Harvester()
h.add_cti_file(path) # accepted without a sound
h.update()
print(len(h.device_info_list)) # 0: nothing contributed
On a current release the fail-fast form puts the same verdict at the add:
h.add_cti_file(path, check_existence=True, check_validity=True) # raises: file missing
On the 1.0.4-era build that line raises TypeError instead, because the parameters do not exist yet — one more reason the isfile line belongs in every version of the script.
expected = 1 (one GigE camera reachable on the NIC — from the bench)
observed = 0 (printed by the probe: len(device_info_list))
verdict = 0 < 1 (the add/update chain produced nothing)
bisect = 3 CTIs registered -> at most 3 fresh add/update cycles
On a bench with three CTIs registered — a GEV producer, a frame grabber's producer and a leftover from a removed camera — the bisect is one fresh Harvester instance per file, one add and one update each. The file whose solo run returns 0 is the one to fix. Here the isfile line answers in milliseconds: False proves the path layer failed before any library was touched.
The decision, with its threshold: correct the string to the SDK's install folder and re-run; the probe prints True, then a count of 1. observed == expected is clean. observed == 0 indicts the path or the producer layer. Anything in between means part of the device side is missing. Fix the path first — it is the cheapest layer to test and the most common fault.
The pattern to adopt: check at add, count at update
Turn the diagnosis into a startup habit:
- Keep every producer path in one list of absolute strings, next to the expected camera count.
- Test each path with
os.path.isfileand log every miss; on a release that supports the arguments, passcheck_existence=True, check_validity=Trueas well. - Call
update()once, after all adds — enumeration walks the whole list. - Compare
len(device_info_list)against the expected count and log both numbers; any shortfall is a fault, not only zero. - On a shortfall, bisect: a fresh Harvester instance per CTI until the count localizes the bad file.
On a dedicated vision controller this check belongs in the boot script, before the first inspection cycle of the shift:
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
The MV-VC3301H-128G66 is a vision controller with PCIe expansion support, so the same consumer code that runs on your bench PC runs at the line, printing the count into the boot log.
Zero devices with a healthy CTI: the device layer
Once isfile passes and a solo run still returns 0, the producer came up and found no camera to report. Enumerators list only devices they can actually reach, so the suspects move to the wire: camera power, link LEDs, the subnet the NIC sits on, and the host's driver stack for the transport.
GigE Vision producers are the common case. A producer binds the NICs whose subnets match its cameras, so a device parked on a foreign subnet stays invisible no matter how correct the CTI is — the Access GigE cameras from another subnet without direct cabling walkthrough shows how to reach one or re-home it, and the 3D camera not pingable on a Linux controller: fix subnet and link fix covers the same symptom from the host side. When the camera sits on the right subnet and still fails to appear, check what the producer itself supports: the GenTL producer GigE Vision support: check compatibility article lists what a producer must offer before your device shows up. A firewall rule that drops discovery replies produces the identical silence.
Match the CTI to the transport you wired
Every transport family ships its own producer: a GEV producer for GigE and 10GigE cameras, the frame grabber's CTI for CoaXPress and Camera Link chains, a USB3 producer for USB3 Vision cameras. The file you add must describe the transport the camera is actually on — a perfect GEV producer never sees a camera hanging off a CXP card.
For CoaXPress the producer lives in the card's own software package, because the card, not the camera, is what the library drives:
- Interface
- CoaXPress
The MV-GX1008 is an octo CXP-6 frame grabber; add the CTI from its driver package and the card enumerates whatever sits on its ports.
On an all-GigE bench the host side can be the motherboard NIC, or a dedicated card that keeps the camera streams off shared bandwidth:
- Interface
- GigE
- Ports
- 4 ports
- Interface
- GigE
- Ports
- 2 ports
The MV-GE1104 is a 4-port GigE frame grabber for benches that want the streams on their own silicon; the MV-GE2002P is a dual-port PoE NIC, so camera power rides the Ethernet cable and the GEV producer enumerates through it as usual.
When the chain is right the camera finally shows up as itself — on a typical bench a 2.5 MP GigE area scan camera such as the MV-CU020-90GC:
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- Up to 3 MP
Each camera contributes its own entry, listed with vendor, model and serial text; seeing the serial you stuck on the instrument confirms that all three layers are clean.
Checks before you blame your code
Run this list once per station and the silent-failure family disappears from your logs:
- The path is absolute and passed as a raw string; escape sequences and mixed separators only survive when the string is raw.
- The file is the CTI the SDK installed, or a deployment copy whose path is written down; refresh the copy at every SDK update.
- The consumer process and the producer bitness match — a 64-bit Python loading a 64-bit producer.
- One producer per transport, and no duplicated path in the list.
- The expected camera count sits next to the add calls, and the boot log prints expected and observed side by side.
- A solo add and update per CTI localizes any shortfall in under a minute.
To size the host side of a new cell, start at frame grabbers or the accessories finder, both sorted by interface and port count, and let the system configurator assemble the card into a full chain. Camera choices begin at the camera finder, and controllers that run this startup check unattended live under vision controllers. The GenTL family continues on the systems topic page, and we help with specific set-ups through the contact page.
Questions
Are check_existence and check_validity available in every release?
No. They were added in response to reports that a missing path passed silently, and at introduction they defaulted to False so existing scripts kept the same behavior. On a 1.0.4-era build the parameters do not exist, and passing them raises TypeError. On a current release pass check_existence=True and check_validity=True explicitly, and keep the os.path.isfile test for anything older.
Is a partial device count also a failure?
Yes. The count is only clean when it equals the number of cameras the producer can reach. On a subnet with five powered cameras, four entries still means one camera is down, duplicated on the network or filtered, even though the CTI itself is perfect. Compare observed against expected rather than against zero; zero only tells you the fault sits in the path or the producer layer.
Do I need one CTI file per camera?
No. A GenTL producer is written per transport, not per camera: one GEV producer enumerates every GigE camera it can reach on the host's subnets, and a frame grabber's CTI enumerates whatever sits on its ports. A handful of files covers a whole cell, and the expected count is simply your camera count. Registering the same file twice gains nothing but confusion.
Can I place a copy of the producer library next to my program?
Yes. Producer libraries are found through the system path or from a folder beside the application, which makes deployment simple. The trap is staleness: after an SDK update, a forgotten copy in an old folder shadows the current producer and enumerates nothing, or half of last year's devices. Keep one deployment copy per program, record its path in your configuration, and refresh it with every SDK update.
Does update() raise when a producer fails to load?
Usually not. A library that cannot come up — wrong bitness, a missing dependency, a file that is not a GenTL producer at all — normally contributes zero devices, and update() returns without a sound. The loud import error you know from loading a library in your own code stays quiet inside enumeration. Treat any shortfall against the expected count as a load failure until a solo run proves otherwise.




