System designTroubleshooting

TLParamsLocked node not existing when a legacy camera starts

Why start_acquisition raises AttributeError with TLParamsLocked node not existing on legacy cameras, and how to install the fixed Harvester build.

LumaSpec Vision10 min read Share on LinkedIn

On this page 9 sections
  1. 01Read the traceback before touching anything
  2. 02Why a legacy camera has no TLParamsLocked node
  3. 03Three causes, ranked, with a test for each
  4. 04Worked example: proving a stale install from one 1.2.8 traceback
  5. 05Fix the package, not the symptoms
  6. 06Guard your own code while a pinned version stays
  7. 07Keep the camera or replace it
  8. 08Checks before you close the ticket
  9. 09Questions

You call ia.start_acquisition() on a camera that enumerated cleanly, and Python answers with an AttributeError that names TLParamsLocked. Nothing is wrong with the cable, and the camera streams once the call gets past this line. Two stacked causes produce the error: a camera whose XML was written to a GenICam SFNC older than 2.0, so it has no TLParamsLocked node, and a Harvesters build whose fallback for exactly that case catches the wrong exception type. This page decodes the traceback, ranks the three causes with a test for each, proves a stale install from line numbers alone, and carries the fix through to a running frame stream.

Read the traceback before touching anything

The failure arrives as three frames, and each one names a different piece of the stack.

The innermost frame sits in genicam/genapi.py: get_node() asks the node map for TLParamsLocked, and the map answers with LogicalErrorException and the message Node not existing. The middle frame is genapi.py again: the attribute-access wrapper catches that error and re-raises it as AttributeError, deliberately, so that Python's hasattr can probe optional features without a try and except around every read. In the 1.2.8 traceback the re-raise is genapi.py line 2084. The outer frame is harvesters/core.py inside start_acquisition: line 2428 sets self.remote_device.node_map.TLParamsLocked.value = 1 just before streaming, and line 2429 guards it with except GenericException.

The chain therefore reads: the camera says the node does not exist, GenAPI translates that into AttributeError, and a fallback that expects only GenericException lets the AttributeError escape start_acquisition.

For lookups that fail outside acquisition start — a mistyped feature name, a device opened by another process — the broader GenICam feature-access diagnosis is the reference. This failure is narrower: the node is genuinely absent from the XML, and the repair belongs in the library's fallback, not in your feature names.

Why a legacy camera has no TLParamsLocked node

GenICam's SFNC defines which features a device XML declares. TLParamsLocked arrived with SFNC 2.0: an XML written to SFNC 2.0 or later declares it in the remote device's node map, an XML from an earlier generation does not. The Harvesters source marks its fallback with exactly that condition — devices below SFNC 2.0.

The node's job is narrow. Setting it to 1 just before streaming freezes the transport-layer parameters, so nothing reconfigures the device mid-capture. A camera without the node has nothing to freeze, so the correct behavior is to skip the assignment and start the stream anyway. The camera loses nothing: the lock is housekeeping before streaming, and legacy devices deliver frames without it — which is exactly what the fixed fallback demonstrates.

Three causes, ranked, with a test for each

Work the causes in this order; the first two are the fault, the third is the trigger.

  1. A stale installed copy. You cloned the fixed source and ran the install, but the interpreter still imports the old modules from site-packages. Most likely when conda or virtual environments are in play — in the bug report, the install log even printed the new version string harvesters==1.2.8.post0.dev1 while the old code kept running. Test: compare the traceback's line numbers with your clone (the worked example below).
  2. A release build that predates the fix. The installed start_acquisition guards the lock with except GenericException only. Test: open the installed core.py and read the except line.
  3. A legacy camera XML. SFNC below 2.0, no TLParamsLocked node — present in every occurrence of this failure, and not a defect. Test: one hasattr call.
Cause Test Fix
Stale site-packages copy Traceback line 2429 shows except GenericException: while the clone shows the two-type tuple Uninstall, then reinstall from the clone in the running interpreter
Release predates the fix Installed core.py lists one exception type in the fallback Move to a build whose fallback catches AttributeError as well
XML below SFNC 2.0 hasattr(node_map, 'TLParamsLocked') prints False None needed; the fixed fallback skips the lock

