A lightweight Python library for thermal sensing and analytics on Linux platforms (x86_64 and ARM). It provides unified APIs for recording, visualization, and intelligent analysis of thermal data from Hikvision or compatible infrared sensors.
-
Raw Frame Recording Capture and store radiometric thermal frames (e.g., 96Γ96, 16-bit raw) with timestamps.
-
Colored Visualization Generate pseudo-color thermal images (e.g., 240Γ240 RGB) with adjustable color maps.
-
Live Stream Interface Stream frames in real time, perform temperature conversion and display dynamically.
-
Shared Memory Architecture Efficient zero-copy access to thermal data via shared memory interface.
-
Multi-Device Support Connect and use multiple thermal cameras simultaneously:
- Consistent device ID mapping based on USB serial numbers (similar to
cv2.VideoCapture) - Automatic device enumeration and selection
- Device-specific shared memory segments for parallel operation
- Persistent device mapping stored in
~/.pythermal/device_mapping.json
- Consistent device ID mapping based on USB serial numbers (similar to
-
Thermal Object Detection Detect objects based on temperature ranges with clustering support:
- Temperature-based object detection (default: 31-39Β°C for human body)
- Adaptive human detection using environment temperature estimation
- Object center detection and clustering
- Temperature statistics per detected object (min / max / avg)
-
Environment & Body Temperature Estimation Estimate ambient room temperature and body temperature:
- Environment temperature estimation using percentile method (default: 5th percentile)
- Body temperature estimation from environment temperature using physiological model
- Adaptive detection thresholds based on room temperature
-
YOLO v11 Detection (Optional) Advanced object and pose detection using YOLO v11:
- Object detection with YOLO v11 (supports default and custom thermal models)
- Pose/keypoint detection with 17 COCO keypoints
- Support for custom thermal-specific models
- Real-time inference on thermal images
-
Offline Replay and Analysis (Future Development) Replay recorded sessions for algorithm benchmarking or dataset generation.
Install from source with automatic USB setup:
git clone https://github.com/AIoT-Infrastructure/pythermal.git
cd pythermal
pip install -e .β¨ Automatic USB Setup: When you install with pip install -e ., the package automatically:
- Sets up USB device permissions (copies udev rules to
/etc/udev/rules.d/) - Adds your user to the
plugdevgroup - Reloads udev rules
You may be prompted for your password during installation to complete the USB setup. After installation:
- Disconnect and reconnect your thermal camera
- Log out and log back in (or restart) for group changes to take effect
If you prefer to set up USB permissions manually, or if automatic setup didn't work:
# After installing the package
pythermal-setup-usbThis command will prompt for your password and set up USB permissions manually.
For a complete setup including system dependencies (FFmpeg libraries) and native compilation:
cd pythermal
./setup.shThis script will:
- Detect your system architecture (x86_64 or ARM) and install required system dependencies (FFmpeg libraries)
- Set up USB device permissions for the thermal camera
- Compile the native thermal recorder (
pythermal-recorder) for your architecture
Note: The native binaries are already included in the package, so compilation is only needed if you want to rebuild them.
Install directly on a Linux device (x86_64 or ARM, e.g., x86_64 desktop, Jetson, OrangePi, Raspberry Pi):
uv pip install pythermalAfter installation, run pythermal-setup-usb to set up USB permissions.
To enable YOLO v11 object and pose detection, install with the yolo extra:
uv pip install pythermal[yolo]Or install ultralytics separately:
pip install ultralytics>=8.0.0β Bundled Native Runtime The package ships with the native thermal recorder (
pythermal-recorder) and required shared libraries (.sofiles) for both x86_64 (pythermal/_native/linux64/) and ARM (pythermal/_native/armLinux/). The library automatically detects your system architecture and uses the appropriate binaries.
The ThermalCapture class provides a unified interface for both live camera feeds and recorded sequences. It's similar to cv2.VideoCapture and automatically handles device initialization.
from pythermal import ThermalCapture
# For live camera (default - uses smallest available device)
capture = ThermalCapture() # or ThermalCapture(0) or ThermalCapture(None)
# For specific device (by consistent device ID)
capture = ThermalCapture(device_index=1) # Use device with ID 1
# For recorded sequence
capture = ThermalCapture("recordings/thermal_20240101.tseq")
# Check for new frame
if capture.has_new_frame():
# Get metadata
metadata = capture.get_metadata()
print(f"Frame {metadata.seq}: {metadata.min_temp:.1f}Β°C - {metadata.max_temp:.1f}Β°C")
# Get YUYV frame (240x240)
yuyv_frame = capture.get_yuyv_frame()
# Get temperature array (96x96, uint16)
temp_array = capture.get_temperature_array()
# Mark frame as read
capture.mark_frame_read()
# Cleanup
capture.release()Or use as a context manager:
with ThermalCapture() as capture:
if capture.has_new_frame():
metadata = capture.get_metadata()
yuyv_frame = capture.get_yuyv_frame()
temp_array = capture.get_temperature_array()
capture.mark_frame_read()
# Automatically releases on exitDisplay real-time thermal imaging feed:
from pythermal import ThermalLiveView
# For live camera (default)
viewer = ThermalLiveView()
viewer.run() # Opens OpenCV window with live thermal feed
# For recorded sequence
viewer = ThermalLiveView("recordings/thermal_20240101.tseq")
viewer.run() # Replays recorded sequenceControls:
- Press
qto quit - Press
tto toggle between YUYV view and temperature view - Press
+/-to adjust contrast (CLAHE enhancement) - Move mouse over image to see temperature at cursor position
- Press
SPACEto pause/resume (for recorded sequences)
The live view displays room temperature estimation in the overlay, calculated using the 5th percentile method with smoothing.
from pythermal import ThermalRecorder
import time
rec = ThermalRecorder(output_dir="recordings", color=True)
rec.start() # Starts device and begins recording
rec.record_loop(duration=10) # Record for 10 seconds
rec.stop() # Stop recordingThis records both:
- Raw temperature frames (
96Γ96, uint16) - YUYV visual frames (
240Γ240) - Colored RGB frames (
240Γ240, uint8 RGB) ifcolor=True
Using the unified ThermalCapture interface (recommended):
from pythermal import ThermalCapture
capture = ThermalCapture() # Live camera, or pass file path for recorded data
# Check for new frame
if capture.has_new_frame():
# Get metadata
metadata = capture.get_metadata()
print(f"Frame {metadata.seq}: {metadata.min_temp:.1f}Β°C - {metadata.max_temp:.1f}Β°C")
# Get YUYV frame (240x240)
yuyv_frame = capture.get_yuyv_frame()
# Get temperature array (96x96, uint16)
temp_array = capture.get_temperature_array()
# Mark frame as read
capture.mark_frame_read()
capture.release()Multi-Device Usage
When multiple thermal cameras are connected, PyThermal automatically assigns consistent device IDs based on USB serial numbers. This ensures that the same physical device always gets the same ID, even after reconnection:
from pythermal import ThermalCapture
# Use device with ID 0 (first device, or smallest available)
capture0 = ThermalCapture(device_index=0)
# Use device with ID 1 (second device)
capture1 = ThermalCapture(device_index=1)
# If no device_index is specified, uses smallest available device ID
capture_auto = ThermalCapture() # Automatically selects smallest available device
# Each device uses separate shared memory:
# Device 0: /dev/shm/yuyv240_shm
# Device 1: /dev/shm/yuyv240_shm_1
# Device 2: /dev/shm/yuyv240_shm_2
# etc.Device Mapping
Device IDs are stored persistently in ~/.pythermal/device_mapping.json, mapping USB serial numbers to consistent device IDs. This ensures:
- Same device always gets the same ID (even after reboot)
- Device IDs remain stable across sessions
- Automatic selection of smallest available device when
device_index=None
Advanced: Direct Shared Memory Access
For advanced use cases, you can access the shared memory interface directly:
from pythermal import ThermalDevice, ThermalSharedMemory
# Use specific device (by consistent device ID)
device = ThermalDevice(device_index=1)
device.start()
shm = device.get_shared_memory()
if shm.has_new_frame():
metadata = shm.get_metadata()
yuyv_frame = shm.get_yuyv_frame()
temp_array = shm.get_temperature_array()
# Get temperature map in Celsius (96x96, float32)
temp_celsius = shm.get_temperature_map_celsius()
shm.mark_frame_read()
device.stop()Detect objects based on temperature ranges and visualize them with clustering:
from pythermal import ThermalCapture, detect_object_centers, cluster_objects
capture = ThermalCapture() # Live camera, or pass file path for recorded data
if capture.has_new_frame():
metadata = capture.get_metadata()
temp_array = capture.get_temperature_array()
# Detect objects in temperature range (default: 31-39Β°C for human body)
objects = detect_object_centers(
temp_array=temp_array,
min_temp=metadata.min_temp,
max_temp=metadata.max_temp,
temp_min=31.0, # Minimum temperature threshold
temp_max=39.0, # Maximum temperature threshold
min_area=50 # Minimum area in pixels
)
# Cluster nearby objects together
clusters = cluster_objects(objects, max_distance=30.0)
# Each object contains: center_x, center_y, width, height, area,
# avg_temperature, max_temperature, min_temperature
for obj in objects:
print(f"Object at ({obj.center_x:.1f}, {obj.center_y:.1f}): "
f"{obj.avg_temperature:.1f}Β°C")
capture.mark_frame_read()
capture.release()See examples/detect_objects.py for a complete visualization example. All examples support both live camera feeds and recorded sequences using the ThermalCapture interface.
Detect humans using adaptive temperature thresholds based on environment temperature:
from pythermal import ThermalCapture, detect_humans_adaptive
capture = ThermalCapture() # Live camera, or pass file path for recorded data
if capture.has_new_frame():
metadata = capture.get_metadata()
temp_array = capture.get_temperature_array()
# Adaptive detection (auto-estimates room temperature)
objects = detect_humans_adaptive(
temp_array=temp_array,
min_temp=metadata.min_temp,
max_temp=metadata.max_temp
)
# Or specify room temperature
objects = detect_humans_adaptive(
temp_array=temp_array,
min_temp=metadata.min_temp,
max_temp=metadata.max_temp,
environment_temp=22.0 # Known room temperature
)
capture.mark_frame_read()
capture.release()The adaptive detection uses the formula Ts = Te + Ξ± Γ (Tc β Te) where:
Ts= Skin temperature (estimated body temperature)Te= Environment temperatureTc= Core body temperature (37Β°C)Ξ±= Blood flow regulation coefficient (0.4-0.7 for face/torso)
Detect objects and human poses using YOLO v11 models:
from pythermal import ThermalCapture
from pythermal.detections.yolo import YOLOObjectDetector, YOLOPoseDetector
import cv2
capture = ThermalCapture() # Live camera, or pass file path for recorded data
# Initialize YOLO detectors (default models auto-download on first use)
object_detector = YOLOObjectDetector(model_size="nano") # Options: nano, small, medium, large, xlarge
pose_detector = YOLOPoseDetector(model_size="nano")
if capture.has_new_frame():
yuyv_frame = capture.get_yuyv_frame()
bgr_frame = cv2.cvtColor(yuyv_frame, cv2.COLOR_YUV2BGR_YUYV)
# Object detection
objects = object_detector.detect(bgr_frame)
for obj in objects:
print(f"Detected {obj['class_name']} with confidence {obj['confidence']:.2f}")
# Pose detection
poses = pose_detector.detect(bgr_frame)
for pose in poses:
print(f"Detected person with {len(pose['keypoints'])} keypoints")
# Visualize
vis_image = object_detector.visualize(bgr_frame, objects)
# or
vis_image = pose_detector.visualize(bgr_frame, poses)
capture.mark_frame_read()
capture.release()Place your custom YOLO v11 models (.pt files) in:
pythermal/pythermal/detections/yolo/models/
Quick usage:
# Use custom model from models directory
detector = YOLOObjectDetector(model_path="custom_thermal_object.pt")π For detailed instructions on finding the models directory, uploading custom models, training, and troubleshooting, see the YOLO Detection Guide.
See examples/yolo_object_detection.py and examples/yolo_pose_detection.py for complete examples.
PyThermal supports connecting and using multiple thermal cameras simultaneously. Each device is assigned a consistent device ID based on its USB serial number, ensuring stable identification across sessions and reboots.
-
Device Enumeration: When you connect a thermal camera, PyThermal queries the USB SDK to get the device's serial number.
-
Consistent ID Mapping: Each unique serial number is mapped to a consistent device ID (0, 1, 2, ...) stored in
~/.pythermal/device_mapping.json. -
Automatic Selection: If no
device_indexis specified, PyThermal automatically uses the smallest available device ID. -
Device-Specific Shared Memory: Each device uses its own shared memory segment:
- Device 0:
/dev/shm/yuyv240_shm - Device 1:
/dev/shm/yuyv240_shm_1 - Device 2:
/dev/shm/yuyv240_shm_2 - etc.
- Device 0:
Basic Usage:
from pythermal import ThermalCapture
# Automatically use smallest available device
capture = ThermalCapture()
# Use specific device by ID
capture0 = ThermalCapture(device_index=0)
capture1 = ThermalCapture(device_index=1)Parallel Operation:
from pythermal import ThermalCapture
import threading
def capture_from_device(device_id):
capture = ThermalCapture(device_index=device_id)
while True:
if capture.has_new_frame():
metadata = capture.get_metadata()
print(f"Device {device_id}: {metadata.avg_temp:.1f}Β°C")
capture.mark_frame_read()
# Capture from multiple devices in parallel
thread0 = threading.Thread(target=capture_from_device, args=(0,))
thread1 = threading.Thread(target=capture_from_device, args=(1,))
thread0.start()
thread1.start()Command-Line Usage:
# Use device 0 (default)
python examples/live_view.py
# Use device 1
python examples/live_view.py --device-index 1
# Record from device 0
python examples/record_thermal.py --duration 10
# Record from device 1
python examples/record_thermal.py --duration 10 --device-index 1The device mapping is stored in ~/.pythermal/device_mapping.json:
{
"SERIAL001": 0,
"SERIAL002": 1,
"SERIAL003": 2
}This ensures:
- Same physical device always gets the same ID
- Device IDs persist across reboots
- Easy identification of devices by serial number
-
Device ID changes after reconnection
- Check that USB serial numbers are being read correctly
- Verify
~/.pythermal/device_mapping.jsonexists and is writable - Try removing the mapping file and letting PyThermal recreate it
-
Cannot access multiple devices simultaneously
- Ensure each device uses a different
device_index - Check that shared memory segments are unique for each device
- Verify USB permissions are set up correctly for all devices
- Ensure each device uses a different
-
Wrong device selected
- Explicitly specify
device_indexinstead of relying on automatic selection - Check the device mapping file to see which serial number maps to which ID
- Use
enumerate_deviceshelper to list all connected devices
- Explicitly specify
| Command | Description |
|---|---|
pythermal-preview |
Live preview with temperature overlay |
pythermal-setup-usb |
Set up USB device permissions for thermal camera |
Examples:
# Live preview
pythermal-preview
# Set up USB permissions (if not done during install)
pythermal-setup-usbThe pythermal-setup-usb command will:
- Copy udev rules to
/etc/udev/rules.d/ - Add your user to the
plugdevgroup - Reload udev rules
After running, disconnect and reconnect your thermal camera, and log out/in for changes to take effect.
| Class | Purpose |
|---|---|
ThermalCapture |
Unified interface for live camera feeds and recorded sequences (recommended) |
ThermalDevice |
Manages thermal camera initialization via subprocess and shared memory access (advanced) |
ThermalSharedMemory |
Reads thermal data from shared memory (YUYV frames, temperature arrays, metadata) (advanced) |
ThermalSequenceReader |
Reads pre-recorded thermal sequences from .tseq files |
ThermalRecorder |
Records raw and colored frames to files |
ThermalLiveView |
Displays live thermal imaging feed with OpenCV |
FrameMetadata |
Named tuple containing frame metadata (seq, flag, dimensions, temperatures) |
| Class | Purpose |
|---|---|
DetectedObject |
Dataclass representing a detected object with center, size, and temperature stats |
BackgroundSubtractor |
Background subtraction for motion detection using running average |
ROI |
Region of Interest definition with optional temperature thresholds |
ROIManager |
Manages multiple ROIs for zone monitoring and filtering |
YOLOObjectDetector |
YOLO v11 object detector (requires ultralytics package) |
YOLOPoseDetector |
YOLO v11 pose/keypoint detector (requires ultralytics package) |
| Function | Purpose |
|---|---|
detect_object_centers |
Detect object centers from temperature map based on temperature range |
detect_humans_adaptive |
Adaptive human detection using environment temperature estimation |
detect_moving_objects |
Detect moving objects using background subtraction |
cluster_objects |
Cluster detected objects that are close to each other |
| Function | Purpose |
|---|---|
estimate_environment_temperature_v1 |
Estimate room temperature using 5th percentile method (with smoothing) |
estimate_body_temperature |
Estimate body (skin) temperature from environment temperature |
estimate_body_temperature_range |
Get temperature range for different body parts (hands/feet vs face/torso) |
| Function | Purpose |
|---|---|
calculate_aspect_ratio |
Calculate aspect ratio (width/height) of detected object |
calculate_compactness |
Calculate compactness (circularity approximation) |
calculate_circularity |
Calculate true circularity from contour |
calculate_convexity_ratio |
Calculate convexity ratio from contour |
filter_by_aspect_ratio |
Filter objects by aspect ratio |
filter_by_compactness |
Filter objects by compactness |
filter_by_area |
Filter objects by area (min/max) |
filter_by_shape |
Filter objects by multiple shape criteria |
| Method | Purpose |
|---|---|
YOLOObjectDetector.detect() |
Detect objects in image, returns list of detections with bbox, class, confidence |
YOLOObjectDetector.visualize() |
Draw bounding boxes and labels on image |
YOLOPoseDetector.detect() |
Detect poses/keypoints in image, returns list of poses with 17 keypoints |
YOLOPoseDetector.visualize() |
Draw skeleton, keypoints, and bounding boxes on image |
- Python β₯ 3.9
- Linux environment (x86_64 or ARM, e.g., x86_64 desktop, Jetson, OrangePi, Raspberry Pi)
- NumPy, OpenCV (auto-installed via pip)
- Thermal camera connected via USB
- Proper USB permissions (automatically set up during
pip install -e ., or manually viapythermal-setup-usb)
- ultralytics β₯ 8.0.0: Required for YOLO v11 detection features
pip install ultralytics # or pip install pythermal[yolo]
The library uses a native binary (pythermal-recorder) that runs as a separate process and writes thermal data to shared memory (/dev/shm/yuyv240_shm). The Python library communicates with this process via shared memory for efficient zero-copy data access.
The package includes native files for both x86_64 and ARM architectures:
x86_64 (pythermal/_native/linux64/):
pythermal/_native/linux64/
βββ pythermal-recorder # Main thermal recorder executable
βββ libHCUSBSDK.so # Hikvision USB SDK library
βββ libhpr.so # Hikvision processing library
βββ libusb-1.0.so* # USB library dependencies
βββ libuvc.so # UVC library
ARM (pythermal/_native/armLinux/):
pythermal/_native/armLinux/
βββ pythermal-recorder # Main thermal recorder executable
βββ libHCUSBSDK.so # Hikvision USB SDK library
βββ libhpr.so # Hikvision processing library
βββ libusb-1.0.so* # USB library dependencies
βββ libuvc.so # UVC library
The library automatically detects your system architecture and loads the appropriate binaries.
Each device uses its own shared memory segment:
- Device 0:
/dev/shm/yuyv240_shm - Device 1:
/dev/shm/yuyv240_shm_1 - Device 2:
/dev/shm/yuyv240_shm_2 - etc.
The shared memory (/dev/shm/yuyv240_shm or /dev/shm/yuyv240_shm_{device_id}) contains:
Offset Size Content
0 FRAME_SZ YUYV frame data (240Γ240Γ2 bytes)
FRAME_SZ TEMP_DATA_SIZE Temperature array (96Γ96Γ2 bytes, uint16)
FRAME_SZ+TEMP ... Metadata:
- seq (4 bytes, uint32)
- flag (4 bytes, uint32, 1=new, 0=consumed)
- width (4 bytes, uint32)
- height (4 bytes, uint32)
- min_temp (4 bytes, float)
- max_temp (4 bytes, float)
- avg_temp (4 bytes, float)
- reserved (4 bytes)
The ThermalDevice class:
- Starts
pythermal-recorderas a subprocess - Waits for shared memory to become available
- Provides access to thermal data via
ThermalSharedMemory - Automatically cleans up the process on exit
-
FileNotFoundError: pythermal-recorder not foundMake sure you've runsetup.shto compile the native binaries, and that the package was installed correctly. -
PermissionError: pythermal-recorder is not executableRunchmod +xon the executable, or reinstall the package. -
TimeoutError: Shared memory did not become available- Check that the thermal camera is connected via USB
- Verify USB permissions are set up correctly (run
pythermal-setup-usborsetup.sh) - Try disconnecting and reconnecting the camera
- Log out and log back in (or restart) after setting up USB permissions
- Check that no other process is using the thermal camera
-
RuntimeError: Thermal recorder process exited unexpectedlyCheck the process output for error messages. Common issues:- Camera not detected
- Missing USB permissions (run
pythermal-setup-usbto fix) - Missing shared libraries (check
LD_LIBRARY_PATH)
-
USB Permission Issues
- If you get permission errors accessing the thermal camera:
- Run
pythermal-setup-usb(orsudo pythermal-setup-usb) - Disconnect and reconnect your thermal camera
- Log out and log back in (or restart your system)
- Verify with
lsusbthat your camera is detected
- Run
- If you get permission errors accessing the thermal camera:
-
ImportError: ultralytics package is required for YOLO detectionInstall the ultralytics package:pip install ultralytics # or pip install pythermal[yolo] -
FileNotFoundError: Model file not found- For custom models, ensure the
.ptfile is inpythermal/pythermal/detections/yolo/models/ - Or provide the absolute path to the model file
- Default models are automatically downloaded on first use (check internet connection)
- See YOLO Detection Guide for detailed troubleshooting
- For custom models, ensure the
pythermal/
βββ pythermal/
β βββ __init__.py
β βββ core/ # Core thermal camera components
β β βββ __init__.py
β β βββ device.py # ThermalDevice class (manages subprocess)
β β βββ thermal_shared_memory.py # Shared memory reader
β β βββ sequence_reader.py # ThermalSequenceReader for recorded files
β β βββ capture.py # ThermalCapture unified interface
β βββ record.py # ThermalRecorder class
β βββ live_view.py # ThermalLiveView class
β βββ detections/ # Object detection module
β β βββ __init__.py
β β βββ utils.py # Shared utilities and shape analysis
β β βββ temperature_detection.py # Temperature-based detection
β β βββ motion_detection.py # Background subtraction and motion detection
β β βββ roi.py # ROI management and zone monitoring
β β βββ yolo/ # YOLO v11 detection module
β β βββ __init__.py
β β βββ object_detection.py # YOLO object detection
β β βββ pose_detection.py # YOLO pose detection
β β βββ models/ # Custom thermal models directory
β β βββ README.md # Instructions for custom models
β βββ usb_setup/ # USB setup scripts (included in package)
β β βββ setup.sh # USB permissions setup script
β β βββ setup-thermal-permissions.sh
β β βββ 99-thermal-camera.rules # udev rules file
β βββ _native/
β βββ linux64/ # x86_64 binaries
β β βββ pythermal-recorder
β β βββ *.so # Native libraries
β βββ armLinux/ # ARM binaries
β βββ pythermal-recorder
β βββ *.so # Native libraries
βββ examples/ # Example scripts
β βββ live_view.py
β βββ record_thermal.py
β βββ detect_objects.py # Object detection visualization example
β βββ detect_motion.py # Motion detection example
β βββ detect_roi.py # ROI zone monitoring example
β βββ yolo_object_detection.py # YOLO object detection example
β βββ yolo_pose_detection.py # YOLO pose detection example
βββ setup.sh # Full setup script (permissions, dependencies, compilation)
βββ setup.py # Python package setup (includes automatic USB setup)
βββ README.md
@inproceedings{zeng2025thermikit,
title={ThermiKit: Edge-Optimized LWIR Analytics with Agent-Driven Interactions},
author={Zeng, Lan and Huang, Chunhao and Xie, Ruihan and Huang, Zhuohan and Guo, Yunqi and He, Lixing and Xie, Zhiyuan and Xing, Guoliang},
booktitle={Proceedings of the 2025 ACM International Workshop on Thermal Sensing and Computing},
pages={40--46},
year={2025}
}
This library is released under the Apache 2.0 License for research and non-commercial use.
Only the compiled native library (.so) is shipped; no vendor source or headers are distributed.
π« Developed by AIoT Lab, CUHK