Seek ThermalThermal sensing for mission critical applications
Guide

Usage Patterns

Common integration patterns and best practices


Overview

This page walks you through the most common integration patterns for seekcamera-python, from capturing your first thermal frame to displaying live video with OpenCV and recording to file. You will learn how the event-driven callback model works, how to configure cameras at connect time, and how to safely coordinate frame data between background callback threads and your main application thread. Following these patterns helps you build reliable, responsive applications around Seek Thermal OEM camera cores.


Prerequisites

Before working through the examples on this page, make sure you have the following in place:

  • seekcamera-python installed and importable (see the installation guide)
  • Seek Thermal SDK shared library present on your system and discoverable at runtime
  • A supported Seek Thermal OEM camera core connected via USB
  • Python 3 (the library targets Python 3)
  • NumPy — required for accessing raw frame data arrays
  • OpenCV (cv2) and Pillow (PIL) — required only for the display and recording examples
  • Basic familiarity with Python threading primitives (threading.Condition) is helpful for the multi-threaded display pattern

Installation
  1. Install seekcamera-python from PyPI (or your local build):
pip install seekcamera-python
  1. Install optional dependencies if you plan to use the OpenCV display or recording patterns:
pip install opencv-python numpy Pillow
  1. Verify the import works correctly:
from seekcamera import SeekCameraManager, SeekCameraIOType
print("seekcamera-python imported successfully")
  1. Confirm your camera is detected by the operating system before running any application code. On Linux, check lsusb; on Windows, check Device Manager.

Configuration

All per-camera configuration is applied inside your CONNECT event handler, before or after starting a capture session. The table below describes the key options you can set on a SeekCamera instance.