The probe, with the test that proves the result:

# ia: the ImageAcquirer object your Harvester session created
node_map = ia.remote_device.node_map        # remote device's GenICam node map
print(hasattr(node_map, 'TLParamsLocked'))  # False on SFNC < 2.0 XMLs, True otherwise

On the legacy camera this prints False, which proves the missing node. On an SFNC 2.0 camera it prints True, and node_map.TLParamsLocked.value = 1 then succeeds.

Worked example: proving a stale install from one 1.2.8 traceback

Inputs, as recorded in the bug report for this failure: Ubuntu 18.04.3 LTS, Python 3.6.10, harvesters 1.2.8 installed under site-packages, a fresh clone of master at commit a8c292cea0bf41fe07ab185b719ef58f26c40564, an install run that logged harvesters==1.2.8.post0.dev1, and a GEV (GigE Vision) device on a GenTL producer.

The rule: a traceback prints the file and line number of the code that actually executed, and Python imports from site-packages rather than from wherever a clone sits on disk. The fixed fallback must list both exception types on the except line that follows the TLParamsLocked assignment.

The arithmetic, written out. The traceback names .../site-packages/harvesters/core.py and shows line 2428 as the assignment and line 2429 as except GenericException: — one exception type caught. The fixed master requires that same line to read except (GenericException, AttributeError): — two types. The clone's checked-out hash matches master, so the source on disk carries two; the executed file catches one. Since 1 ≠ 2, the running code is not the clone's code, whatever version string the install log printed.

Result: the installed package predates the fix, even though the clone is current.

Decision, with the threshold: whenever the except line in the traceback disagrees with the same line in your clone, treat the install as stale. Uninstall, then reinstall from the clone inside the interpreter you actually run — in the bug report, the reinstall landed in one conda environment while the session ran in another. The pass condition: line 2429 of the executed core.py shows the two-type tuple, and start_acquisition returns on the same camera without AttributeError.

Fix the package, not the symptoms

  1. Know the target line: the fallback around the TLParamsLocked assignment must read except (GenericException, AttributeError): — the maintainer's fix, present in the current master.
  2. Uninstall in the interpreter that runs your script. Check which conda or virtualenv is active, then run pip uninstall harvesters. Run it a second time; a report that nothing is installed is the confirmation you want.
  3. Clear the leftovers. Delete the build/ directory that the earlier setup.py run left inside the clone, then reinstall from the clone — python setup.py install, or pip install . from inside it.
  4. Prove the running copy. Run python -c 'import harvesters; print(harvesters.file)', open the printed core.py, and confirm the except line in start_acquisition shows the two-type tuple.
  5. Re-run ia.start_acquisition(). On the legacy camera it now returns and frames arrive. Record the version from pip show harvesters next to the clone's commit hash, so the next engineer knows what is pinned.

The same version-skew signature appears elsewhere: SDK examples that fail with AttributeError and Docker builds that mix library versions are the same fault in different hosts — install the version you pinned, where you run it.

Guard your own code while a pinned version stays

If your requirements pin the old build, apply the maintainer's one-line change to the vendored copy — replace except GenericException: with except (GenericException, AttributeError): around the lock — and pin the patched commit. Solving it from outside the library does not work: wrapping ia.start_acquisition() in a try and except AttributeError fails, because the exception is raised inside start_acquisition before the stream exists, and a second call hits the same line.

In code you own that touches optional features directly, probe before you write:

# ia: the ImageAcquirer object from your Harvester session
node_map = ia.remote_device.node_map             # remote device's GenICam node map

has_lock = hasattr(node_map, 'TLParamsLocked')   # bool: False on SFNC below 2.0 XMLs
if has_lock:
    node_map.TLParamsLocked.value = 1            # lock TL parameters before streaming

The test that proves the branch: on the legacy camera has_lock is False and the assignment never runs; on an SFNC 2.0 camera it is True and the assignment succeeds. The same probe-first discipline covers every optional feature — a node that exists but refuses writes is the neighboring failure, an access-state problem rather than a missing node.

