Fix offset issues.

This commit is tested against all test files I have available. We tried
to parse the temperature array offset from the file to match that one
Autel device, but that ended up breaking other stuff. It seems the
offset is not explicitly recorded.

TODO: Figure out if they are using 273.0 or 273.15 K.
This commit is contained in:
jaseg 2025-02-05 20:27:59 +01:00
parent 9c2ed1c70c
commit 176c1ccac2
2 changed files with 69 additions and 25 deletions

30
run_tests.py Normal file
View file

@ -0,0 +1,30 @@
from pathlib import Path
import infiray_irg
test_files = sorted(Path('test_pictures').glob('*.irg'))
for f in test_files:
try:
coarse, fine, vis = infiray_irg.load(f.read_bytes(), print_debug_information=True)
print('\033[93m', f'{f.name:>20}', 'Coldest pixel:', fine.min(), 'C', 'Hottest pixel:', fine.max(), 'C', '\033[0m')
except Exception as e:
print(f'Error parsing {f}')
raise e
print()
print('Header diffs:')
for offset, legend in {
0: 'magc hlen coars_len yres xres flg0 unk1 zero fine unk2 jpeg_len_ yres xres emissivit fine_off1 fine_off2 distance_',
64: ' unit gain'}.items():
print(f'{" ":>20}', legend)
test_headers = {f: f.read_bytes()[offset:offset+64] for f in test_files}
header_idx_diffs = [len(set(header[i] for header in test_headers.values())) > 1 for i in range(64)]
for f, header in test_headers.items():
print(f'{str(f.name):>20}', ' '.join(
''.join(
(f'\033[91m{header[i]:02x}' if header_idx_diffs[i] else f'\033[0m{header[i]:02x}')
for i in range(chunk, chunk+2))
for chunk in range(0, 64, 2)
)+'\033[0m')

View file

