System designHow-to

Get a GenICam notification when one-push autofocus has finished

Register a GenICam property notification on the camera's FocusAuto command so your app knows the moment one-push autofocus finished, with C++ and Python code.

LumaSpec Vision11 min read Share on LinkedIn

On this page 10 sections
  1. 01When does one-push autofocus finish on a GenICam camera?
  2. 02How does a notification replace the fixed sleep?
  3. 03What do you need before you write the callback?
  4. 04How do you register the callback and execute the command?
  5. 05How does the same pattern look in Python and a GUI?
  6. 06What else can property notifications watch?
  7. 07Why does the callback never fire?
  8. 08How do you verify the notification end to end?
  9. 09When should you poll instead of waiting for the event?
  10. 10Questions

You call one-push autofocus, then guess. Sleep two seconds and hope the lens has settled: too little and the first frames come back soft while the lens is still moving, too long and every recipe change pays the difference in cycle time. GenICam offers a better contract - property notifications. You register a callback on the camera's FocusAuto command, execute the command, and your function runs the moment the camera marks the move finished. This guide shows the registration in C++ and in Python, the arithmetic that says how much cycle time the event saves over polling or a fixed sleep, the failure modes that stop the callback from firing, and the ten-cycle test that proves the result.

When does one-push autofocus finish on a GenICam camera?

The camera decides, and it tells you: a one-push command stays marked as executing until the focus move finishes, and the GenICam property notification then fires your callback, where isDone() returns true. The application never guesses a time.

FocusAuto is the standard GenICam feature name for one-push focus. Calling execute() on that command starts the lens motor; while it runs the command is busy, and when the camera finishes it raises a property-update event through the GenICam messaging system. That messaging system carries the other events an integrator cares about too: a manual control locking because an automatic mode took over, a one-push white balance completing, a plain value changing. One registration mechanism covers all of them.

You meet the pattern on cameras with a motorized focus lens - a recipe change moves the working distance, a part change needs a refocus, a zoom module re-frames. Before notifications, each of those ended in a sleep with a safety margin stacked on top.

How does a notification replace the fixed sleep?

Polling isDone() every 250 ms adds an average 125 ms and a worst case 250 ms to every focus move, and a fixed 2 s sleep wastes the whole gap between the sleep and the real focus time; the event callback reports completion the moment the camera fires it.

Polling samples a flag, so completion can land anywhere in the interval: a 250 ms interval means the detection delay is uniform from 0 to 250 ms, averaging 125 ms. A fixed sleep does not sample at all. Say the lens settles in 600 ms - the example number here; measure yours once with the logging test in the verification section. A 2,000 ms wait then wastes 1,400 ms per recipe change. On a line running one part per second, that is 1.4 parts of capacity lost per change (1,400 ms / 1,000 ms per part = 1.4).

mean poll delay = poll interval / 2 = 250 ms / 2 = 125 ms worst poll delay = poll interval = 250 ms wasted per change = fixed wait - real focus time = 2,000 ms - 600 ms = 1,400 ms

The event path adds no sampling interval: the callback fires when the camera reports completion, so the measured delay equals the focus time itself. The bounded software auto-exposure loop is the tool for the opposite case - a camera that offers no events, where software must poll with sleeps. Where the camera implements property notifications, that loop and its interval arithmetic disappear.

What do you need before you write the callback?

You need three things: an open device whose property map lists FocusAuto, the SDK's property classes - command, boolean, integer and float - and a callback function matching the SDK's signature, registered before you call execute().

Open the device and set your features before the stream starts, the order the manual exposure before streaming guide walks through, so the registration exists before any command can run. Then find the command: findCommand("FocusAuto") against the device's property map. A failed lookup is information, not a dead end - the camera has no focus motor, so one-push focus is simply not in the feature set; store a working distance per recipe or pick a motorized model instead. Feature names, symbolics and capitalisation get their own diagnostic in the GenICam enumeration invalid value guide.

The pattern runs on any area scan camera driven from host code - typically your application on one of our vision controllers, with the camera on GigE Vision or USB3 Vision.

How do you register the callback and execute the command?

Registration is three calls - find the command, add the notification, execute - and one cleanup call on shutdown; the listing uses one SDK's C++ spelling, and every GenICam SDK names the same four steps similarly.

