"""Load H32 MSOP packets and DIFOP angles from a Medulla dlog session.""" from __future__ import annotations from dataclasses import dataclass from pathlib import Path import numpy as np from tools.rscap_v2.h32_msop import default_horizontal_deg, default_vertical_deg from .difop import DifopAngles, parse_difop_angles from .dobject import ( discover_records_from_source, iter_payloads_from_source, open_dlog_source, ) from .payload_v1 import parse_difop_payload, parse_msop_batch_payload @dataclass class H32DlogLidarSession: dlog_root: str msop_object: str difop_object: str msop_packets: list[bytes] msop_host_utc_ticks: list[int] msop_batch_count: int difop_record_count: int angle_source: str vertical_deg: np.ndarray horizontal_deg: np.ndarray session_id: str | None = None lidar_ip: str | None = None host_ticks_min: int | None = None host_ticks_max: int | None = None def load_h32_dlog_lidar( dlog_root: Path | str, *, msop_object: str = "frontlidar-msop-raw", difop_object: str = "frontlidar-difop-raw", require_difop: bool = False, host_ticks_min: int | None = None, host_ticks_max: int | None = None, ) -> H32DlogLidarSession: with open_dlog_source(dlog_root) as source: # DIFOP angles: prefer packets inside the window, else any in the capture. angles: DifopAngles | None = None difop_count = 0 session_id: str | None = None lidar_ip: str | None = None for _record, payload in iter_payloads_from_source( source, difop_object, host_ticks_min=host_ticks_min, host_ticks_max=host_ticks_max, ): difop = parse_difop_payload(payload) difop_count += 1 try: angles = parse_difop_angles(difop.raw) except ValueError: continue if session_id is None: session_id = difop.session_id lidar_ip = difop.lidar_ip if angles is None: for _record, payload in iter_payloads_from_source(source, difop_object): difop = parse_difop_payload(payload) difop_count += 1 try: angles = parse_difop_angles(difop.raw) except ValueError: continue if session_id is None: session_id = difop.session_id lidar_ip = difop.lidar_ip if angles is not None: break msop_packets: list[bytes] = [] msop_host_utc_ticks: list[int] = [] batch_count = 0 for record, payload in iter_payloads_from_source( source, msop_object, host_ticks_min=host_ticks_min, host_ticks_max=host_ticks_max, ): batch = parse_msop_batch_payload(payload) batch_count += 1 if session_id is None: session_id = batch.session_id lidar_ip = batch.lidar_ip for item in batch.packets: msop_packets.append(item.raw) # Per-packet UTC host receive from MSOP DLog payload only. # Do NOT fall back to DObject tic (DateTime.Now / local). msop_host_utc_ticks.append(int(item.host_receive_utc_ticks)) if not msop_packets: msop_records = discover_records_from_source(source, msop_object) raise RuntimeError( f"no MSOP packets from DObject {msop_object!r} under {source.label} " f"(log records={len(msop_records)}, " f"host_ticks=[{host_ticks_min}, {host_ticks_max}])" ) if angles is None: if require_difop: raise RuntimeError( f"no valid DIFOP calibration from DObject {difop_object!r} under {source.label}" ) vertical = default_vertical_deg() horizontal = default_horizontal_deg() angle_source = "default_msop_only_vertical_-16_to_16_deg" else: vertical = angles.vertical_deg horizontal = angles.horizontal_deg angle_source = "difop_channel_angles" return H32DlogLidarSession( dlog_root=source.label, msop_object=msop_object, difop_object=difop_object, msop_packets=msop_packets, msop_host_utc_ticks=msop_host_utc_ticks, msop_batch_count=batch_count, difop_record_count=difop_count, angle_source=angle_source, vertical_deg=vertical, horizontal_deg=horizontal, session_id=session_id, lidar_ip=lidar_ip, host_ticks_min=host_ticks_min, host_ticks_max=host_ticks_max, )