"""Measurement identities shared by SDK runtime and offline calibration exporters.""" from __future__ import annotations import hashlib import json from copy import deepcopy from pathlib import Path import numpy as np from .calibration import filters_fingerprint def _feature_signature(identity) -> str: encoded = json.dumps(identity, sort_keys=True, separators=(",", ":"), allow_nan=False) return hashlib.sha256(encoded.encode("utf-8")).hexdigest() class CalibrationContext(dict): """Actual pipeline fields plus the runtime inputs needed to compare HHD settings. JSON serialization contains only the actual fields. Retaining the signature inputs lets old files differ in downsample without ignoring other hashed settings. """ def __init__(self, fields, feature_identity): super().__init__(fields) self._feature_identity = deepcopy(feature_identity) def signature_for_downsample(self, downsample: int) -> str: """Recompute the runtime identity with only the requested HHD resolution.""" identity = deepcopy(self._feature_identity) identity["tracker"]["hhd_downsample"] = downsample return _feature_signature(identity) @property def actual_downsample(self) -> int: """The HHD downsample this runtime context was actually built with.""" raw = self.get("hhd_downsample", 1) try: return max(1, int(raw)) if raw is not None else 1 except (TypeError, ValueError): return 1 def sha256_file(path) -> str: return hashlib.sha256(Path(path).read_bytes()).hexdigest() def domain_fingerprint(domain) -> str: """Bind a coordinate lookup to the actual unroll, not just its dimensions.""" data = domain.to_dict() if hasattr(domain, "to_dict") else domain digest = hashlib.sha256() for key, dtype in (("map_x", " dict: """Build the closed v1 feature identity from effective values, excluding readout filters. ``field`` carries the closed profile identity for field models (shape, dtype, unit, mask and lifecycle). It is omitted for scalar profiles so their signatures stay byte-identical to previously exported files. """ camera = config.get("camera", {}) tracker = dict(config.get("tracker", {})) raw_downsample = tracker.get("hhd_downsample", 1) try: downsample = max(1, int(raw_downsample)) if raw_downsample is not None else 1 except (TypeError, ValueError): downsample = 1 tracker["hhd_downsample"] = downsample if source_scale is not None: source_scale = float(source_scale) measurement = filters_fingerprint(config.get("filters", {}) if filters is None else filters).get("measurement", {}) # A missing EMA setting has the same effective meaning as the SDK default. measurement.setdefault("flow_ema_alpha", 1.0) result = { "stitch_resolution": camera.get("resolution_stitch"), "dis_mode": tracker.get("dis_mode"), "hhd_downsample": downsample, "filters": {"measurement": measurement}, } if geometry_sha256 is not None: result["geometry_sha256"] = geometry_sha256 if source_scale is not None: result["source_scale"] = float(source_scale) if coordinates is not None: result["coordinates"] = coordinates if output_domain_sha256 is not None: result["output_domain_sha256"] = output_domain_sha256 effective = { "feature_version": "orisys_hhd_raw/1", "processing_backend": processing_backend or config.get("type", "compound_eye"), "camera": {key: camera.get(key) for key in ("resolution_raw", "resolution_stitch")}, "tracker": tracker, "contact": config.get("contact", {}), "measurement": measurement, "geometry_sha256": geometry_sha256, "output_domain_sha256": output_domain_sha256, "source_scale": source_scale, "preprocessing": preprocessing or {}, } if field is not None: result["field"] = deepcopy(field) effective["field"] = deepcopy(field) result["feature_signature_sha256"] = _feature_signature(effective) return CalibrationContext(result, effective)