Work through it in order:

  1. Open the device and take its property map: grabber.devicePropertyMap().
  2. Find the command and keep it in scope: auto autofocuscmd = grabber.devicePropertyMap().findCommand("FocusAuto", err); - the object must stay alive for as long as the camera is open.
  3. Write the callback. It receives the property and checks the command state:
// API names follow the GenICam SDK's C++ property API.
// focus_done and focus_finished_at are your own variables.
bool focus_done = false;
std::chrono::steady_clock::time_point focus_finished_at{};

void auto_focus_done(Property& prop)
{
    if (prop.asCommand().isDone())
    {
        focus_done = true;
        focus_finished_at = std::chrono::steady_clock::now();
    }
}
  1. Register the callback, then execute the command:
auto notifytoken = autofocuscmd.eventAddNotification(auto_focus_done);
autofocuscmd.execute();

eventAddNotification() returns a NotificationToken. Your main thread keeps running while the lens moves; the callback fires once the camera reports the move finished.

  1. Shut down in reverse order - remove the registration, stop the stream, close the device:
autofocuscmd.eventRemoveNotification(notifytoken);
grabber.streamStop();
grabber.deviceClose();

How does the same pattern look in Python and a GUI?

In Python the same three calls keep their names in snake case - device_property_map, event_add_notification, event_remove_notification - and the callback reads value and is_locked, which is exactly what an exposure field needs while auto exposure owns the value.

The classic GUI case is an exposure field on a camera running auto exposure: the camera adjusts the exposure time continuously and may lock the manual control, so the field must grey out and follow every change. A spin-box subclass does both from one callback:

class PropertyFloatEdit(QDoubleSpinBox):
    def __init__(self):
        super().__init__()
        self.prop = None          # the camera property, set in set_property()
        self.notify_token = None  # returned by event_add_notification()

    def set_property(self, grabber, feature_id):
        # feature_id: your SDK's id for the ExposureTime feature
        self.prop = grabber.device_property_map.find_float(feature_id)
        if self.prop is not None:
            self.setMinimum(self.prop.minimum)
            self.setMaximum(self.prop.maximum)
            self.value = self.prop.value
            self.notify_token = self.prop.event_add_notification(
                self.on_property_update
            )

    def on_property_update(self, prop):
        self.setEnabled(not prop.is_locked)
        self.setValue(prop.value)

Assign the property after the device is open - self.edt_exposuretime.set_property(self.grabber, PropId.EXPOSURE_TIME) - and call self.prop.event_remove_notification(self.notify_token) when the window or the device closes. Not every camera model supports reading the exposure time while auto exposure is active; on those, the notification is the practical read point, because the callback arrives exactly when the value has changed. The GUI-free version of setting exposure through a GenICam API is in the Python GenICam exposure guide, and reading the exposure time from frame metadata is the capture-side alternative.

What else can property notifications watch?

Any property the camera exposes can carry a notification: the same registration reports one-push white balance completion, a plain value change, or the lock that auto exposure puts on the manual exposure controls - one eventAddNotification call and one token per property.

Command properties behave like FocusAuto: find the command, register, execute, and check isDone() in the callback. The one-push white balance command - BalanceWhiteAuto in the standard feature naming - works identically, so a recipe manager waits on white balance the same way it waits on focus. Boolean and integer properties notify on lock and value changes, which is how the exposure field above follows the camera.

The notification tells you each camera finished its adjustment; it does not tell you two cameras agree. After a one-push white balance across a rig, test white balance consistency between cameras before rejecting parts.

Why does the callback never fire?

Two causes cover most failures: the property object was destroyed when its creating function returned - killing the registration with it - and the callback was registered after execute() had already finished. Keep the object alive for the whole time the camera is open, and always register before you execute.

Work down the list in this order:

  1. The property object died. A helper that creates autofocuscmd, calls execute() and returns takes the registration with it. Hold the object in main() or as a member for as long as the device is open.
  2. Registered too late. A quick move can finish before the registration line runs - a 300 ms move beats 400 ms of start-up code. Register first, execute second.
  3. The feature is missing. findCommand("FocusAuto") returned nothing because the lens has no motor. The broader property-not-found family has its own walkthrough in the camera exposure property not found guide.
  4. The callback blocks. Heavy work, a GUI call that pumps events, or a second SDK call inside the callback can stall the notification thread. Set the flag, copy the timestamp, return.
  5. Token housekeeping slipped. Remove the token once, at shutdown, with the stored token - then stop the stream and close the device.

