System designExplainer

Write your own test adaptor for camera image acquisition

Write a synthetic-frame test adaptor to verify an image acquisition stack with no camera, and register a sample adaptor an SDK upgrade dropped from the list.

LumaSpec Vision11 min read Share on LinkedIn

On this page 11 sections
  1. 01What an adaptor is, and why one can vanish from the list
  2. 02What a synthetic adaptor proves, and what it cannot
  3. 03The four functions a minimal test adaptor needs
  4. 04A frame pattern that turns delivery faults into artifacts
  5. 05Bandwidth arithmetic: test frame versus real frame
  6. 06Registering a dropped sample adaptor, step by step
  7. 07Where a synthetic adaptor earns its keep
  8. 08Choosing the host card for the real link
  9. 09Limits of the synthetic half
  10. 10Pre-flight checklist
  11. 11Questions

An acquisition SDK never talks to a camera directly. It talks to an adaptor: the loadable module that knows one transport — GigE Vision, USB3 Vision, Camera Link, CoaXPress — and exposes the single device model the SDK's functions share. Two problems send engineers to this layer. After a library upgrade, a sample adaptor that used to appear in the installed-adaptor list is gone. And before a camera or frame grabber reaches the bench, you still need proof that buffers, callbacks, pixel format and storage all work. Both have a direct answer: register the adaptor by its file path, and write a small test adaptor that synthesizes a 640 × 480 8-bit frame with a pattern you can assert on.

What an adaptor is, and why one can vanish from the list

Every acquisition library keeps its device model separate from its transports. The SDK defines one interface — enumerate devices, open, set the region of interest and pixel format, start, deliver frames — and each transport implements it in its own adaptor module, a DLL or shared library on disk. GigE and 10GigE cameras sit behind an Ethernet adaptor, Camera Link and CoaXPress cameras behind frame-grabber adaptors, USB3 cameras behind their own. This layer is also the reason one viewer can list a camera that another viewer cannot see: the SDK support mismatch usually starts here.

The installed-adaptor list is built from a registry that the installer populates. Between releases, vendors trim what registers by default, and sample adaptors are the usual casualties. The file still ships — it sits in the SDK install folder under its adaptor-kit samples — but nothing registered it, so enumeration skips it. The remedy is the SDK's documented registration function: pass the adaptor file's full path, reset the acquisition session so the library rebuilds its list, and enumerate again. The entry that reappears is often named after the module file rather than the old label, so any script that opens devices by name needs that string updated.

What a synthetic adaptor proves, and what it cannot

A test adaptor is an adaptor module that fabricates frames instead of receiving them. Register it like any other and the SDK treats it as one more device: it enumerates, opens, accepts the region-of-interest and pixel-format calls you actually make, and delivers frames through the same buffer queue and callbacks as real hardware.

That makes it a clean divider between the two halves of an acquisition chain. Downstream of the adaptor, everything is under test: open and start sequence, buffer allocation and queue depth, stride handling, 8-bit versus packed-Bayer conversion, callback ordering, timeout behavior, disk writes, and the processing pipeline's throughput. Upstream of it, nothing is: the NIC or frame grabber driver, the cable, the trigger chain, exposure timing and sensor noise stay out of the picture. So when a real camera later shows no image, the fault lives in that untested half — the cable and wiring check is where to start. When a grab times out under load, the one wait per frame rule describes the timeout path the synthetic run should already have exercised.

Run them in that order: synthetic first to clear the software, then hardware to clear the link.

The four functions a minimal test adaptor needs

A usable test adaptor needs surprisingly little. Four entry points cover the whole grab loop:

  1. Enumerate — return one device under a fixed name, so the SDK's device list has something to open.
  2. Open and close — accept the first open, refuse a second, release state on close.
  3. Configure — store the region of interest and pixel format the SDK requests, and reject values you do not synthesize.
  4. Acquire — fill the caller's buffer with the test pattern, advance a frame counter, return.

The skeleton below shows the three that hold logic; enumeration is a fixed device record. The names are illustrative — your SDK's adaptor kit defines the real signatures and export table:

#include <stdint.h>
#include <stddef.h>

