把 Studio 主机协议改成单向端口,并让「从视频重算」走分析入口
控制器经 AcquireGate / ImageSink / ShellNotes 与信号接线,采集 tick 用 acquire_presenter 上屏;三维测试证明分析结果已喂 3D 后,重算并入 on_analysis_requested。 验证:apps/studio 下相关 pytest 与 tools/ui_check.py(634 checks)通过。
This commit is contained in:
@@ -4,11 +4,15 @@ The catalog replaced two rules that were wrong in the same direction — a hard-
|
||||
list, and "any ``*.json`` beside a ``sensor.json`` is another configuration". These tests build
|
||||
a tree containing exactly the shapes those rules mishandled: a descriptor bundle, a legacy
|
||||
``sensor.json`` bundle, a folder holding only ``force_calibration.json``, a malformed
|
||||
``manifest.json``, a bundle whose declared asset is missing, and two bundles sharing one
|
||||
``profile_id``.
|
||||
``manifest.json``, a bundle whose declared asset is missing, a bundle with a calibration
|
||||
selected, and two bundles sharing one ``profile_id``.
|
||||
|
||||
The tree is built here, in the test's own temporary directory: a committed fixture would be a
|
||||
second copy of the bundle format that could drift from the real one without anyone noticing.
|
||||
|
||||
The labels are asserted here too, because they are the catalog's whole operator-facing
|
||||
contract: what a configuration is, where it came from and what it will give — never a
|
||||
``profile_id``, an ``ok``/``degraded``/``failed`` verdict, or the word ``legacy``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -33,6 +37,19 @@ from orisys_app_shared.sensors import (
|
||||
CAMERA_OK = {"resolution_raw": [640, 480], "fps": 30, "resolution_stitch": [100, 100]}
|
||||
TRACKER_OK = {"algorithm": "dis"}
|
||||
|
||||
#: A calibration that loads cleanly and declares the family it belongs to, so a selected
|
||||
#: calibration produces no warning at all: the shape an operator should meet with no remark.
|
||||
CALIBRATION = {
|
||||
"schema_version": 2,
|
||||
"unit": "N",
|
||||
"channels": {
|
||||
"normal": {"points": [[0, 0], [100, 1.0]]},
|
||||
"shear_x": {"points": [[0, 0], [100, 1.0]]},
|
||||
"shear_y": {"points": [[0, 0], [100, 1.0]]},
|
||||
},
|
||||
"meta": {"sensor_type": "mini", "sn": "FIXTURE-1"},
|
||||
}
|
||||
|
||||
|
||||
def _config(
|
||||
*, declared_type: str | None = None, stitcher: bool = False
|
||||
@@ -53,7 +70,7 @@ def _descriptor(profile_id: str, **overrides: Any) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {
|
||||
"schema_version": 1,
|
||||
"profile_id": profile_id,
|
||||
"display_name": profile_id,
|
||||
"display_name": "Mini Lab",
|
||||
"product_family": "mini",
|
||||
"processing_backend": "single_camera",
|
||||
"sensor_json": "sensor.json",
|
||||
@@ -93,11 +110,16 @@ def trees(tmp_path):
|
||||
user / "missing-assets" / "manifest.json",
|
||||
_descriptor(
|
||||
"orisys.finger.missing-r1",
|
||||
display_name="Finger R01 (map missing)",
|
||||
product_family="finger",
|
||||
processing_backend="uv_unroll",
|
||||
computational_assets={"unroll_map": "assets/not-here.npz"},
|
||||
),
|
||||
)
|
||||
# A descriptor bundle with a calibration selected: the valid shape.
|
||||
_write(user / "mini-cal" / "sensor.json", _config())
|
||||
_write(user / "mini-cal" / "manifest.json", _descriptor("orisys.mini.cal-r1"))
|
||||
_write(user / "mini-cal" / "force_calibration.json", CALIBRATION)
|
||||
# Two bundles declaring one profile_id.
|
||||
for name in ("dup-a", "dup-b"):
|
||||
_write(user / name / "sensor.json", _config())
|
||||
@@ -113,6 +135,7 @@ def test_every_bundle_shape_is_discovered_with_its_origin(trees):
|
||||
"shipped/box",
|
||||
"shipped/calib-only",
|
||||
"user/mini-lab",
|
||||
"user/mini-cal",
|
||||
"user/broken-manifest",
|
||||
"user/missing-assets",
|
||||
"user/dup-a",
|
||||
@@ -233,13 +256,85 @@ def test_an_entry_the_sdk_rejects_is_never_reported_as_startable(trees):
|
||||
assert (entry.start_blocked_reason is None) == expected, entry.ref
|
||||
|
||||
|
||||
def test_the_label_states_family_profile_origin_and_validity(trees):
|
||||
def test_the_label_states_what_it_is_where_it_came_from_and_what_it_gives(trees):
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
assert entry is not None
|
||||
assert entry.label() == "Mini Lab · User · No calibration selected — raw force"
|
||||
|
||||
|
||||
def test_a_startable_label_carries_no_identity_and_no_severity_word(trees):
|
||||
"""What an operator chooses between is products and outcomes, not internal names."""
|
||||
for entry in trees.entries:
|
||||
if not entry.valid:
|
||||
continue
|
||||
label = entry.label()
|
||||
assert entry.profile_id not in label, label
|
||||
for word in ("ok", "degraded", "failed", "legacy"):
|
||||
assert word not in label, f"{word!r} reached the row: {label}"
|
||||
|
||||
|
||||
def test_no_calibration_selected_reads_as_raw_force_not_as_a_defect(trees):
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
assert entry is not None
|
||||
# The SDK reports the absent calibration as a warning, which is the state the row used to
|
||||
# read as "degraded" — a severity word that never said "raw force" or "not selected".
|
||||
assert entry.state == "degraded"
|
||||
assert "No calibration selected — raw force" in entry.label()
|
||||
assert entry.selected_calibration == ""
|
||||
|
||||
|
||||
def test_a_selected_calibration_reads_as_a_selection(trees):
|
||||
entry = trees.by_ref("user/mini-cal")
|
||||
assert entry is not None and entry.valid, entry.reason if entry else "missing"
|
||||
assert entry.state == "ok", entry.reason
|
||||
assert entry.selected_calibration == "force_calibration.json"
|
||||
# Named, and said as something selected — never as a property of the bundle ...
|
||||
assert entry.label() == "Mini Lab · User · Calibration: force_calibration.json"
|
||||
# ... and the "ok" the SDK calls that is not a word an operator needs to read.
|
||||
assert "ok" not in entry.label()
|
||||
|
||||
|
||||
def test_an_unstartable_label_says_cannot_start_with_the_sdks_own_reason(trees):
|
||||
entry = trees.by_ref("user/missing-assets")
|
||||
label = entry.label()
|
||||
assert "finger" in label
|
||||
assert entry.profile_id in label
|
||||
assert ORIGIN_USER in label
|
||||
assert entry.state in label
|
||||
assert entry is not None and not entry.valid
|
||||
assert entry.label() == f"Finger R01 (map missing) · Cannot start: {entry.reason}"
|
||||
assert "unroll_map" in entry.label()
|
||||
|
||||
|
||||
def test_a_duplicate_label_names_the_twin(trees):
|
||||
first = trees.by_ref("user/dup-a")
|
||||
second = trees.by_ref("user/dup-b")
|
||||
assert first is not None and second is not None
|
||||
assert first.label() == "Mini Lab · User · Also provided by user/dup-b"
|
||||
assert second.label() == "Mini Lab · User · Also provided by user/dup-a"
|
||||
|
||||
|
||||
def test_the_tooltip_keeps_the_identity_the_shape_the_state_and_the_findings(trees):
|
||||
"""The facts stop being the first thing read; they do not stop being available."""
|
||||
entry = trees.by_ref("user/mini-lab")
|
||||
assert entry is not None
|
||||
tooltip = entry.tooltip()
|
||||
assert entry.profile_id in tooltip
|
||||
assert "descriptor bundle" in tooltip
|
||||
assert f"SDK validation: {entry.state}" in tooltip
|
||||
assert "processing can start" in tooltip, "the verdict word is left unexplained"
|
||||
assert "calibration: none selected — raw force" in tooltip
|
||||
# A finding is a sentence: the code is dropped, the capability it names is not.
|
||||
assert "calibration.absent" not in tooltip
|
||||
assert "calibrated force is unavailable; raw force is available" in tooltip
|
||||
|
||||
|
||||
def test_the_tooltip_of_a_legacy_bundle_says_its_shape(trees):
|
||||
entry = trees.by_ref("shipped/box")
|
||||
assert entry is not None
|
||||
tooltip = entry.tooltip()
|
||||
assert "compatibility bundle, no descriptor" in tooltip
|
||||
assert entry.profile_id in tooltip
|
||||
# The code the row lost is not lost here: it is rendered as what it means, and the file it
|
||||
# is about is kept.
|
||||
assert "descriptor.legacy" not in tooltip
|
||||
assert "no manifest.json" in tooltip
|
||||
assert "built-in profile table" in tooltip
|
||||
|
||||
|
||||
def test_the_root_decision_names_the_root_in_use():
|
||||
|
||||
Reference in New Issue
Block a user