@ -6,7 +6,7 @@ import io
__version__ = "1.4.0"
def load(data):
def load(data, print_debug_information=False):
def consume(n):
nonlocal data
out, data = data[:n], data[n:]
@ -14,7 +14,18 @@ def load(data):
raise ValueError(f'File is truncated, tried to read {n} bytes, but only {len(out)} bytes remain.')
return out
header = consume(128)
header = consume(4)
_magic, header_len = struct.unpack('<HH', header)
header += consume(header_len - 4)
if print_debug_information:
import binascii
c200_reference_header = binascii.unhexlify('caac800000c000000001c00000008001000001c00001738300000001c0001c25000010a8290010a82900c4090000a00f000010270000000000001027000000000000000000000204000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acca')
print('[infiray_irg debug] Header hex:', binascii.hexlify(header).decode()[:512])
print('[infiray_irg debug] C200 diff:', ''.join(
(f'\033[0m{b:02x}' if a == b else f'\033[91m{b:02x}') for a, b in zip(header, c200_reference_header)
) + '\033[0m')
for model, match in {
'c201': bytes([0xca, 0xac]),
'other': bytes([0xba, 0xab]),
@ -25,40 +36,48 @@ def load(data):
else:
raise ValueError(f'Header magic not found. Got header: {header[0]:02x} {header[1]:02x}')
_unk0, coarse_section_length, y_res, x_res,\
coarse_section_length, y_res, x_res,\
flag0, _unk1, _zero1, fine_offset, _unk2, jpeg_length,\
y_res_2, x_res_2, _unk3, = struct.unpack('<HIHHHHHHHIHHI', header[2:34])
y_res_2, x_res_2, emissivity = struct.unpack('<IHHHHHHHIHHI', header[4:34])
emissivity /= 1e4
fine_temp_offset1, fine_temp_offset2, *rest, high_gain_mode_flag = struct.unpack('<11I', header[34:78])
# unit_flag indicates which temperature unit the UI was using. It does not seem to affect the image data in this
# file.
fine_temp_offset1, fine_temp_offset2, distance, *rest, unit_flag, high_gain_mode_flag = struct.unpack('<9IHHI', header[34:78])
distance /= 1e4
if fine_temp_offset1 != fine_temp_offset2:
warnings.warn(f'File lists two different zero offsets for the fine image data {fine_temp_offset1} and {fine_temp_offset2}. Resulting radiometric data might be offset. Please report this with an example file to code@jaseg.de.')
fine_temp_offset = fine_temp_offset1 / 10000
# import textwrap
# print(textwrap.dedent(f'''
# {_unk0=}, {coarse_section_length=}, {y_res=}, {x_res=},
# {flag0=}, {_unk1=}, {_zero1=}, {fine_offset=}, {_unk2=}, {jpeg_length=},
# {y_res_2=}, {x_res_2=}, {_unk3=}
# {fine_temp_offset1=} {fine_temp_offset1=} {rest=}, {high_gain_mode_flag=}'''))
if print_debug_information:
print('[infiray_irg debug] Matched model:', model)
import textwrap
print(textwrap.dedent(f'''
[infiray_irg debug] {header_len=}, {coarse_section_length=}, {y_res=}, {x_res=},
[infiray_irg debug] {flag0=}, {_unk1=}, {_zero1=}, {fine_offset=}, {_unk2=}, {jpeg_length=},
[infiray_irg debug] {y_res_2=}, {x_res_2=}, {emissivity=} {distance=}
[infiray_irg debug] {fine_temp_offset1=} {fine_temp_offset1=} {rest=}, {high_gain_mode_flag=} {unit_flag=}'''))
if x_res*y_res != coarse_section_length:
raise ValueError('Resolution mismatch in header')
vis_jpg = None
coarse_img = np.frombuffer(consume(coarse_section_length), dtype=np.uint8).reshape((y_res, x_res))
fine_img = np.frombuffer(consume(x_res*y_res*2), dtype=np.uint16).reshape((y_res, x_res))
if model == 'c201':
if header[-2:] != bytes([0xac,0xca]):
raise ValueError(f'Header end marker not found. Got header: {header[-2]:02x} {header[-1]:02x}')
coarse_img = np.frombuffer(consume(coarse_section_length), dtype=np.uint8).reshape((y_res, x_res))
fine_img = np.frombuffer(consume(x_res*y_res*2), dtype=np.uint16).reshape((y_res, x_res))
if flag0 == 1: # Seen in Autel Robotics Evo II Dual 640T V3 file
fine_img = (fine_img / 64) - fine_temp_offset
fine_img = (fine_img / 64) - 25.0
else: # C201 files
# 1/16th Kelvin steps
fine_img = (fine_img / 16) - fine_temp_offset
fine_img = (fine_img / 16) - 273.15
# The offset for low gain mode images is a bit unclear. It seems all readings around room temperature and
# below in low gain mode are kind of garbage.
if jpeg_length > 0:
# I have seen a file from an Autel Robotics Evo II Dual 640T V3 that looks like a C201 file, but lacks the
@ -66,20 +85,15 @@ def load(data):
vis_jpg = Image.open(io.BytesIO(consume(jpeg_length)))
elif model == 'other':
if header[-2:] != bytes([0xab,0xba]):
if header[-2:] != bytes([0xac,0xca]):
raise ValueError(f'Header end marker not found. Got header: {header[-2]:02x} {header[-1]:02x}')
coarse_img = np.frombuffer(consume(coarse_section_length), dtype=np.uint8).reshape((y_res, x_res))
# 0.1 Kelvin steps
fine_img = np.frombuffer(consume(x_res*y_res*2), dtype=np.uint16).reshape((y_res, x_res))
fine_img = fine_img / 10 - fine_temp_offset
fine_img = fine_img / 10 - 273.15
vis_jpg = Image.open(io.BytesIO(data))
else:
header += consume(128)
coarse_img = np.frombuffer(consume(coarse_section_length), dtype=np.uint8).reshape((y_res, x_res))
fine_img = np.frombuffer(consume(x_res*y_res*2), dtype=np.uint16).reshape((y_res, x_res))
fine_img = fine_img / 10 - fine_temp_offset
fine_img = fine_img / 10 - 273.2
# In my example file, data now contains the JSON '{"roi":[]}' and no JPG. We ignore that.