diff options
-rw-r--r-- | run_tests.py | 30 | ||||
-rw-r--r-- | src/infiray_irg.py | 64 |
2 files changed, 69 insertions, 25 deletions
diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..03a358a --- /dev/null +++ b/run_tests.py @@ -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') diff --git a/src/infiray_irg.py b/src/infiray_irg.py index c6a583a..400febb 100644 --- a/src/infiray_irg.py +++ b/src/infiray_irg.py @@ -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. |