A minimal, dependency-light Python interface for the Thunder Optics O-series spectrometer (O / OR / ORT1 / ORT2) together with a live-view GUI for real-time measurements, dark/reference handling, and sensitivity calibration against known illuminants.
Brought to you by M.E.S.S. — Mattern Engineering & Software Solutions.
- USB spectrometer communication — direct bulk transfer via PyUSB /
libusbK, implementing the documented
AA 55 | Len | Cmd | Data | Chkframe protocol. - Python interface — a thin
ThunderSpectrometerclass withacquire(integration_ms), pixel count, and wavelength-polynomial calibration read from the device. - Live spectrum view GUI — matplotlib-based, with:
- integration-time and averaging sliders
- pause / auto-Y / log-Y / peak-finder toggles
- dark capture, reference capture, transmission / absorbance modes
- overlay of literature reference curves (D65, AM1.5G, CIE-A, blackbodies)
- one-click instrument-response calibration against a known illuminant
- CSV export of the currently displayed spectrum
No database, no framework, no extras. Three files of logic, one GUI, one driver class.
Requires Python 3.10+.
Recommended: uv
uv gives you a fully reproducible environment from uv.lock and is
significantly faster than pip on slow storage (SD cards, etc.).
git clone <repo-url>
cd Spectrometer_Handheld
uv sync
uv run python main.pyThat's it. uv sync creates .venv/, installs the exact pinned versions
from uv.lock, and uv run picks up that environment automatically.
Install uv itself via
curl -LsSf https://astral.sh/uv/install.sh | sh (Linux / macOS) or
powershell -c "irm https://astral.sh/uv/install.ps1 | iex" (Windows).
python -m venv .venv
# Windows:
.venv\Scripts\activate
# Linux / macOS:
source .venv/bin/activate
pip install -r requirements.txtrequirements.txt is autogenerated from uv.lock and pins the same
versions. Regenerate with uv export --no-hashes --no-annotate -o requirements.txt.
On Linux / macOS you also need a libusb runtime
(sudo apt install libusb-1.0-0 / brew install libusb).
On Windows, see the driver note below.
On Windows 11 the stock driver delivered with the device may fail to
install (typically a 32/64-bit packaging conflict). The working fallback
is to bind libusbK to both USB interfaces (MI_00 and MI_01) of
the device using Zadig:
- Run Zadig as Administrator, enable Options → List All Devices.
- Select
optosky (Interface 0), choose libusbK, install. - Select
optosky (Interface 1), choose libusbK, install.
Device IDs: VID 0x0483 / PID 0x6666.
After that, both vendor software and this Python interface work.
A fully screenshotted walkthrough is in
USB_Problem/win11_driver_fix.md.
uv run python main.pyor, if you prefer plain venv:
python main.py
# or equivalently
python spectrum_viewer.pyKeyboard shortcuts inside the viewer:
| Key | Action |
|---|---|
| space | Pause / resume |
d |
Dark on/off |
r |
Reference on/off |
b |
Cycle library curve |
k |
Calibrate / clear |
m |
Cycle display mode |
l |
Log Y on/off |
p |
Peak labels on/off |
a |
Auto-Y on/off |
c |
Save current CSV |
q |
Quit |
from thunder_spectro import ThunderSpectrometer
with ThunderSpectrometer() as spec:
print("Pixels :", spec.pixel_count)
print("Coefficients:", spec.coefficients)
print(f"Wavelength : {spec.wavelengths[0]:.2f} ... "
f"{spec.wavelengths[-1]:.2f} nm")
spectrum = spec.acquire(integration_time_ms=10)
print("min/max/mean:", spectrum.min(), spectrum.max(), spectrum.mean())spec.wavelengths is a numpy array matching spectrum pixel-for-pixel,
computed from the cubic polynomial the device reports at startup.
Spectrometer_Handheld/
├── main.py # entry point (runs the viewer)
├── spectrum_viewer.py # matplotlib live-view GUI
├── thunder_spectro.py # USB driver (PyUSB)
├── reference_spectra.py # D65 / AM1.5G / CIE-A / blackbody curves
├── pyproject.toml # project metadata + dependency spec (uv)
├── uv.lock # exact version pins for reproducible installs
├── requirements.txt # pip fallback, autogenerated from uv.lock
├── LICENSE # MIT
├── README.md
└── USB_Problem/
├── win11_driver_fix.md # Zadig / libusbK workaround walkthrough
└── screenshots/
Early-stage, experimental, but functional.
- Tested on Windows 11 (64-bit) against a Thunder Optics ORT1 (product page) with Hamamatsu S11639 sensor (2048 px, ~350–1050 nm).
- Acquisition, dark subtraction, reference capture, transmission, absorbance, and sensitivity calibration all work end-to-end.
- Interfaces are intentionally minimal so the protocol stays easy to read and extend.
- Windows driver quirk — libusbK must be bound manually via Zadig the first time.
- Hardware-dependent — the driver targets Thunder Optics O-series
devices (
VID 0x0483 / PID 0x6666). Other vendors using the same STM32 VID will not be compatible. - Qualitative library curves — the included D65 / AM1.5G / CIE-A / blackbody curves are meant for visual comparison and single-point sensitivity calibration. For quantitative photometry you must supply your own calibrated reference source.
- No scipy dependency — the peak finder is deliberately simple (local-max + prominence). Fine for live display, not a replacement for proper peak fitting.
- Optional CLI batch-acquisition (no GUI) for headless logging.
- Better peak fitting (Gaussian / Voigt) behind an optional scipy dependency.
- Re-usable calibration files (save/load
response_*.csvdirectly from the GUI, not only on every Calib press). - Linux / macOS smoke tests.
- Raspberry Pi kiosk mode (fullscreen auto-start via systemd user service).
Contributions via issues / PRs are welcome — small, focused changes preferred.
Released under the MIT License. See LICENSE.
Developed and maintained by Philipp Mattern Mattern Engineering & Software Solutions (M.E.S.S.) — mess.engineering
