System designHow-to

Timed video capture with sensor data on an enable button

Timed video capture with sensor data: arm a 10 to 15 second clip on an enable button, put frames and DAQ samples on one clock, size the disk, replay both.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01Arm a continuous stream, not a per-frame trigger
  2. 02What the station needs
  3. 03The capture sequence, step by step
  4. 04Put frames and samples on one clock
  5. 05Size the disk with one multiplication
  6. 06Play the clip back beside the logged data
  7. 07Choose the camera, link and cable
  8. 08When this pattern is wrong
  9. 09Pre-flight checklist
  10. 10Questions

You need a pushbutton station that records a 10 to 15 second clip and later plays that clip back next to the measurements you logged. The pattern that survives contact with real hardware keeps the camera acquiring continuously and lets the button open a timed window inside that stream, with one timestamp latched at the button and shared by the video file and the data file. This guide walks the whole loop: the arming logic, the clock discipline that keeps frames and samples aligned, the storage arithmetic that sizes the drive, and a replay loop that steps video and data together. It assumes one industrial camera, one data acquisition device and a desktop-class host.

Arm a continuous stream, not a per-frame trigger

Start the camera in continuous acquisition when the program launches and leave it running. The enable button never touches the camera: it latches t0 on one monotonic clock, resets a frame counter, and opens a 10 to 15 s window in the stream you are already grabbing. The clip ends when the counter reaches fps × duration frames — 1050 frames at 70 fps for 15 s, 700 for 10 s — with the elapsed window as the backstop stop.

Triggering every frame from software is what drops frames in this job. Each trigger travels to the camera and each completion travels back through the host, and at 70 fps the frame period is only 14.3 ms, so one delayed round trip is a lost frame. Starting and stopping the camera per clip fails differently: the stream restart lands an unbudgetable delay right where your event begins.

Both failure modes are laid out in software trigger timeouts and single-frame acquisition that stays busy. For wiring the button event itself, read software-triggered image capture from a GUI button; for the always-running loop underneath it, continuous acquisition mode for live image loops.

What the station needs

  • An area scan camera that runs continuous acquisition and stamps every frame. GigE Vision cameras carry a per-frame timestamp in the buffer metadata, so the time arrives with the image.
  • A global shutter when the event moves fast: a beating specimen sweeps across pixel rows during a rolling exposure and arrives torn.
  • A PoE NIC from the frame grabber range, so power and data share one camera cable and the bench stays clean.
  • Your DAQ device, sampling sensors on the same host, with every sample timestamped on the same monotonic clock as the frame loop.
  • Disk sized by the multiplication below, confirmed with a write test.

The capture sequence, step by step

  1. At program start, put the camera into continuous acquisition at the working frame rate and pixel format. Size the grab queue for at least one longest clip: 15 s at 70 fps is 1050 frames, so queue 1200.
  2. Start the DAQ task on the same host. Both loops read one monotonic clock source, and its identity goes into the run header.
  3. On the enable button, latch t0 from that clock, reset the frame counter, and mark incoming frames with a clip ID.
  4. Append every frame whose timestamp falls inside [t0, t0 + duration] to the clip file, writing the frame timestamp beside the pixels.
  5. Stop the clip at fps × duration frames, or at t0 + duration, whichever arrives first. Frame-count stopping is exact; elapsed time is the backstop, and elapsed-time loops carry a classic failure in which the file closes early — see timed measurement file capture halted by an elapsed-time bug.
  6. Close the clip with a header — t0, fps, resolution, pixel format, clip ID — and write the same t0 and clip ID into the measurement file header.
  7. Check every clip: frame count within one of the expected number, first and last timestamps spanning the window, first and last frames opened by eye.
  8. Return to idle grabbing. The station is armed again the moment the clip closes.

Put frames and samples on one clock