#define TEST_WIDTH   640u   /* pixels per line */
#define TEST_HEIGHT  480u   /* lines per frame */
#define TEST_BPP     1u     /* 8-bit mono      */

typedef struct {
    uint32_t width;     /* set by adaptor_init  */
    uint32_t height;    /* set by adaptor_init  */
    uint32_t stride;    /* set by adaptor_init  */
    uint8_t  open;      /* 0 = closed           */
    uint64_t frame_no;  /* frames delivered     */
} test_adaptor_t;

int adaptor_init(test_adaptor_t *ad)
{
    if (ad == 0) return -1;
    ad->width    = TEST_WIDTH;
    ad->height   = TEST_HEIGHT;
    ad->stride   = TEST_WIDTH * TEST_BPP;   /* 640 bytes */
    ad->open     = 0u;
    ad->frame_no = 0u;
    return 0;
}

int adaptor_open(test_adaptor_t *ad)
{
    if (ad == 0 || ad->open) return -1;
    ad->open = 1u;
    return 0;
}

int adaptor_acquire(test_adaptor_t *ad, uint8_t *buf, size_t buf_bytes)
{
    uint32_t x, y;
    uint8_t  n;
    if (ad == 0 || buf == 0 || ad->open == 0u) return -1;
    if (buf_bytes < (size_t)ad->stride * ad->height) return -2;
    n = (uint8_t)(ad->frame_no & 0xFFu);
    for (y = 0u; y < ad->height; y++)
        for (x = 0u; x < ad->width; x++)
            buf[(size_t)y * ad->stride + x] = (uint8_t)(x + y + n);
    ad->frame_no++;
    return 0;
}

At 640 × 480 and one byte per pixel the stride is 640 bytes and a frame fills exactly 307,200 bytes, so the size guard rejects an undersized allocation before the loop can write past its end.

A frame pattern that turns delivery faults into artifacts

A constant grey frame hides bugs; a moving pattern surfaces them. Set every pixel to the sum of its coordinates plus the frame number, modulo 256:

v(x, y, n) = (x + y + n) mod 256

Each delivered frame shifts the whole diagonal ramp by one grey level, and that single choice turns the common acquisition faults into distinct, readable artifacts:

  • Frozen pipeline — two consecutive frames are identical and the ramp has not shifted: the callback delivered the same buffer twice.
  • Dropped frames — the grey level at a pixel jumps by more than one between reads: frames arrived but processing skipped them.
  • Wrong stride in the consumer — the diagonals shear into staircases: the display code assumes a different bytes-per-line than 640.
  • Format confusion — an 8-bit buffer read as packed Bayer produces color mosaics; read as 16-bit, alternating dark and bright columns.

One assertion makes the pattern provable rather than visible. The test: initialize, open, allocate 307,200 bytes, acquire twice, check four values. Frame 0 must give buf[0] = 0 and buf[307199] = 94, because 639 + 479 + 0 = 1118 and 1118 mod 256 = 94. Frame 1 must give buf[0] = 1 and buf[307199] = 95. Any other values mean the pattern logic or the stride is wrong before a line of camera code exists.

Bandwidth arithmetic: test frame versus real frame

The small frame is deliberate: the synthetic stream should cost almost nothing, so the test measures software, not the host's bandwidth.

frame_bytes = width × height × bytes per pixel stream_MBps = frame_bytes × frames per second ÷ 1,000,000

The test adaptor moves 640 × 480 × 1 = 307,200 bytes per frame — exactly 300 KiB. At 30 fps that is 307,200 × 30 = 9,216,000 bytes per second, about 9.2 MB/s, and a 16-frame ring buffer holds 16 × 307,200 = 4,915,200 bytes, about 4.9 MB. Any PC that runs the SDK handles that with room to spare.

The camera the pipeline will eventually serve is a different animal. The LS-CMR-0166 runs 4000 × 3000 at 30 fps. In 8-bit mono one frame is 4000 × 3000 × 1 = 12,000,000 bytes — 12 MB — so the stream is 12,000,000 × 30 = 360 MB/s and a 16-frame ring wants 192 MB of host memory. A Gigabit Ethernet port delivers about 118 MB/s of payload once protocol overhead is paid, so 360 MB/s does not fit on it; USB3's 5 Gbit/s line rate leaves roughly 380 MB/s usable, which is the arithmetic that puts this class of camera on USB3.

