重构 Autocal 采集页:单一队列与控制器驱动的主按钮

选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
ykcheungab
2026-10-18 16:04:32 +08:00
parent 85d0b396e3
commit 06e06126ca
44 changed files with 7769 additions and 2218 deletions
+34 -9
View File
@@ -290,6 +290,7 @@ def _write_multipoint_trial(
raw_per_n: float,
samples_per_hold: int,
frame_start: int,
capture_status: str | None = None,
) -> list[dict]:
write_csv(
path / "reference.csv",
@@ -302,14 +303,17 @@ def _write_multipoint_trial(
["t_host_mono_ns", "t_host_utc_ns", "kind", "detail"],
[],
)
write_json_file(path / "session.json", {
manifest = {
"trial_id": f"{site_id}-repeat-{repeat_index:03d}",
"site_id": site_id,
"status": "complete",
"uv_px": {"u": 128, "v": 64},
"images": {"captured": with_capture,
"reason": "" if with_capture else "camera not attached yet"},
})
}
if capture_status is not None:
manifest["capture_status"] = capture_status
write_json_file(path / "session.json", manifest)
if not with_capture:
return []
frames: list[dict] = []
@@ -324,8 +328,13 @@ def _write_multipoint_trial(
)
frames.extend(hold_frames)
forces.extend(hold_forces)
write_capture(path / "capture", frames, forces, fields=None if video else FRAMES_FIELDS,
video_segments=1)
if not video:
# A data-only recording: the rows exist, no frame was written to a video.
frames = [
{**row, "segment": 0, "segment_frame": 0, "in_video": 0} for row in frames
]
write_capture(path / "capture", frames, forces, fields=FRAMES_FIELDS,
video_segments=1 if video else 0)
return []
@@ -343,8 +352,17 @@ def multipoint_session(
simulate: bool = False,
trial_status: str = "complete",
extra_holds: list[Hold] | None = None,
recording_block: dict | None = None,
capture_status: str | None = None,
) -> Path:
"""A complete multi-point session directory through the review contract."""
"""A complete multi-point session directory through the review contract.
``video=False`` writes a data-only recording: frame rows exist with
``in_video=0`` and no MP4 is created. ``recording_block`` writes the root
manifest's ``recording`` block; ``None`` keeps the fixture shaped like a
session recorded before that block existed. ``capture_status`` puts the
lifecycle into the trial records, as the campaign does.
"""
session_id = f"finger-SN1_{root.name}_test0001"
trials: list[dict] = []
completion: list[dict] = []
@@ -379,21 +397,25 @@ def multipoint_session(
raw_per_n=raw_per_n,
samples_per_hold=samples_per_hold,
frame_start=1,
capture_status=capture_status,
)
trials.append({
trial_record = {
"trial_id": f"{site_id}-repeat-{repeat:03d}",
"site_id": site_id,
"repeat_index": repeat,
"directory": directory,
"status": trial_status,
"reason": "" if trial_status == "complete" else "stopped by the operator",
})
}
if capture_status is not None:
trial_record["capture_status"] = capture_status
trials.append(trial_record)
if trial_status == "complete":
complete += 1
completion.append({"site_id": site_id, "repeats_planned": repeats,
"repeats_complete": complete,
"complete": complete >= repeats})
write_json_file(root / "session.json", {
manifest = {
"format_version": "autocal-multipoint-1.0",
"session_id": session_id,
"sensor_model": "finger",
@@ -411,7 +433,10 @@ def multipoint_session(
"[t_start_host_mono_ns, t_end_host_mono_ns). Rows are not paired."},
"platform_motion": False,
"automatic_force_control": False,
})
}
if recording_block is not None:
manifest["recording"] = recording_block
write_json_file(root / "session.json", manifest)
return root