from pyleem.metainfo import (
FILE_CONTENTS,
IMG_CONTENTS,
UNIT_CODES,
DATA_TAGS,
)
import struct
from datetime import datetime, timedelta, timezone
[docs]
def convert_win_filetime(timestamp):
"""Convert Windows filetime to datetime string.
Windows filetime is a 64-bit value representing 100-nanosecond
intervals since January 1, 1601 (UTC). To further
convert to local time, need to add the timezone offset.
:param int timestamp: Windows filetime value.
:return: Formatted datetime string (YYYY/MM/DD HH:MM:SS.ffffff).
:rtype: str
"""
epoch = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = epoch + timedelta(microseconds=timestamp / 10)
return dt.strftime("%Y/%m/%d %H:%M:%S.%f")
[docs]
def is_tag_in_range(tag, range):
"""Check whether tag's base value is within range.
UView uses the highest bit to mark image overlay. This function
masks that bit before checking range. Range min and max can be
the same value for a single tag.
:param int tag: Integer tag value (0-255).
:param tuple range: Tuple (min_base, max_base) defining range.
:return: True if base tag is within range, False otherwise.
:rtype: bool
"""
base_tag = tag & 0x7F
return range[1] >= base_tag >= range[0]
[docs]
def parse_leem_data(data):
"""Parse LEEM-specific metadata from extra data block.
LEEM data contains tagged metadata with various formats including
standard values, gauge readings, and camera settings.
:param bytes data: LEEM metadata bytes.
:return: Dictionary of metadata as (value, unit) tuples.
:rtype: dict
:raises ValueError: If an unrecognized tag value is encountered.
"""
leemdata = {}
while data:
tag = data[0]
data = data[1:]
if tag == 255:
# end tag
break
elif is_tag_in_range(tag, (0, 99)):
# standard tags 0..99
source, data = data.split(b"\x00", maxsplit=1)
source = source.decode("cp1252", errors="replace")
if data[:4] == b"sO\xc3G":
value = "invalid"
elif data[:4] == b"\xf3O\xc3G":
value = "local"
else:
value = struct.unpack(f"<f", data[:4])[0]
last_char = source[-1]
unit = None
if last_char.isdigit() and int(last_char) in UNIT_CODES:
unit = UNIT_CODES[int(last_char)]
source = source[:-1]
leemdata[source] = (value, unit)
data = data[4:]
elif is_tag_in_range(tag, (106, 109)) or is_tag_in_range(tag, (120, 126)):
# gauge tags
# 106 - 109 are standard gauge tags
# additional gauge tags are 120 - 126 (the menu suggests 127 - 130
# are also gauge tags but they are outside of the correct range)
source, unit, data = data.split(b"\x00", maxsplit=2)
source = source.decode("cp1252", errors="replace")
unit = unit.decode("cp1252", errors="replace")
value = struct.unpack(f"<f", data[:4])[0]
leemdata[source] = (value, unit)
data = data[4:]
elif is_tag_in_range(tag, (100, 100)) or is_tag_in_range(tag, (111, 116)):
base_tag = tag & 0x7F
for source, unit, arg_struct in DATA_TAGS[base_tag]:
size = struct.calcsize(arg_struct)
value = struct.unpack(arg_struct, data[:size])[0]
leemdata[source] = (value, unit)
data = data[size:]
elif is_tag_in_range(tag, (104, 104)):
# camera exposure and average
# at least in our instrument, the average value is reversed compared to
# the menu settings, where the average count is before the setting
exposure, average_count, average_mode = struct.unpack(f"<fbb", data[:6])
leemdata["Camera Exposure"] = (exposure, "s")
leemdata["Camera Average"] = (average_count, None)
if average_mode == 0:
leemdata["Camera Average Mode"] = ("no average", None)
elif average_mode > 0:
leemdata["Camera Average Mode"] = ("average", None)
elif average_mode < 0:
leemdata["Camera Average Mode"] = ("sliding average", None)
data = data[6:]
elif is_tag_in_range(tag, (105, 105)):
# image title
title, data = data.split(b"\x00", maxsplit=1)
leemdata["Image Title"] = (title.decode("cp1252", errors="replace"), None)
elif is_tag_in_range(tag, (110, 110)):
# FOV
fov, data = data.split(b"\x00", maxsplit=1)
fov_text = fov.decode("cp1252", errors="replace")
mm_suffix = b"\xb5m".decode("cp1252")
if fov_text.endswith("um") or fov_text.endswith(mm_suffix):
fov = float(fov_text.removesuffix("um").removesuffix(mm_suffix))
fov_unit = "um"
else:
fov = fov_text
fov_unit = None
leemdata["FOV"] = (fov, fov_unit)
cal_fov = struct.unpack(f"<f", data[:4])[0]
leemdata["Cal. FOV"] = (cal_fov, None)
data = data[4:]
else:
raise ValueError(f"Incorrect tag value: {tag}")
return leemdata