A Python library for reading and analyzing Fluke thermal imaging files: full support for .is2 (still images), partial support for .is3 (video — container and metadata; the thermal video codec itself is proprietary and undocumented, see below).
Package: fluke-thermal-reader · Import: import fluke_thermal_reader or from fluke_thermal_reader import read_is2
- .is2 reading: Full parsing of Fluke thermal imaging files (.is2)
- .is3 reading (partial): extracts the visible-light H.264 video track and camera/calibration metadata from Fluke thermal video files; see read_is3 below
- Temperature conversion: Raw counts to temperature (°C) with emissivity and reflected background correction (radiative formula)
- Metadata: Camera model, dimensions, emissivity, transmission, background temperature, min/max/avg from file and from JSON when present
- Command-line interface:
fluke_thermal_reader <file> [--info|--stats|--export-csv]after installing - Minimal dependencies: Only
numpy - Tested and working: Fluke Ti480P, Ti300, TiS75+; PTi120 parses but is unverified (see below)
pip install fluke-thermal-readerfrom fluke_thermal_reader import read_is2
# Load a .is2 file
data = read_is2("thermal_image.is2")
# Thermal matrix (2D, °C)
thermal_data = data["data"]
print(f"Temperature range: {thermal_data.min():.1f}°C - {thermal_data.max():.1f}°C")
# Metadata
print(f"Camera: {data['CameraModel']}")
print(f"Size: {data['size']}") # [width, height]
print(f"Emissivity: {data['Emissivity']}")
print(f"Background temperature: {data['BackgroundTemp']}°C")import matplotlib.pyplot as plt
from fluke_thermal_reader import read_is2
data = read_is2("thermal_image.is2")
plt.imshow(data["data"], cmap="coolwarm", aspect="equal")
plt.colorbar(label="Temperature (°C)")
plt.title(f"Thermal image — {data['CameraModel']}")
plt.show()A more complete, ready-to-run example is provided in basic_usage_example.py at the repository root.
It will:
- Ask you to select a
.is2file via a file dialog - Print basic metadata and temperature statistics
- Show the thermal image with a blue→red colormap and markers for the coldest (MIN) and hottest (MAX) pixels
python basic_usage_example.py| Key | Type | Description |
|---|---|---|
data |
2D ndarray | Temperature in °C per pixel |
FileName |
str | File name |
CameraModel |
str | Thermal camera model |
CameraSerial |
str | Serial number |
size |
[w, h] | Image dimensions |
MinTemp, MaxTemp, AvgTemp |
float | From file/JSON when present |
Emissivity |
float | Emissivity |
Transmission |
float | Transmission |
BackgroundTemp |
float | Background temperature |
thumbnail_path |
str / None | Thumbnail path (if present) |
photo_path |
str / None | Visible photo path (if present) |
- Python 3.8+
numpy >= 1.20.0
For visualization: matplotlib (optional).
Tested and working with:
- Fluke Ti480P
- Fluke Ti300
- Fluke TiS75+ — verified against 4 reference exports (mean error 0.4-4.4°C depending on file). Its file format has no real embedded calibration curve or usable temperature range, so readings use a fixed slope (stable across all 4 references) plus the file's own background-temperature field as the best available per-file offset — not a perfect per-file calibration, but close.
- Fluke PTi120 — verified against a reference export (mean error ~0.07°C). Its calibration data has no real embedded curve, so temperature comes from a fixed linear scale fit against that one reference rather than from data in the file itself — accuracy on other units/scenes isn't guaranteed; feedback (and more reference exports) welcome if you hit an inaccurate reading.
Other Fluke .is2 files may work; feedback and sample files for additional models are welcome.
Fluke_Python/
├── fluke_thermal_reader/ # Main package
│ ├── __init__.py
│ ├── reader.py # read_is2, read_is3, FlukeReader
│ ├── parsers.py # IS2 parser
│ ├── is3_parser.py # IS3 (Matroska video) parser — visible track + metadata only
│ ├── camera_profiles.py # per-model profile registry
│ ├── utilities.py # UnitConversion, calc_equation
│ └── cli.py
├── docs/
│ └── is3_video_codec_notes.md # V_FLUKE/HUFF reverse-engineering notes
├── basic_usage_example.py # Full example script (CLI + plot)
├── requirements.txt
└── README.md
Installing the package (pip install fluke-thermal-reader or an editable install, see below)
provides a fluke_thermal_reader command:
fluke_thermal_reader thermal_image.is2 --info --stats
fluke_thermal_reader thermal_image.is2 --export-csv output.csv
fluke_thermal_reader thermal_video.is3 --infoWithout any flags it prints a short summary (image size and average temperature, or video
duration/frame count for .is3).
# Editable install with dev dependencies (pytest, black, flake8, mypy)
pip install -e ".[dev]"
# Run the test suite
pytest -v
# Run a single test
pytest tests/test_reader.py::test_read_is2_file_not_found
# Format / lint / type-check
black .
flake8
mypy fluke_thermal_readerSee Publish_to_PiPy.md for the release process.
.is3 files are Matroska (MKV) video containers with two tracks: a standard H.264 visible-light
video, and a thermal track using a proprietary, undocumented codec (V_FLUKE/HUFF).
from fluke_thermal_reader import read_is3
video = read_is3("thermal_video.is3")
print(video["CameraModel"], video["FrameCount"], video["Duration"])
print(video["visible_video_path"]) # extracted H.264 elementary stream (mux with ffmpeg if needed)- Visible-light video: extracted and written out as a raw H.264 (
.h264) file next to the source file (or tooutput_dirif passed toread_is3). Mux to.mp4with e.g.ffmpeg -i video_visible.h264 -c copy video.mp4. - Camera model / calibration metadata: extracted from the container's attachment.
- Thermal data: not implemented.
video["thermal_data"]is alwaysNone;video["thermal_status"]explains why (V_FLUKE/HUFFhas no known public specification). Seedocs/is3_video_codec_notes.mdfor a detailed writeup of what's been reverse-engineered so far — the container and per-frame table format are understood, but not the codec itself.
If you can help identify the V_FLUKE/HUFF format (an SDK, a spec, a reference decoder), please open
an issue.
See the LICENSE file in the repository.
- Fix (correctness, TiS75+): TiS75+ thermal frames were being extracted with the wrong strategy
(a raw-uint16-blob fallback instead of the varint-protobuf format it actually uses, the same format
as PTi120) and, separately, any negative decoded value was treated as an invalid pixel instead of
only the real
-1sentinel — together these produced 60-77°C mean errors against reference exports while still "looking" plausible (a smooth 22-95°C-ish image). Both are fixed; remaining error after the fix is 0.4-4.4°C, limited by the lack of a real per-file calibration in the format itself (seeCLAUDE.md). This is why "looks plausible" was never sufficient validation for this file format, and TiS75+ regression tests with real reference exports are now part of the test suite. - Fix (correctness):
CalibrationData.gpbenccan contain multiple calibration tables (e.g. standard vs. extended high-temperature range); the parser now picks the correct one instead of silently merging them, which previously produced errors of 300°C+ on Ti300 files with a near-ambient scene. A similar mismatch is still unresolved on at least one Ti480P case — seeCLAUDE.mdfor details if you hit this. - Fix: image dimensions could be inferred from the wrong bundled JPEG (a full-resolution visible-light photo instead of the IR-registered image) when no metadata was present, corrupting the parsed size.
- Fix: the
fluke_thermal_readercommand-line interface was broken (written against an unused dataclass API); it now works against the same dictread_is2()/read_is3()return, and is installed as a real console-script entry point (pip installgives you thefluke_thermal_readercommand, not justpython -m fluke_thermal_reader.cli). - Add: partial
.is3(video) support —read_is3()extracts the visible-light H.264 track and camera/calibration metadata. Thermal video decoding is not implemented: the codec (V_FLUKE/HUFF) is proprietary and undocumented; seedocs/is3_video_codec_notes.mdfor what's been reverse-engineered so far. - Add: initial support for Fluke PTi120 (
CalTempDataRex.gpbencstored as a real protobuf message with an unpacked repeated-varint field, rather than a rawuint16blob). Temperature accuracy verified against a reference export (mean error ~0.07°C) using a fixed linear scale fit to that reference, since the file itself has no real embedded calibration curve — see theTested camera modelssection above. - Fix: a Ti300 sample with a small overexposed highlight had a ~1.6°C mean / ~23°C peak error from an unresolved calibration-curve edge case near the top of its range; not root-caused, but the regression test now tracks it explicitly instead of silently allowing it to grow.
- Perf: vectorized the calibration lookup-table construction with numpy.
- Packaging: fixed the repository so
pyproject.toml/MANIFEST.in/release scripts are actually tracked and installable from a clean checkout (previously excluded by a.gitignoremistake).
- Stable .is2 parser with temperature conversion (emissivity + background temperature)
- Initial release, basic .is2 support