OptionHow to setCommon valuesEffect
Frame formatcamera.capture_session_start(format)SeekCameraFrameFormat.THERMOGRAPHY_FLOAT, SeekCameraFrameFormat.COLOR_ARGB8888Determines which frame attribute you read in the frame callback (camera_frame.thermography_float or camera_frame.color_argb8888). Choose THERMOGRAPHY_FLOAT when you need temperature values in °C; choose a colour format when rendering to screen.
Color palettecamera.color_palette = ...SeekCameraColorPalette.TYRIAN and other palette constantsControls the false-colour mapping applied to colour-format frames. Only meaningful when using a colour frame format. Set this before calling capture_session_start.
Shutter modecamera.shutter_mode = ...SeekCameraShutterMode.AUTO, SeekCameraShutterMode.MANUALAUTO (default) lets the camera trigger flat-field correction automatically. Switch to MANUAL to suppress shutter events during time-sensitive operations such as video recording; restore AUTO immediately afterward to avoid image drift.
IO type (manager)SeekCameraManager(io_type)SeekCameraIOType.USB, bitwise OR of multiple typesTells the manager which hardware interfaces to monitor. Combine types with `

Configuration that is not set explicitly inherits the camera's firmware defaults.


Usage

The event-driven model

seekCamera-python is built around an asynchronous, event-driven architecture. You create a SeekCameraManager, register an event callback, then let the manager notify you when cameras connect, disconnect, or encounter errors. Inside the CONNECT handler you configure the camera and start a capture session; inside the DISCONNECT handler you stop it.

from seekcamera import (
    SeekCameraIOType,
    SeekCameraManager,
    SeekCameraManagerEvent,
    SeekCameraFrameFormat,
)

def on_event(camera, event_type, event_status, user_data):
    if event_type == SeekCameraManagerEvent.CONNECT:
        camera.register_frame_available_callback(on_frame, user_data)
        camera.capture_session_start(SeekCameraFrameFormat.THERMOGRAPHY_FLOAT)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        camera.capture_session_stop()
    elif event_type == SeekCameraManagerEvent.ERROR:
        print(f"Camera error: {event_status}")

def on_frame(camera, camera_frame, user_data):
    frame = camera_frame.thermography_float
    # frame.data is a NumPy array of float32 temperature values
    print(f"Frame {frame.width}x{frame.height} from {camera.chipid}")

with SeekCameraManager(SeekCameraIOType.USB) as manager:
    manager.register_event_callback(on_event, user_data=None)
    # Keep the process alive while the manager runs
    import time
    while True:
        time.sleep(1.0)

The user_data argument you pass to register_event_callback and register_frame_available_callback flows through to every invocation of your callbacks. Use it to carry application state (open file handles, renderer objects, queues, etc.) without relying on global variables.


Pattern 1 — Logging thermography data to CSV

Use SeekCameraFrameFormat.THERMOGRAPHY_FLOAT when you need calibrated temperature measurements. The frame's .data attribute is a NumPy array of float32 values representing degrees Celsius.

import numpy as np
from seekcamera import (
    SeekCameraIOType, SeekCameraManager,
    SeekCameraManagerEvent, SeekCameraFrameFormat,
)

def on_frame(camera, camera_frame, file):
    frame = camera_frame.thermography_float
    np.savetxt(file, frame.data, fmt="%.1f")

def on_event(camera, event_type, event_status, _):
    if event_type == SeekCameraManagerEvent.CONNECT:
        file = open("thermography-" + camera.chipid + ".csv", "w")
        camera.register_frame_available_callback(on_frame, file)
        camera.capture_session_start(SeekCameraFrameFormat.THERMOGRAPHY_FLOAT)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        camera.capture_session_stop()

Each camera gets its own CSV file named with its unique chip ID, making it straightforward to correlate data when multiple cameras are connected.


Pattern 2 — Displaying colour frames with OpenCV

All OpenCV rendering must happen on the main thread. Use a threading.Condition to hand frame data safely from the background callback thread to your main loop.

from threading import Condition
import cv2
from seekcamera import (
    SeekCameraIOType, SeekCameraColorPalette,
    SeekCameraManager, SeekCameraManagerEvent,
    SeekCameraFrameFormat, SeekCamera, SeekFrame,
)

class Renderer:
    def __init__(self):
        self.busy = False
        self.frame = SeekFrame()
        self.camera = SeekCamera()
        self.frame_condition = Condition()
        self.first_frame = True

def on_frame(_camera, camera_frame, renderer):
    with renderer.frame_condition:
        renderer.frame = camera_frame.color_argb8888
        renderer.frame_condition.notify()

def on_event(camera, event_type, event_status, renderer):
    if event_type == SeekCameraManagerEvent.CONNECT:
        if renderer.busy:
            return
        renderer.busy = True
        renderer.camera = camera
        renderer.first_frame = True
        camera.color_palette = SeekCameraColorPalette.TYRIAN
        camera.register_frame_available_callback(on_frame, renderer)
        camera.capture_session_start(SeekCameraFrameFormat.COLOR_ARGB8888)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        if renderer.camera == camera:
            camera.capture_session_stop()
            renderer.camera = None
            renderer.frame = None
            renderer.busy = False

window_name = "Thermal Viewer"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)

with SeekCameraManager(SeekCameraIOType.USB) as manager:
    renderer = Renderer()
    manager.register_event_callback(on_event, renderer)

    while True:
        with renderer.frame_condition:
            if renderer.frame_condition.wait(150.0 / 1000.0):
                img = renderer.frame.data
                if renderer.first_frame:
                    h, w, _ = img.shape
                    cv2.resizeWindow(window_name, w * 2, h * 2)
                    renderer.first_frame = False
                cv2.imshow(window_name, img)

        if cv2.waitKey(1) == ord("q"):
            break
        if not cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE):
            break

cv2.destroyWindow(window_name)

The frame_condition.wait(0.150) call blocks the main thread for up to 150 ms. If a frame arrives sooner, the condition is notified and rendering proceeds immediately.


Pattern 3 — Working with multiple cameras

The SeekCameraManager tracks all connected cameras automatically. To handle multiple cameras simultaneously, give each camera its own state object rather than sharing a single renderer. In the event callback, use camera.chipid to route events to the correct state:

camera_states = {}

def on_event(camera, event_type, event_status, _):
    if event_type == SeekCameraManagerEvent.CONNECT:
        state = {"file": open(f"thermography-{camera.chipid}.csv", "w")}
        camera_states[camera.chipid] = state
        camera.register_frame_available_callback(on_frame, state["file"])
        camera.capture_session_start(SeekCameraFrameFormat.THERMOGRAPHY_FLOAT)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        camera.capture_session_stop()
        camera_states.pop(camera.chipid, None)

You can also monitor multiple IO types by combining SeekCameraIOType values with a bitwise OR:

with SeekCameraManager(SeekCameraIOType.USB) as manager:
    ...

Examples

Example 1 — Capture thermography frames and save to CSV

This self-contained script connects to a USB camera, reads calibrated temperature frames, and appends each frame to a CSV file named after the camera's chip ID.

#!/usr/bin/env python3
from time import sleep
import numpy as np
from seekcamera import (
    SeekCameraIOType,
    SeekCameraManager,
    SeekCameraManagerEvent,
    SeekCameraFrameFormat,
)

def on_frame(camera, camera_frame, file):
    frame = camera_frame.thermography_float
    print(f"frame available: {camera.chipid} (size: {frame.width}x{frame.height})")
    np.savetxt(file, frame.data, fmt="%.1f")

def on_event(camera, event_type, event_status, _user_data):
    print(f"{event_type}: {camera.chipid}")
    if event_type == SeekCameraManagerEvent.CONNECT:
        try:
            file = open("thermography-" + camera.chipid + ".csv", "w")
        except OSError as e:
            print(f"Failed to open file: {e}")
            return
        camera.register_frame_available_callback(on_frame, file)
        camera.capture_session_start(SeekCameraFrameFormat.THERMOGRAPHY_FLOAT)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        camera.capture_session_stop()
    elif event_type == SeekCameraManagerEvent.ERROR:
        print(f"{event_status}: {camera.chipid}")

with SeekCameraManager(SeekCameraIOType.USB) as manager:
    manager.register_event_callback(on_event)
    while True:
        sleep(1.0)

Expected console output (one line per frame, chip ID will vary):

SeekCameraManagerEvent.CONNECT: ABC123DEF456
frame available: ABC123DEF456 (size: 320x240)
frame available: ABC123DEF456 (size: 320x240)
...

A file named thermography-ABC123DEF456.csv is created in the working directory. Each row contains space-separated temperature values formatted to one decimal place.


Example 2 — Live display with OpenCV and colour palette

This script renders incoming colour frames in an OpenCV window. Press q or close the window to exit. See Pattern 2 in the Usage section for the full annotated source.

#!/usr/bin/env python3
from threading import Condition
import cv2
from seekcamera import (
    SeekCameraIOType, SeekCameraColorPalette,
    SeekCameraManager, SeekCameraManagerEvent,
    SeekCameraFrameFormat, SeekCamera, SeekFrame,
)

class Renderer:
    def __init__(self):
        self.busy = False
        self.frame = SeekFrame()
        self.camera = SeekCamera()
        self.frame_condition = Condition()
        self.first_frame = True

def on_frame(_camera, camera_frame, renderer):
    with renderer.frame_condition:
        renderer.frame = camera_frame.color_argb8888
        renderer.frame_condition.notify()

def on_event(camera, event_type, event_status, renderer):
    if event_type == SeekCameraManagerEvent.CONNECT:
        if renderer.busy:
            return
        renderer.busy = True
        renderer.camera = camera
        renderer.first_frame = True
        camera.color_palette = SeekCameraColorPalette.TYRIAN
        camera.register_frame_available_callback(on_frame, renderer)
        camera.capture_session_start(SeekCameraFrameFormat.COLOR_ARGB8888)
    elif event_type == SeekCameraManagerEvent.DISCONNECT:
        if renderer.camera == camera:
            camera.capture_session_stop()
            renderer.camera = None
            renderer.frame = None
            renderer.busy = False
    elif event_type == SeekCameraManagerEvent.ERROR:
        print(f"{event_status}: {camera.chipid}")

window_name = "Seek Thermal - Python OpenCV Sample"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)

with SeekCameraManager(SeekCameraIOType.USB) as manager:
    renderer = Renderer()
    manager.register_event_callback(on_event, renderer)
    while True:
        with renderer.frame_condition:
            if renderer.frame_condition.wait(150.0 / 1000.0):
                img = renderer.frame.data
                if renderer.first_frame:
                    h, w, _ = img.shape
                    cv2.resizeWindow(window_name, w * 2, h * 2)
                    renderer.first_frame = False
                cv2.imshow(window_name, img)
        key = cv2.waitKey(1)
        if key == ord("q"):
            break
        if not cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE):
            break

cv2.destroyWindow(window_name)

Expected behaviour: An OpenCV window opens and displays a live false-colour thermal image using the Tyrian palette. The window auto-sizes to twice the native frame resolution on the first frame.


Example 3 — Capture a still or record a video (OpenCV + Pillow)

This example adds keyboard-controlled still capture and AVI recording on top of the live display. Run it and use the following keys:

KeyAction
cCapture a single JPEG
rToggle recording on/off (saves myVideo.avi on stop)
qQuit

While recording, the shutter is switched to SeekCameraShutterMode.MANUAL to prevent interruptions; it is restored to AUTO when recording stops.

# Key excerpt — shutter control during recording
if key == ord("r"):
    if not record:
        record = True
        renderer.camera.shutter_mode = SeekCameraShutterMode.MANUAL
    else:
        record = False
        renderer.camera.shutter_mode = SeekCameraShutterMode.AUTO
        # ... assemble img_array into myVideo.avi via cv2.VideoWriter

See examples/seekcamera-opencv_record.py in the repository for the complete runnable script.

Expected console output when recording stops:

Recording stopped and video is in myVideo.avi

Troubleshooting

Camera not detected at startup

Symptom: The CONNECT event never fires; no output appears after the manager starts.

Likely cause: The Seek Thermal SDK shared library cannot be found, the camera is not connected, or USB permissions are missing.

Fix:

  • Confirm the SDK shared library is installed and on the system library path (LD_LIBRARY_PATH on Linux, PATH on Windows).
  • Check that the USB device appears in lsusb (Linux) or Device Manager (Windows).
  • On Linux, add a udev rule granting your user access to the device, or run your script with sudo temporarily to confirm it is a permissions issue.

ImportError when importing seekcamera

Symptom: ImportError: No module named 'seekcamera' or a similar error at the from seekcamera import ... line.

Likely cause: seekcamera-python is not installed in the active Python environment, or you are running a different Python interpreter than the one used for pip install.

Fix:

  • Run pip show seekcamera-python to verify installation.
  • Ensure you are using the same Python environment: which python and which pip should point to the same prefix.
  • If using a virtual environment, activate it before running your script.

DISCONNECT fires immediately after CONNECT

Symptom: You see a CONNECT event followed almost instantly by a DISCONNECT event, and no frames are received.

Likely cause: An exception is being raised silently inside your CONNECT handler (for example, a failed open() call) and the camera session never starts, or the camera is physically disconnecting due to a power issue.

Fix:

  • Wrap the body of your CONNECT handler in a try/except block and print any exceptions.
  • Check the ERROR event handler; event_status will contain a SeekCameraError describing what went wrong.
  • Inspect USB power delivery — some USB hubs cannot supply sufficient current to the camera core.

OpenCV window freezes or shows no image

Symptom: The window opens but remains black, or the displayed image is static and never updates.

Likely cause: The Condition.notify() in the frame callback is never reaching the main thread's wait() call, or cv2.imshow is being called from outside the main thread.

Fix:

  • Confirm that cv2.imshow and cv2.waitKey are called only from the main thread — never from inside on_frame.
  • Increase the wait timeout if your system is slow: renderer.frame_condition.wait(500.0 / 1000.0).
  • Ensure renderer.frame_condition.notify() is called while the condition lock is held (i.e., inside the with renderer.frame_condition: block).

Video recorded at wrong frame rate

Symptom: myVideo.avi plays back too fast or too slow.

Likely cause: The frame rate calculation relies on header.timestamp_utc_ns from the first and last recorded frames. If ts_first is not reset correctly between recording sessions, the time delta will be wrong.

Fix:

  • Ensure frame_count, ts_first, and ts_last are all reset to 0 at the start of each new recording session.
  • Keep recordings short when using SeekCameraShutterMode.MANUAL, as thermal drift without flat-field correction will degrade image quality over time.

SeekCameraManagerEvent.ERROR fires with an unknown status

Symptom: Your error handler prints an unfamiliar SeekCameraError subclass name.

Likely cause: The SDK encountered an internal error, often related to firmware communication or an unsupported operation for the connected hardware revision.

Fix:

  • Log the full str(event_status) value and consult the Seek Thermal SDK documentation for the specific error code.
  • Ensure the SDK shared library version matches the version expected by seekcamera-python.
  • Reconnect the camera; transient USB errors often resolve on reconnection.