The camera behind those real numbers:

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
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

The decision that falls out: size the ring buffer and the host link for the real camera, never for the test adaptor. A pipeline that passes with 300 KiB frames can still starve at 12 MB frames, so repeat the throughput run with hardware attached — the same Python grab loop you validated synthetically is the one you re-run.

Registering a dropped sample adaptor, step by step

  1. Find the file. Look in the SDK install folder, under its adaptor-kit or samples directory, for the sample adaptor module; installers often leave it unregistered.
  2. Register it by full path. Call the SDK's adaptor-registration function with the absolute file path — relative paths fail quietly on some builds.
  3. Reset the session. Call the library's reset call so it rebuilds the installed-adaptor list; a session that is already running keeps the old registry.
  4. Enumerate and read the name. The adaptor reappears under its module file name, not necessarily the label an older release printed.
  5. Update name references. Device selectors, scripts and saved configurations that match by name need the new string.

Registration belongs to one install and generally does not survive a library upgrade, so keep the call in the bring-up script beside the enumeration check.

If a device then fails on start, the initialization start sequence covers the open order the synthetic adaptor already forced you to get right.

Where a synthetic adaptor earns its keep

  • Continuous integration. Build servers carry no cameras; a registered test adaptor lets acquisition tests run in every build and catch interface regressions, including the release-to-release change that drops an adaptor from the list.
  • Deterministic bug reproduction. Buffer starvation and stride bugs replay exactly, frame for frame, with no network variance in the way.
  • Host bring-up before hardware lands. Install the SDK, register the adaptor, run the pipeline, and the PC side is proven before the frame grabber arrives; then check the full interface chain on the real card.
  • Upgrade regression. The installed-adaptor list is part of your system's contract; asserting on it catches SDK updates that change names or ordering.
  • Training. New engineers exercise open, grab, timeout and shutdown paths on any laptop, with no camera to book.

Multi-camera port assignment stays a hardware test: assigning cameras to ports reliably depends on NIC, MAC and port ordering that synthetic devices cannot represent.

When the software side is proven, the remaining decision is host hardware, matched to the camera interface and the port count. For a one- or two-camera GigE setup, the MV-GE2002P is a dual-port PoE NIC that powers the cameras over the data cable. For a bench of 10GigE cameras, the MV-GT2104 is a quad-port copper 10GbE NIC that keeps four cameras off a switch. For Camera Link and CoaXPress, the MV-GX1008 is an octo CXP-6 frame grabber that takes eight CoaXPress cameras into one host.

The three cards side by side:

