让 Finger 视频分析使用所选的力标定文件。
分析链不再在打开前拒绝标定,而是把文件作为 cal_path 交给 FingerRuntime,并用操作者确认的设备编号核对文件里的 SN。标定传递测试与对话框 SN 校验通过。
This commit is contained in:
@@ -139,6 +139,9 @@ class BareVideoSetup:
|
||||
#: (the first frame it processes — which *is* a clean tracker, so the first frame is the
|
||||
#: reference without a reset).
|
||||
reference_frame: int | None = None
|
||||
#: Serial the operator confirmed for ``calibration``. Empty when the pass uses the
|
||||
#: bundle's own file or raw force.
|
||||
device_id: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -346,6 +349,7 @@ def session_document(
|
||||
reference_frame: int | None,
|
||||
identity: str,
|
||||
source_facts: dict[str, Any],
|
||||
device_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""The ``session.json`` a bare video is analysed under.
|
||||
|
||||
@@ -388,6 +392,7 @@ def session_document(
|
||||
"profile_ref": str(profile.ref),
|
||||
"profile_id": str(profile.profile_id or ""),
|
||||
"calibration": "" if calibration is None else str(Path(calibration).resolve()),
|
||||
"device_id": str(device_id or ""),
|
||||
"reference_frame": None
|
||||
if reference_frame is None
|
||||
else int(reference_frame),
|
||||
@@ -511,6 +516,7 @@ def build_workspace(
|
||||
identity, source_facts = video_identity(resolved)
|
||||
calibration = None if setup is None else setup.calibration
|
||||
reference_frame = None if setup is None else setup.reference_frame
|
||||
device_id = None if setup is None else setup.device_id
|
||||
if calibration is not None and not Path(calibration).is_file():
|
||||
raise BareVideoError(f"the chosen calibration {calibration} is not there")
|
||||
frames = synthetic_frame_index(facts)
|
||||
@@ -522,6 +528,7 @@ def build_workspace(
|
||||
reference_frame=reference_frame,
|
||||
identity=identity,
|
||||
source_facts=source_facts,
|
||||
device_id=device_id,
|
||||
)
|
||||
directory = workspace_directory(identity, root=root)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -167,14 +167,18 @@ class ChainSpec:
|
||||
export_dir: Path | None = None
|
||||
flip_source_vertical: bool = True
|
||||
mirror: bool = False
|
||||
#: An explicit ``force_calibration.json`` for the box chain, or ``None`` for whatever
|
||||
#: the configuration's own bundle resolves. It is *not* part of :meth:`identity`,
|
||||
#: deliberately: a recording was taken under one calibration and the identity of *what
|
||||
#: it is* must not move when a different curve is tried against it. The calibration
|
||||
#: half of the invalidation key is the recording's own ``calibration_id`` (see
|
||||
#: An explicit force-calibration file, or ``None`` for whatever the configuration's
|
||||
#: own bundle resolves. It is *not* part of :meth:`identity`, deliberately: a recording
|
||||
#: was taken under one calibration and the identity of *what it is* must not move when
|
||||
#: a different curve is tried against it. The calibration half of the invalidation key
|
||||
#: is the recording's own ``calibration_id`` (see
|
||||
#: :meth:`AnalysisService.dependency_report`), which the generated workspace sets from
|
||||
#: the chosen file's content.
|
||||
calibration: Path | None = None
|
||||
#: Device serial the operator confirmed for ``calibration``. Passed with ``cal_path``
|
||||
#: so the SDK can reject a file whose ``sn`` is a different sensor. Not part of
|
||||
#: :meth:`identity`: a mismatch fails the open, and a match does not change the file.
|
||||
device_id: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.sensor_type not in ("finger", "box"):
|
||||
@@ -210,6 +214,7 @@ def chain_spec_for(
|
||||
config_ref: str | Path | None = None,
|
||||
export_dir: str | Path | None = None,
|
||||
calibration: str | Path | None = None,
|
||||
device_id: str | None = None,
|
||||
) -> ChainSpec:
|
||||
"""The chain a recording was made with, unless the caller names a different one.
|
||||
|
||||
@@ -234,11 +239,13 @@ def chain_spec_for(
|
||||
bundle = Path(str(export_dir))
|
||||
elif kind == "finger" and resolved is not None:
|
||||
bundle = resolve_finger_export_dir(resolved)
|
||||
serial = str(device_id or "").strip() or None
|
||||
return ChainSpec(
|
||||
sensor_type=kind,
|
||||
config_ref=resolved,
|
||||
export_dir=bundle,
|
||||
calibration=None if calibration is None else Path(str(calibration)),
|
||||
device_id=serial,
|
||||
)
|
||||
|
||||
|
||||
@@ -296,15 +303,6 @@ class _OpenChain:
|
||||
if self.spec.sensor_type == "finger":
|
||||
from orisys.finger import FingerRuntime
|
||||
|
||||
if self.spec.calibration is not None:
|
||||
# The finger runtime opens its ``Sensor`` without a calibration path,
|
||||
# so a chosen curve cannot be applied to this chain. Refusing is the
|
||||
# only honest answer: a pass that ran anyway would store numbers from a
|
||||
# different calibration than the one the caller asked for.
|
||||
raise RuntimeError(
|
||||
"the finger chain cannot take a chosen force calibration; it uses "
|
||||
"the one the bundle resolves"
|
||||
)
|
||||
kwargs: dict[str, Any] = {
|
||||
"video": str(self.video),
|
||||
"verbose": False,
|
||||
@@ -315,6 +313,13 @@ class _OpenChain:
|
||||
kwargs["config"] = str(self.spec.config_ref)
|
||||
if self.spec.export_dir is not None:
|
||||
kwargs["export_dir"] = str(self.spec.export_dir)
|
||||
if self.spec.calibration is not None:
|
||||
# ``cal_path`` is the file the operator chose. ``device_id`` is the
|
||||
# serial they confirmed for this video; the SDK rejects the file when
|
||||
# its ``sn`` is a different sensor.
|
||||
kwargs["cal_path"] = str(self.spec.calibration)
|
||||
if self.spec.device_id:
|
||||
kwargs["device_id"] = str(self.spec.device_id)
|
||||
self._runtime = FingerRuntime.open(**kwargs)
|
||||
self.sensor = self._runtime.tactile
|
||||
self._read = self._runtime.read_source
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""A chosen Finger calibration is handed to the runtime, not refused before it opens."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.analysis.service import ChainSpec, _OpenChain
|
||||
|
||||
|
||||
class _Runtime:
|
||||
tactile = object()
|
||||
capture = None
|
||||
opened: dict = {}
|
||||
|
||||
@classmethod
|
||||
def open(cls, **kwargs):
|
||||
cls.opened = dict(kwargs)
|
||||
return cls()
|
||||
|
||||
def read_source(self):
|
||||
return False, None
|
||||
|
||||
def process_frame(self, frame):
|
||||
return None
|
||||
|
||||
|
||||
def test_a_finger_chain_passes_the_chosen_file_and_device(monkeypatch, tmp_path):
|
||||
import orisys.finger as finger
|
||||
|
||||
monkeypatch.setattr(finger, "FingerRuntime", _Runtime)
|
||||
_Runtime.opened = {}
|
||||
calibration = tmp_path / "DD02-14.json"
|
||||
calibration.write_text("{}", encoding="utf-8")
|
||||
video = tmp_path / "clip.mp4"
|
||||
video.write_bytes(b"")
|
||||
spec = ChainSpec(
|
||||
sensor_type="finger",
|
||||
calibration=calibration,
|
||||
device_id="DD02-14",
|
||||
)
|
||||
_OpenChain(spec, video, tmp_path).open()
|
||||
assert _Runtime.opened["cal_path"] == str(calibration)
|
||||
assert _Runtime.opened["device_id"] == "DD02-14"
|
||||
assert _Runtime.opened["video"] == str(video)
|
||||
|
||||
|
||||
def test_a_finger_chain_without_a_file_does_not_invent_one(monkeypatch, tmp_path):
|
||||
import orisys.finger as finger
|
||||
|
||||
monkeypatch.setattr(finger, "FingerRuntime", _Runtime)
|
||||
_Runtime.opened = {}
|
||||
video = tmp_path / "clip.mp4"
|
||||
video.write_bytes(b"")
|
||||
spec = ChainSpec(sensor_type="finger")
|
||||
_OpenChain(spec, video, tmp_path).open()
|
||||
assert "cal_path" not in _Runtime.opened
|
||||
assert "device_id" not in _Runtime.opened
|
||||
Reference in New Issue
Block a user