Usage Patterns
Common integration patterns and best practices
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.
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
- Install seekcamera-python from PyPI (or your local build):
pip install seekcamera-python
- Install optional dependencies if you plan to use the OpenCV display or recording patterns:
pip install opencv-python numpy Pillow
- Verify the import works correctly:
from seekcamera import SeekCameraManager, SeekCameraIOType
print("seekcamera-python imported successfully")
- Confirm your camera is detected by the operating system before running any application code. On Linux, check
lsusb; on Windows, check Device Manager.
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.
| Option | How to set | Common values | Effect |
|---|---|---|---|
| Frame format | camera.capture_session_start(format) | SeekCameraFrameFormat.THERMOGRAPHY_FLOAT, SeekCameraFrameFormat.COLOR_ARGB8888 | Determines 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 palette | camera.color_palette = ... | SeekCameraColorPalette.TYRIAN and other palette constants | Controls 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 mode | camera.shutter_mode = ... | SeekCameraShutterMode.AUTO, SeekCameraShutterMode.MANUAL | AUTO (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 types | Tells the manager which hardware interfaces to monitor. Combine types with ` |
Configuration that is not set explicitly inherits the camera's firmware defaults.
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:
...
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:
| Key | Action |
|---|---|
c | Capture a single JPEG |
r | Toggle recording on/off (saves myVideo.avi on stop) |
q | Quit |
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
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_PATHon Linux,PATHon 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
sudotemporarily 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-pythonto verify installation. - Ensure you are using the same Python environment:
which pythonandwhich pipshould 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
CONNECThandler in atry/exceptblock and print any exceptions. - Check the
ERRORevent handler;event_statuswill contain aSeekCameraErrordescribing 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.imshowandcv2.waitKeyare called only from the main thread — never from insideon_frame. - Increase the
waittimeout 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 thewith 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, andts_lastare all reset to0at 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.