Keep the camera or replace it

The camera is healthy: it enumerated, its XML loaded, and every node except the missing one answered. Keeping it is the right call whenever the guarded build starts it and the image suits the task — the fix costs minutes and no hardware.

Replacement earns its cost in two cases. First, the producer no longer maintains the camera's GenTL support, so other pre-2.0 quirks will surface one by one. Second, you are standardizing new stations: a camera designed after SFNC 2.0 declares TLParamsLocked, the lock applies, and this entire code path stays dormant.

Both cards share the same 6 MP, 1/1.8" rolling-shutter mono sensor behind a C-mount; they differ in interface only, GigE on one and USB3 on the other.

Area scan camerasHikrobot MV-CT0MG0060/R/NN/NN 6 MP Area Scan Camera, 3rd Gen., GigE, Mono, BASEMV-CT0MG0060/R/NN/NN
Sensor format
1/1.8"
Lens mount
C-mount
$909.00View part
Area scan camerasHikrobot MV-CT0MU0060/R/NN/NN 6 MP Area Scan Camera, 3rd Gen., USB3.0, Mono, BASEMV-CT0MU0060/R/NN/NN
Sensor format
1/1.8"
Lens mount
C-mount
$969.00View part

The camera finder filters by interface, resolution, shutter and sensor format, and the camera step of the configurator pairs the camera with a matching lens and cable. When the bench becomes a line cell, vision controllers host the same GenICam stack with several GigE ports and built-in light channels; the area scan cameras category is the catalog side of this page.

Checks before you close the ticket

  • Traceback path. The file in the traceback sits under site-packages, not in your clone — you were reading the code that runs, not the code you edited.
  • Except line. The executed core.py reads except (GenericException, AttributeError):; the AttributeError leg is what handles the legacy camera.
  • Interpreter match. import harvesters; print(harvesters.__file__) in the running interpreter prints the path you just fixed.
  • hasattr result. False on the legacy camera — expected, and proof that the skip branch is the right one.
  • Frames. start_acquisition returns and the stream delivers, with no AttributeError in the log.
  • Pin recorded. The commit hash or version sits next to the requirements file, so a rebuild cannot quietly restore the old line.

Once those six pass, the ticket becomes a documentation note rather than a bug. More acquisition-side debugging lives on the systems topic page. If the replacement path wins instead, build the camera side here or tell us the camera and host and we will spec the pair.

Questions

What does TLParamsLocked actually lock?

Transport-layer parameters: once the value is set to 1 just before streaming, the stream configuration is frozen so nothing reconfigures the device mid-capture. Camera XMLs written to GenICam SFNC 2.0 or later declare the node in the remote device's map; older XMLs lack it, so a correct library skips the assignment and starts the stream anyway.

Why does the missing node surface as AttributeError instead of LogicalErrorException?

GenAPI's node map catches the LogicalErrorException carrying the Node not existing message inside its attribute lookup and re-raises it as AttributeError, so Python's hasattr can probe optional features without wrapping every read in try and except. The side effect: an except clause that lists only GenericException misses the failure completely.

Do USB3 Vision or CoaXPress cameras hit the same error?

Yes, if their XML declares SFNC below 2.0 — the transport type does not matter, because the node lives in the device's XML either way. A fixed library catches both exception types, so the same camera starts normally on GigE Vision, USB3 Vision or CoaXPress once the fallback is in place.

Can a pinned Harvesters version still start acquisition?

Apply the maintainer's one-line change to your vendored copy — catch (GenericException, AttributeError) around the TLParamsLocked assignment — and pin the patched commit so a rebuild cannot restore the old line. Budget for the pin: the old build will keep collecting other pre-fix bugs, so plan the jump to a current release at the next maintenance window.

Is the camera or the cable at fault?

No. The device enumerated, its XML loaded, and the node map answered every query up to the lock, so the link is healthy. The failure is a feature-generation mismatch between the library's expectation and an older XML. With the fixed fallback in place, the same camera and cable stream normally.