System designTroubleshooting

Change camera gain at runtime without stopping the pipeline

Why get_child_by_name("cam") returns None on a running pipeline, and how to reach the camera's gain and exposure nodes while frames keep flowing.

LumaSpec Vision8 min read Share on LinkedIn

On this page 10 sections
  1. 01Symptom: the camera child object is None
  2. 02Cause 1: the pipeline runs in another process
  3. 03Cause 2: the child does not exist yet
  4. 04Cause 3: wrong child or feature name
  5. 05The write itself: safe while streaming
  6. 06Worked example
  7. 07Checks before you wire this into production
  8. 08When the usual answer is wrong
  9. 09Checklist
  10. 10Questions

You have a GStreamer-style pipeline running a machine-vision camera, and you want to move gain or exposure from a launch-time constant to something your application controls while frames flow. The example you copied shows a child object on the source element being used to set features, but when you try the same call you get None and the program crashes on the next line. This walkthrough explains why that happens, in order of likelihood, and gives the working pattern: build the pipeline inside your own process, bring it up, then write the camera's GenICam nodes directly.

The cameras this applies to are ordinary GigE and USB3 area scan cameras with a GenICam feature layer — for example the LS-CMR-0053 — plus a vision controller to run the pipeline if the station has no dedicated PC.

Symptom: the camera child object is None

The failing line looks like this:

cam = source.get_child_by_name("cam")
# cam is None here

Everything after it — reading DeviceModelName, setting ExposureTime or Gain — cannot work, because you are calling methods on None. The None itself is the symptom; the causes are below, most likely first.

Cause 1: the pipeline runs in another process

If you start the pipeline with a shell script that runs a command-line launcher, the pipeline object lives in that process. Your Python code, running elsewhere, has no Gst.Pipeline to call get_by_name() on, and no amount of replaying the example inside your script changes that. This is the single most common misunderstanding: the example works because everything — pipeline construction, state change, feature access — happens in one process.

The fix is to move the launch string into your program:

pipe = Gst.parse_launch(
    "yoursrc name=src device-serial-number=25080729 ! fakesink"
)

You keep the exact pipeline description from your script; only the host process changes.

Cause 2: the child does not exist yet

A source element typically creates its camera handle and publishes the feature child only after it has opened the device — which normally happens during the transition to PLAYING. If you query the child immediately after parse_launch(), before any state change, None is the correct answer, not an error.

Set the state first, wait for the change to complete, then look for the child:

  1. Build the pipeline with Gst.parse_launch(), giving the source element a name.
  2. Call pipe.set_state(Gst.State.PLAYING) and wait for the state change to return.
  3. Fetch the source element with pipe.get_by_name("src").
  4. Fetch the camera child with get_child_by_name() using the name your element actually publishes.
  5. Read a harmless property such as DeviceModelName to prove the handle is live before writing Gain.

Cause 3: wrong child or feature name

The child's name and the property names are not universal constants — they come from the element and from the camera's own GenICam XML. Before writing code, list what is really there:

  • Inspect the source element on the command line to see every property and child it publishes.
  • Cross-check the feature names against the camera's feature tree: Gain may be exposed as GainRaw, GainAbs or an enumeration, and the exposure node may be ExposureTimeRaw rather than ExposureTime.
  • Confirm the exact serial-number property name too; a camera that never opens produces the same None downstream.

This is the same discipline our GenICam enumeration invalid value: check symbolics before you set article covers for enumerations, and it applies to gain just as much: read the node's minimum, maximum and increment before you write a value, because gain steps are often quantized and a rejected write tells you the camera disagreed with your number.

The write itself: safe while streaming

Once you hold a valid handle, changing gain or exposure does not require stopping the pipeline. On most cameras both nodes are writable during acquisition, and the new value applies to subsequent frames. Two practical caveats:

  • A long current exposure can delay when the change lands; the camera applies many settings at the next frame or exposure boundary.
  • Verify, don't assume. Re-read the value after writing, and if your camera tags frames with per-frame exposure and gain metadata, check when the change actually took effect — the approach in Per-frame exposure and gain: verifying camera metadata.
applied_value = clamp(target, node_min, node_max) applied_value = applied_value - ((applied_value - node_min) mod increment)

That rounding to the node's increment is what a raw write of 14.95698 often silently becomes; compute it yourself and the re-read will hold no surprises.