Playback tells the truth only if both files share a time base. Three levels exist; pick one deliberately.

  • Host arrival timestamps, read when each frame and each sample reaches your program. Desktop scheduling adds a few milliseconds of jitter — enough for review playback, not for tying a waveform feature to one frame.
  • Camera hardware timestamps from the frame metadata, with one offset latched against the host clock at t0. This maps frames to samples well under a millisecond and costs one line of setup.
  • A shared hardware pulse: one digital output from the DAQ device into the camera's trigger input, fired at t0 and logged in the data file. Alignment is then bounded by the frame period — 14.3 ms at 70 fps — and host jitter leaves the mapping entirely.

When the camera holds its set rate, frame time is arithmetic:

frame_time = t0 + frame_index / fps

Use the logged per-frame timestamps whenever the rate can drift: industrial cameras in continuous mode hold the set rate, UVC-class webcams do not. Reading time out of a buffered queue has its own latency story — frame timestamp latency in the buffer queue — and two-device clocks drift apart in timestamp clock domains at lower frame rates. When the second data source is a serial instrument instead of sampled analog, the same discipline applies: synchronizing serial data with triggered image capture.

Size the disk with one multiplication

Clip size is four factors multiplied:

clip_bytes = width × height × bytes_per_pixel × fps × duration
Pixel format Bytes per pixel
Mono8 or RAW8 1.0
YUV422 packed 2.0
RGB8 unpacked 3.0

Worked example — a VGA-class global-shutter camera in Mono8 at 70 fps:

  • One frame: 659 × 494 pixels × 1 B = 325,546 B, about 0.33 MB.
  • The stream: 325,546 B × 70 fps = 22,788,220 B/s, about 22.8 MB/s.
  • A 15 s clip: 22.8 MB/s × 15 s is about 342 MB. A 10 s clip is about 228 MB.
  • A full day at one clip per minute: 342 MB × 480 clips gives about 164 GB.
  • The same clip in YUV422 color: about 684 MB.

Throughput is not the risk at this size. 22.8 MB/s is under a fifth of a GigE link's 125 MB/s line rate, and any SATA or NVMe SSD sustains hundreds of MB/s. The two real disk killers are creating one file per frame — write one file per clip — and letting antivirus scanners touch frames as they land; exclude the capture directory.

Play the clip back beside the logged data

Load both files by clip ID and read t0 and fps from the clip header. Drive one time cursor for both views: for the frame on screen at time t, index the data file to the sample nearest t and draw it. At 70 fps a frame spans 14.3 ms — about 14 samples at 1 kHz — so nearest-sample indexing looks exact on a chart; interpolate the cursor line only if the trace must look continuous. Pause and scrub by timestamp, never by frame index alone. Export a column of per-frame timestamps beside the data when reviewers want a spreadsheet.

On the capture side of the same loop, recording video from a machine vision camera in Python covers file formats and compression trade-offs, and numbered filenames for saved frames keeps a season of clips retrievable. Adding cameras later extends the pattern directly: multi-camera recording without dropped frames.

Match the camera to the event, then let the arithmetic above pick the interface. Fast motion takes a global shutter. The VGA stream in the worked example fits GigE with fivefold margin; a 12 MP stream at 30 fps in Mono8 is 360 MB/s and belongs on USB3 or 10GigE.

For the capture camera, the MV-CU020-90GC is a 2.5 MP GigE camera with a global shutter and a C-mount.

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
Interface
GigE
Resolution
Up to 3 MP
$309.00View partBuild a system with it

To prove the button, gating and file logic before the real hardware arrives, the LS-CMR-0166 is a 12 MP USB3 camera running 30 fps at 4000 × 3000 with a rolling shutter.

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

Power and data can share one link: the MV-GE2002P is a dual-port PoE NIC, leaving a port free for a second camera.

Frame grabbersHikrobot MV-GE2002P Dual-port PoE NIC, full height V2.0MV-GE2002P
Interface
GigE
Ports
2 ports
$249.00View partBuild a system with it

The link itself: the LS-CBL-0117 is a 15 m RJ45 camera data cable with locking studs.

