146 lines
5.8 KiB
Python
146 lines
5.8 KiB
Python
"""Formatting helpers for ImageLeaderData and nested metadata structures.
|
|
|
|
Provides `format_image_leader(leader)` which returns a human-readable dict
|
|
and `pretty_print_image_leader(leader)` which returns a multiline string.
|
|
"""
|
|
from typing import Any, Dict, Optional
|
|
import math
|
|
|
|
|
|
def _safe_get(obj: Any, attr: str, default: Any = None) -> Any:
|
|
try:
|
|
return getattr(obj, attr)
|
|
except Exception:
|
|
return default
|
|
|
|
|
|
def _bytes_to_hex(b: Any) -> str:
|
|
try:
|
|
# supports ctypes arrays of c_ubyte or bytes
|
|
return ''.join(f"{int(x):02X}" for x in b)
|
|
except Exception:
|
|
try:
|
|
return bytes(b).hex().upper()
|
|
except Exception:
|
|
return str(b)
|
|
|
|
|
|
def _rad_to_deg(rad: Optional[float]) -> Optional[float]:
|
|
if rad is None:
|
|
return None
|
|
try:
|
|
return float(rad) * (180.0 / math.pi)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def format_image_leader(leader: Any) -> Dict[str, Any]:
|
|
"""Return a dict with human-friendly fields extracted from `leader`.
|
|
|
|
Accepts either a ctypes ImageLeaderData-like object or a dict-like structure.
|
|
"""
|
|
out: Dict[str, Any] = {}
|
|
if leader is None:
|
|
return out
|
|
|
|
# HEADER_TAG.ID (two bytes)
|
|
header_tag = _safe_get(leader, 'HEADER_TAG', None)
|
|
if header_tag is not None:
|
|
out['HEADER_TAG_ID'] = _bytes_to_hex(_safe_get(header_tag, 'ID', b''))
|
|
out['HEADER_TAG_VALID'] = int(_safe_get(header_tag, 'VALID', 0))
|
|
out['HEADER_TAG_SIZE'] = int(_safe_get(header_tag, 'SIZE', 0))
|
|
|
|
# HEADER_DATA (main header)
|
|
hd = _safe_get(leader, 'HEADER_DATA', None)
|
|
if hd is not None:
|
|
out['TYPE'] = int(_safe_get(hd, 'TYPE', 0))
|
|
out['SUBTYPE'] = int(_safe_get(hd, 'SUBTYPE', 0))
|
|
out['FCOUNTER'] = int(_safe_get(hd, 'FCOUNTER', 0))
|
|
out['TIMETAG'] = int(_safe_get(hd, 'TIMETAG', 0))
|
|
out['DX'] = int(_safe_get(hd, 'DX', 0))
|
|
out['DY'] = int(_safe_get(hd, 'DY', 0))
|
|
out['STRIDE'] = int(_safe_get(hd, 'STRIDE', 0))
|
|
out['BPP'] = int(_safe_get(hd, 'BPP', 0))
|
|
out['PALTYPE'] = int(_safe_get(hd, 'PALTYPE', 0))
|
|
# PRODINFO often is an array of two unsigned longs; present hex and ints
|
|
prodinfo = _safe_get(hd, 'PRODINFO', None)
|
|
if prodinfo is not None:
|
|
try:
|
|
out['PRODINFO'] = [int(x) for x in prodinfo]
|
|
except Exception:
|
|
out['PRODINFO'] = str(prodinfo)
|
|
# TOD may be two ushorts
|
|
tod = _safe_get(hd, 'TOD', None)
|
|
if tod is not None:
|
|
try:
|
|
out['TOD'] = [int(x) for x in tod]
|
|
except Exception:
|
|
out['TOD'] = str(tod)
|
|
|
|
# GEO_TAG / GEO_DATA
|
|
geo_tag = _safe_get(leader, 'GEO_TAG', None)
|
|
if geo_tag is not None:
|
|
out['GEO_TAG_ID'] = _bytes_to_hex(_safe_get(geo_tag, 'ID', b''))
|
|
out['GEO_TAG_VALID'] = int(_safe_get(geo_tag, 'VALID', 0))
|
|
out['GEO_TAG_SIZE'] = int(_safe_get(geo_tag, 'SIZE', 0))
|
|
geo = _safe_get(leader, 'GEO_DATA', None)
|
|
if geo is not None:
|
|
out['ORIENTATION_rad'] = float(_safe_get(geo, 'ORIENTATION', 0.0))
|
|
out['ORIENTATION_deg'] = _rad_to_deg(_safe_get(geo, 'ORIENTATION', 0.0))
|
|
out['LATITUDE_rad'] = float(_safe_get(geo, 'LATITUDE', 0.0))
|
|
out['LATITUDE_deg'] = _rad_to_deg(_safe_get(geo, 'LATITUDE', 0.0))
|
|
out['LONGITUDE_rad'] = float(_safe_get(geo, 'LONGITUDE', 0.0))
|
|
out['LONGITUDE_deg'] = _rad_to_deg(_safe_get(geo, 'LONGITUDE', 0.0))
|
|
out['REF_X'] = int(_safe_get(geo, 'REF_X', 0))
|
|
out['REF_Y'] = int(_safe_get(geo, 'REF_Y', 0))
|
|
out['SCALE_X'] = float(_safe_get(geo, 'SCALE_X', 0.0))
|
|
out['SCALE_Y'] = float(_safe_get(geo, 'SCALE_Y', 0.0))
|
|
|
|
# PIXEL_TAG
|
|
pixel_tag = _safe_get(leader, 'PIXEL_TAG', None)
|
|
if pixel_tag is not None:
|
|
out['PIXEL_TAG_ID'] = _bytes_to_hex(_safe_get(pixel_tag, 'ID', b''))
|
|
out['PIXEL_TAG_VALID'] = int(_safe_get(pixel_tag, 'VALID', 0))
|
|
out['PIXEL_TAG_SIZE'] = int(_safe_get(pixel_tag, 'SIZE', 0))
|
|
|
|
return out
|
|
|
|
|
|
def pretty_print_image_leader(leader: Any) -> str:
|
|
d = format_image_leader(leader)
|
|
lines = []
|
|
if not d:
|
|
return "<no leader>"
|
|
lines.append(f"HEADER_TAG.ID: {d.get('HEADER_TAG_ID')}")
|
|
lines.append(f"HEADER_TAG.VALID: {d.get('HEADER_TAG_VALID')}")
|
|
lines.append(f"HEADER_TAG.SIZE: {d.get('HEADER_TAG_SIZE')}")
|
|
lines.append("")
|
|
lines.append(f"TYPE: {d.get('TYPE')}")
|
|
lines.append(f"SUBTYPE: {d.get('SUBTYPE')}")
|
|
lines.append(f"FCOUNTER: {d.get('FCOUNTER')}")
|
|
lines.append(f"TIMETAG: {d.get('TIMETAG')}")
|
|
lines.append(f"DXxDY: {d.get('DX')} x {d.get('DY')}")
|
|
lines.append(f"STRIDE: {d.get('STRIDE')}")
|
|
lines.append(f"BPP: {d.get('BPP')}")
|
|
lines.append(f"PALTYPE: {d.get('PALTYPE')}")
|
|
if 'PRODINFO' in d:
|
|
lines.append(f"PRODINFO: {d.get('PRODINFO')}")
|
|
if 'TOD' in d:
|
|
lines.append(f"TOD: {d.get('TOD')}")
|
|
lines.append("")
|
|
if 'GEO_TAG_ID' in d:
|
|
lines.append(f"GEO_TAG.ID: {d.get('GEO_TAG_ID')}")
|
|
lines.append(f"GEO_TAG.VALID: {d.get('GEO_TAG_VALID')}")
|
|
lines.append(f"GEO_TAG.SIZE: {d.get('GEO_TAG_SIZE')}")
|
|
if 'ORIENTATION_deg' in d:
|
|
lines.append(f"ORIENTATION: {d.get('ORIENTATION_deg'):.4f} deg ({d.get('ORIENTATION_rad'):.6f} rad)")
|
|
if 'LATITUDE_deg' in d and 'LONGITUDE_deg' in d:
|
|
lines.append(f"LAT,LON: {d.get('LATITUDE_deg'):.6f} deg, {d.get('LONGITUDE_deg'):.6f} deg")
|
|
if 'REF_X' in d:
|
|
lines.append(f"REF_X,REF_Y: {d.get('REF_X')}, {d.get('REF_Y')}")
|
|
if 'SCALE_X' in d:
|
|
lines.append(f"SCALE_X,SCALE_Y: {d.get('SCALE_X'):.6f}, {d.get('SCALE_Y'):.6f}")
|
|
if 'PIXEL_TAG_ID' in d:
|
|
lines.append(f"PIXEL_TAG.ID: {d.get('PIXEL_TAG_ID')} VALID={d.get('PIXEL_TAG_VALID')} SIZE={d.get('PIXEL_TAG_SIZE')}")
|
|
return "\n".join(lines)
|