How do you verify the notification end to end?

Log a timestamp before execute() and one inside the callback, run ten focus cycles, and require exactly one callback per cycle with isDone() true and a delay close to your measured focus time - 600 ms in the example, against a 725 ms average for 250 ms polling.

Run it like this:

  1. Record t0 immediately before execute().
  2. In the callback, record t1 and increment a counter.
  3. After ten cycles, check three things: the counter reads 10, every fire had isDone() true, and each t1 - t0 sits close to the measured focus time.

With the example numbers, the event path logs about 600 ms per cycle - the callback fires when the camera reports completion, so no sampling interval is added. Swap the callback for a poll loop that reads the command state every 250 ms and repeat: the logged delay spreads from 600 ms to 850 ms (600 ms of focus plus 0 to 250 ms of sampling delay), averaging 725 ms (600 ms + 125 ms). Ten cycles produce one tight cluster around the focus time for the event path and a sawtooth across the poll window for the loop - the two lists prove which path you are on.

When should you poll instead of waiting for the event?

Poll only when the camera cannot send the event: a lens without a focus motor has no FocusAuto command at all, and an SDK build without property notifications leaves a bounded poll loop as the fallback - size its interval to your latency budget, not to habit.

The decision, per case:

  • Motorized lens, property notifications available - register the callback, delete the sleep, keep the property object alive to device close.
  • Fixed-focus lens - nothing to wait for: store working distance and aperture per recipe and skip autofocus entirely.
  • Focus motor, no property events in your SDK build - poll the command state with the interval at a quarter of your detection budget; a 200 ms budget means a 50 ms interval and a 25 ms mean added delay (50 ms / 2).
  • One-push white balance or exposure-lock UI - same registration, different property, same token housekeeping.

The hardware side is undemanding: the pattern ignores resolution, shutter type and interface, and asks only whether the property map lists the feature. Both transports qualify, so a GigE and a USB3 example cover it:

Area scan camerasHikrobot MV-CS200-10GC 20 MP Area Scan Camera, GigE, IMX183, Color, BasicMV-CS200-10GC
Sensor format
1"
Lens mount
C-mount
$739.00View part
Area scan camerasHikrobot MV-CU020-90GC 2.5 MP Area Scan Camera, GigE, GMAX4002, ColorMV-CU020-90GC
Sensor format
1/1.7"
Lens mount
C-mount
$309.00View part
Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
$560.00View part

The host runs your SDK, the callback loop and the rest of the application; the MV-VC3301H-128G66 pairs an Intel Core i3 with 8 GB of memory and 5+ LAN ports and includes a VM6200Pro license:

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

To size the camera and host as a pair, start at the configurator step below, browse the camera finder and the vision controllers, and read the neighbouring integration guides on the systems topic page. If you want the feature list of a specific camera confirmed before ordering, the contact page reaches the applications team.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

Questions

Does every area scan camera expose the FocusAuto command?

No. FocusAuto is the standard GenICam name for the one-push focus command, and it appears only on cameras with a motorized focus mechanism, such as some motorized-lens models and integrated zoom modules. Open the property map in your SDK's feature browser and search for it; if the name is absent, the lens has no focus motor to command, and per-recipe manual focus is the alternative.

Can one callback function serve several camera properties?

Yes. Pass the same function to each property's eventAddNotification call; every registration returns its own NotificationToken, and the camera fires the callback separately for each property. Store the tokens as you create them so shutdown can call eventRemoveNotification once per registration - removing one token does not remove the others.

How long does a one-push autofocus move take?

It moves with the lens motor, the focus travel and scene contrast, so measure yours once: log a timestamp before execute() and one in the callback, and the difference is your camera's typical completion time. The worked example in this guide uses 600 ms; the notification removes the guess either way, because completion is reported when it happens.

What belongs inside the callback, and what stays out?

Record and return: store the completion time, copy the values you need and set a flag your main loop acts on. Heavy work, GUI updates and further SDK calls inside the callback risk stalling the SDK's notification thread. Treat it like an interrupt handler: short, non-blocking, handed off immediately.

Does the notification work the same over GigE and USB3?

Yes. Property notifications belong to the GenICam layer above the transport, so the registration is identical for a GigE Vision and a USB3 Vision camera; the event travels as part of the camera's control protocol either way. The requirement sits in the SDK build: it must implement property notifications, and the camera firmware must report the feature.