Specification MV-GE2002P MV-GT2104 MV-GX1008
Interconnect PCI-E gen2 × 4 PCI-E gen3 x 8 PCI-E gen3 × 8
Delivery bandwidth 860 MB/s (max. transmission bandwidth); 800 MB/s (sustained transmission bandwidth) 7800 MB/s (max. transmission bandwidth); 6700 MB/s (sustained transmission bandwidth) 7800 MB/s (max. transmission bandwidth); 6700 MB/s (sustained transmission bandwidth)
Standard IEEE 802.3 IEEE802.3 CoaXPress 1.0/1.1
Interface 2 × RJ45 RJ45 × 4 DIN 1.0/2.3 × 8
Camera type supported GigE industrial camera 10 GigE industrial camera CXP-6 connector industrial camera
Camera quantity supported 2 at most 4 at most 8 at most
Camera connection speed 10/100/1000 Mbps 1000/2500/5000/10000 Mbps 1.25/2.5/3.125/5/6.25 Gbps (CXP-1/2/3/5/6)
Stream 2 4 8
Power consumption 7.5 W (excl. PoE) 12.5 W (excluding PoE) Typ. 13.1 W (without PoCXP)
Dimension 181 mm × 126.3 mm × 21.6 mm 181 mm × 126.3 mm × 22.4 mm 181 mm × 126.3 mm × 21.6 mm (7.1" × 5.0" × 0.9")
Weight 190 g 205.14 g –
Temperature Working temperature: 0 °C to 60 °C (32 °F to 140 °F); Storage temperature: –20 °C to 70 °C (–4 °F to 158 °F) Working temperature: 0 °C to 55 °C (32 °F to 131 °F); Storage temperature: –30 °C to 80 °C (–22 °F to 176 °F) Working temperature: 0 °C to 55 °C (32 °F to 131 °F); Storage temperature: –20 °C to 70 °C (–4 °F to 158 °F)
Price $249.00 $1,429.00 $1,949.00

All 3 share: Humidity 10% RH to 95% RH (no condensation). Values as printed in each product's data sheet; open a part for its drawing and full specifications.

Browse the frame grabbers and their shop finder, and on the camera side the area scan cameras with the camera finder. The system configurator starts at the extras step, where interface cards and frame grabbers sit.

Limits of the synthetic half

By construction, a test adaptor answers nothing about the transport. Bandwidth ceilings, driver-level faults, packet loss and resends, trigger latency and jitter, exposure accuracy, sensor noise and defect pixels all live upstream of the pattern generator. Treat every green synthetic run as covering the software half only, and re-run the same suite with the real camera before the station ships — the frame grabber acquisition article walks through that second pass.

Pre-flight checklist

Run through this before you trust a synthetic green light:

  • Pattern asserted, not eyeballed — the four buffer values from the worked example run in the unit test on every build, so a regression cannot slip through.
  • Buffers sized for the real sensor — a 16-frame ring sized for 12 MB frames, not for 300 KiB ones, or the host will starve the moment hardware lands.
  • Names updated after registration — device references use the module file name the current SDK reports, so scripts survive the next upgrade.
  • Throughput re-run with hardware — a 360 MB/s camera stream behaves differently from a 9.2 MB/s synthetic one, and only the real link proves the margin.
  • Enumeration asserted in CI — the installed-adaptor list is checked after every SDK update, catching dropped or renamed adaptors in one run.
  • Timeout path exercised — acquire against a deliberately unopened device so the per-frame wait behavior is proven, not assumed.

More acquisition-stack fixes and checks sit on the systems topic page. If the camera count or interface in your station does not match the examples here, send the parts list through the quote desk or reach the team on the contact page — the stack is easier to size before it is bought.

Questions

Can a test adaptor simulate triggers and strobe timing?

No. Synthetic frames are generated on demand by the host, so there is no exposure instant, no trigger-to-frame latency and no jitter to measure — those need the real camera and a real pulse source. What the adaptor can do is exercise the software side: configure it for the same acquisition mode your trigger setup uses, so mode selection and start behavior are proven before hardware is attached.

What frame size should a test adaptor use?

Make width and height configuration parameters and default them to 640 × 480. The default keeps the synthetic stream near 9.2 MB/s at 30 fps, which any host handles, while the parameters let you set the real sensor's size when you want buffer counts and throughput tested against true frame weights. Test with both: small frames for logic, real-sized frames for the memory and bandwidth budget.

Why does a re-registered sample adaptor appear under a different name?

Explicit registration lists the module under its file name, while default registration can carry a friendlier label, so after an upgrade plus manual registration the listed name often changes. Any code that opens devices by name string then breaks. Where the SDK allows it, select devices by interface and a stable attribute instead, or match on a substring you control in the enumerated list.

Do synthetic devices appear in GigE Vision discovery tools?

No. A test adaptor bypasses the transport entirely: it is not a network device, so browsers that scan for GigE Vision or CoaXPress devices never list it. It shows up only in the SDK's own enumeration, under the adaptor you registered. There is also no address to collide with, so it cannot disturb a real camera sharing the same subnet.

How do I prove frames are fresh and not re-delivered buffers?

Embed the frame counter in the image and assert on it. With the (x + y + n) mod 256 pattern, the top-left pixel equals the frame number modulo 256, so a 300-frame run must show that pixel climbing 0 to 255 and wrapping once. A queue that re-delivers a buffer repeats the value; a dropped frame skips one. Timestamp each read as well, so counter and wall clock agree.