Files
orisys-sdk/src/orisys/calibration_context.py
T
ykcheungab ee830d8505 新增统一力读数会话 ForceSession
把同帧测量、空载归零、模型推断与共用后处理收进一个 SDK 会话,Studio 显示与记录使用同一样本;旧 Sensor 读数接口保持兼容。以 force session 与受影响 SDK 套件、lint 与文档门禁验证,硬件与真实载荷精度未验证。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpCfJF6SvonMKmPZhzKZHb
2026-10-20 07:07:22 +00:00

116 lines
4.6 KiB
Python

"""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", "<f4"), ("map_y", "<f4"), ("valid_mask", "u1"), ("world_xyz", "<f4")):
array = np.ascontiguousarray(data[key], dtype=dtype)
digest.update(key.encode("ascii"))
digest.update(str(array.shape).encode("ascii"))
digest.update(array.tobytes())
return digest.hexdigest()
def build_context(config, filters=None, *, geometry_sha256=None, source_scale=None,
coordinates=None, output_domain_sha256=None, processing_backend=None,
preprocessing=None, field=None) -> 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)