CablesCamera Data Cable, RJ45 (with locking stud), Bend (up) to RJ45, 15 m (LS-CBL-0117)LS-CBL-0117
Type
Camera data cables
Length
15 m
$89.00View partBuild a system with it

Fifteen metres sits far inside the 100 m reach of a GigE link on Cat5e or better, and the locking studs keep the bench station from working loose mid-session.

If the clip must also resolve fine features, not just record the event, settle the pixel count first:

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

Then step the whole station — camera, lens, cable, host slot — through the system configurator.

When this pattern is wrong

  • A consumer webcam proves the arming logic and nothing else: no per-frame timestamps, a frame rate that drifts with exposure, and driver buffering you cannot see. On a Linux host, video capture with libv4l2 shows exactly what that API layer does and does not provide.
  • Recording straight into a compressed consumer container during capture: the frame rate turns variable and the encoder competes with your writer for CPU. Capture raw or lossless, transcode a review copy afterwards.
  • Wall-clock timestamps across a long campaign: one clock adjustment mid-run breaks every offset after it. Keep a monotonic source and one recorded offset — the failure and the fix are laid out in timestamp clock rollover and monotonic time mapping.
  • A rolling shutter on fast motion: the clip plays back, but each frame distorts the geometry.

Pre-flight checklist

  • Camera already grabbing continuously before the operator can reach the button.
  • One monotonic clock feeds t0, the frame loop and the DAQ loop; its identity is in the run header.
  • Expected frame count, fps × duration, written down and checked after every clip.
  • A 30 s write test at full rate completed with no dropped frames.
  • Clip header carries t0, fps, resolution, pixel format and clip ID; the data header carries t0 and clip ID.
  • Replay tested end to end: play, pause, scrub, spreadsheet export.
  • Disk headroom equals clips per day × clip size × retention days, plus 20% spare.

Once the loop passes on the bench, the camera finder narrows the capture camera by shutter type, interface and resolution. For the odd cases — a shared chassis clock, a second synchronized camera, a rate above one GigE link — the contact page reaches our applications team.

Questions

How much disk does a 12 MP clip at 30 fps need?

Multiply it out: 4000 × 3000 pixels × 1 B × 30 fps = 360 MB/s in Mono8, so a 15 s clip is 5.4 GB and a 10 s clip is 3.6 GB. A day at one clip per minute reaches about 2.6 TB, so capture to an NVMe SSD and move finished clips to bulk storage. Switching a 10-bit packed stream down to 8-bit saves about 20%.

Why not fire one software trigger per frame?

Because the host sits in the timing path. Each software trigger travels to the camera and each completion travels back, and the operating system services both with millisecond-scale jitter; at 70 fps the frame period is 14.3 ms, so one slow round trip drops a frame that never returns. Continuous acquisition pipelines exposure, readout and transfer, so your code only ever handles finished frames.

How do I align 1 kHz samples with 70 fps video?

Index the data to the frame. At 70 fps a frame covers 14.3 ms — about 14 samples at 1 kHz — so taking the nearest sample puts you within half a frame period, about 7 ms, of the exposure. If the chart needs a continuous trace, interpolate between bracketing samples for the cursor line only and keep the nearest-sample index as the recorded value.

Can I keep the seconds before the button press?

Yes. Run frames into a ring buffer sized fps × lead-in: at 70 fps a 5 s lead-in is 350 frames, about 114 MB in Mono8 at VGA size. When the button lands, keep writing the timed window, then save the buffer and the window as one clip under a single t0. The lead-in often catches the true start of the event, which operators routinely press late.

Should I compress the clips while recording?

Not on the capture path. Lossy encoding changes pixel values, and the encoder competes with the disk writer for CPU; one stalled encode drops frames permanently. Write raw or a lossless format during capture — lossless typically halves the 342 MB example clip on natural footage — then transcode a review copy afterwards and keep the original as the record.