Skip to content

Commit bd3de29

Browse files
authored
add experimental thermal camera streaming support (#12)
## What does this PR do? <!-- Brief description of the change --> ## Why? <!-- What problem does this solve? Link to issue if applicable. --> Closes # ## Checklist - [ ] Tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) - [ ] New code has type hints - [ ] Updated CHANGELOG.md (if user-facing change) - [ ] Tested on target hardware (if applicable)
1 parent 93b6782 commit bd3de29

9 files changed

Lines changed: 903 additions & 4 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,9 @@ htmlcov/
5252
# Local config
5353
.env
5454
.env.local
55+
56+
# macOS
57+
.DS_Store
58+
59+
# Logs
60+
*.log

examples/thermal_camera.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Thermal camera streaming to Plexus.
3+
4+
Reads from any supported thermal camera and sends frames to Plexus using
5+
the SDK's send_thermal_frame() method. The gateway relays colorized JPEG
6+
frames to the app, along with temperature range and (for small sensors)
7+
per-pixel temperature data.
8+
9+
Supported hardware (--camera argument):
10+
sim Simulated camera — no hardware needed (default)
11+
mlx90640 MLX90640 32×24 I2C sensor (pip install adafruit-circuitpython-mlx90640)
12+
mlx90641 MLX90641 16×12 I2C sensor (pip install adafruit-circuitpython-mlx90641)
13+
usb USB thermal camera in Y16 format (InfiRay, Topdon, Seek, etc.)
14+
15+
Run:
16+
export PLEXUS_API_KEY=plx_xxx
17+
python thermal_camera.py
18+
python thermal_camera.py --camera mlx90640
19+
python thermal_camera.py --camera usb --device 2
20+
"""
21+
22+
import argparse
23+
import sys
24+
import time
25+
26+
from plexus import Plexus
27+
from plexus.cameras.thermal import NoCameraFound, ThermalSource
28+
29+
CAMERA_ID = "thermal"
30+
FPS = 5
31+
32+
33+
def main() -> None:
34+
parser = argparse.ArgumentParser(description="Stream thermal camera to Plexus.")
35+
parser.add_argument(
36+
"--camera",
37+
choices=["sim", "mlx90640", "mlx90641", "usb"],
38+
default=None,
39+
help="Camera driver (default: auto-detect)",
40+
)
41+
parser.add_argument(
42+
"--device",
43+
type=int,
44+
default=0,
45+
help="USB video device index (default: 0)",
46+
)
47+
args = parser.parse_args()
48+
49+
hint = args.device if args.camera == "usb" else args.camera
50+
try:
51+
cam = ThermalSource.open(hint)
52+
except NoCameraFound as e:
53+
print(f"Error: {e}", file=sys.stderr)
54+
sys.exit(1)
55+
56+
px = Plexus(transport="ws")
57+
px.wait_connected()
58+
59+
interval = 1.0 / FPS
60+
frame_count = 0
61+
print(f"Streaming {cam.width}×{cam.height} thermal at {FPS} fps — Ctrl-C to stop")
62+
63+
try:
64+
while True:
65+
t0 = time.time()
66+
67+
temps = cam.read_frame()
68+
px.send_thermal_frame(temps, camera_id=CAMERA_ID)
69+
70+
frame_count += 1
71+
if frame_count % 50 == 0:
72+
print(f" {frame_count} frames sent")
73+
74+
elapsed = time.time() - t0
75+
wait = interval - elapsed
76+
if wait > 0:
77+
time.sleep(wait)
78+
79+
except KeyboardInterrupt:
80+
pass
81+
finally:
82+
cam.close()
83+
px.stop()
84+
print(f"Done. {frame_count} frames sent.")
85+
86+
87+
if __name__ == "__main__":
88+
main()

plexus/cameras/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from plexus.cameras.thermal import (
2+
NoCameraFound,
3+
MLX90640Camera,
4+
MLX90641Camera,
5+
SimulatedThermalCamera,
6+
ThermalCamera,
7+
ThermalFrame,
8+
ThermalSource,
9+
USBThermalCamera,
10+
build_thermal_frame,
11+
encode_frame,
12+
)
13+
14+
__all__ = [
15+
"NoCameraFound",
16+
"MLX90640Camera",
17+
"MLX90641Camera",
18+
"SimulatedThermalCamera",
19+
"ThermalCamera",
20+
"ThermalFrame",
21+
"ThermalSource",
22+
"USBThermalCamera",
23+
"build_thermal_frame",
24+
"encode_frame",
25+
]

0 commit comments

Comments
 (0)