For the broader question of which settings can be touched mid-stream and how to sequence them so capture never stalls, see Apply camera settings while streaming without stalling capture. If you are driving the camera from a capture loop rather than a media pipeline, the get/set pattern in Camera parameter set and get in a capture loop safely is the closer match.

Worked example

Assume a 6 MP GigE camera on a vision controller, gain currently at the camera's minimum.

  1. Parse the launch string with the source named "src".
  2. set_state(PLAYING), wait for the return; the camera opens here.
  3. src = pipe.get_by_name("src"); cam = src.get_child_by_name("cam").
  4. Print DeviceModelName. If it prints, the handle is real; if cam is still None, run the inspection step above before going further.
  5. Read the gain node's min, max and increment — suppose the increment is finer than your target and the target is in range.
  6. Write the target gain, then read it back and expect the increment-rounded value, not your exact float.
  7. Read exposure the same way before touching it; the timing logic is the subject of Setting camera exposure time through a GenICam API in Python.

Checks before you wire this into production

Area scan cameras12 MP GigE area scan camera, IMX226, 1/1.7" rolling shutter, 9 fps (LS-CMR-0053)LS-CMR-0053
Sensor format
1/1.7"
Lens mount
C-mount
Interface
GigE
Resolution
12 MP
$560.00View partBuild a system with it

A compact vision controller is enough to host the pipeline and your control code:

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

When the usual answer is wrong

  • Your element publishes a property interface instead of a child object. Some sources expose features as GObject properties directly on the element, or through a per-camera sub-element created per device. The inspection step, not the example, decides which call you make.
  • You need changes synchronized to a trigger. A plain write lands at the next opportunity, not at a chosen frame. If gain must change on a specific trigger cycle, check whether your camera supports per-configuration sets you can switch atomically instead.
  • The camera is shared with another tool. If a vendor tool or a second pipeline holds the device, your handle may open read-only or not at all. Close everything else first; that is a device-access situation, not a code bug.

Checklist

  • Pipeline built inside your process with parse_launch(), source element named.
  • State changed to PLAYING and waited for before any feature lookup.
  • Child or property names confirmed by inspecting the element and the camera's feature tree.
  • Node min, max and increment read; target value clamped and increment-rounded.
  • Auto-gain and auto-exposure disabled before manual writes.
  • Value re-read after the write; per-frame metadata checked once in acceptance testing.

For help matching a specific camera and controller to this pattern, the area scan cameras finder covers the sensors, and you can ask our applications team about feature-access behavior on a particular model.

Questions

Why does get_child_by_name("cam") return None?

Three common reasons: the pipeline was launched by a shell script, so your Python process has no pipeline object at all; the pipeline object exists but has not reached a state where the source has opened the camera; or the element exposes features under a different child name or property interface than the example you copied. Inspect the element with gst-inspect-1.0 to see the exact feature names and children it publishes before writing code against them.

Can I change exposure and gain while the camera is streaming?

On most industrial cameras yes, exposure and analog gain are writable during acquisition, and the new value applies to subsequent frames. Some sensors only latch exposure at the start of an exposure cycle, and a very long exposure time can delay the change by a frame or two. Write the value, then read per-frame metadata to confirm when it took effect rather than assuming the next frame already shows it.

Do I have to build the pipeline in Python instead of gst-launch-1.0?

If your own code must change features at runtime, yes: a pipeline launched by a shell script lives in that shell's process, and there is no handle your application can reach. You can still use the same pipeline string with Gst.parse_launch() inside your program, which is the smallest change from your existing shell command. External control of a gst-launch process is not a supported path for feature access.

How do I find the exact feature names my camera supports?

Run gst-inspect-1.0 on the source element; it lists the properties and child objects the element publishes, which usually mirror the camera's GenICam feature names. Cross-check against the camera's GenICam XML or its feature browser: names, ranges and valid enumeration symbolics come from the camera, not from your code. Guessing a name gives either a None object or a silent no-op.

What if the camera rejects the gain value I write?

Read the node's minimum, maximum and increment first and clamp your value, because gain steps are often quantized. For enumerations, query the list of valid symbolics before setting one. A rejected write usually surfaces as an error return or an exception from the transport layer; log it instead of swallowing it, and re-read the value to confirm what the camera actually accepted.