Fix the phase-1/2 review findings in recording, calibration and depth replay
What - record: one packet->row conversion (record/sample.py). A failed compute chain records no force value at all, a newton value is written only for an axis that really has a calibration curve, and mono_t (not the wall clock) orders history with a per-row calibration hash. - record lifecycle: explicit starting/recording/stopping/stopped/failed states owned by the writer thread; stop() reports completed=False on timeout and does not write ended_at; consecutive write failures stop acceptance immediately. - record capacity: byte budget for the video queue (512 MiB default); a full queue sheds the *frame* and parks the tiny data row, so a video overflow no longer deletes CSV rows. Lost rows leave a dropped=1 index row and are counted as dropped_gaps / unrecorded_gaps. - calibration identity: sha256 content hash instead of a path, minted from the calibration the sensor actually loaded (reported before the first frame) and confirmed by the acquisition thread; events are timestamped when they happen. - calibration diagnostics: the fit summary, residual and validation error now come from the model that export/apply would use (piecewise_linear); validation points are held out of the build; repeated reference loads are merged with an n-weighted mean and pooled std. - calibration record: every point keeps its full three-axis sample series (frame id, time, raw, newtons, valid) plus diagnostics, written to an independent calibration_session_*.json that can be reopened for inspection; save record / export coefficients / apply to device are three separate actions. - depth replay: the cursor's frame is bound to the depth result by request sequence and frame id, so a moved cursor never shows another frame's map; the SDK is stepped from its current position instead of re-reading from the start; the live chain (box deformation+contact, finger unroll+tactile) is replayed with the exact resolved config path; algorithm state is rebuilt from a reset marker or from the recorded pre-roll, and says so when it cannot be. Why The review (D:/orisys_ws/_review_phase12/review.md) showed the app writing raw counts into the newton columns, recording a stale value after a failed compute, losing CSV rows when the video queue filled, reporting success while a writer was still running, diagnosing a model nobody applies, applying a calibration before the device confirmed it, dropping the samples behind a calibration point, showing a stale depth map under a moved cursor and recomputing depth of the wrong frame. Verification - D:/orisys_ws/_review_phase12/reproduce.py: all ten acceptance lines flipped (log saved as _review_phase12/reproduce_after.log). Including 25/25 rows kept with 8 video frames shed, frames [2]->[3], depth 8.0/8.0, fit 2/2 points. - apps/studio/tools/ui_check.py: 249 checks, 0 failures (twice). - shared suites: 91 tests, 0 failures (new: test_record_semantics, test_record_lifecycle, test_calibration_model, test_calibration_record, test_depth_replay). - apps/studio/tests/test_ui_smoke.py: 23/30; the 7 remaining are harness artifacts of the sandbox (4 tmp_path PermissionError, 3 parametrized tests the shim cannot drive), unchanged from before this change. - compileall clean; locale parity zh/en + t() key coverage: 0 failures. Not in this change - P2#10 disk-based browsing of a long recording and range CSV export (next round, per your choice). - Stability-triggered calibration sampling, replay fixed colour scale, PyOpenGL three-dimensional view. - Hardware verification: no camera attached, so the SDK's real processing chain and the pre-roll's effect on a live tracker were exercised with fakes only.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Offscreen UI smoke tests: construction, layout adaptation, theme tokens."""
|
||||
"""Offscreen UI smoke tests: construction, layout adaptation, theme tokens."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -309,6 +309,13 @@ def _add_calibration_point(window, *, load: float, raw: float, samples: int = 40
|
||||
|
||||
|
||||
def test_calibration_diagnostics_drive_the_fit(make_window):
|
||||
"""The diagnostics must describe the model that would be exported.
|
||||
|
||||
The export builds a piecewise-linear curve, which passes through every point
|
||||
it is built from — so the training residual is zero by construction and an
|
||||
off-trend point can only show up as a *validation* error. A private
|
||||
least-squares residual would have described a model nobody applies.
|
||||
"""
|
||||
window = _show(make_window(), (1400, 880))
|
||||
window.cal_panel.set_running(True)
|
||||
_add_calibration_point(window, load=1.0, raw=100.2)
|
||||
@@ -319,23 +326,36 @@ def test_calibration_diagnostics_drive_the_fit(make_window):
|
||||
recorder = window.cal_recorder
|
||||
fit = recorder.fit("fn")
|
||||
assert fit.usable and fit.n_points == 4
|
||||
worst = fit.max_residual_n
|
||||
assert worst > 0.05, "the off-trend point produced no residual"
|
||||
assert fit.method == "piecewise_linear", fit.method
|
||||
assert fit.breakpoints == 4, fit.breakpoints
|
||||
assert fit.max_residual_n <= 1e-9, "the exported model misses its own points"
|
||||
assert window.cal_panel.fit_label.text()
|
||||
|
||||
# Hold the off-trend point (load 2.0 at raw 400) out: the exported model is
|
||||
# then built without it and the misfit is measurable.
|
||||
window.cal_panel.table.setCurrentCell(1, 0)
|
||||
window.cal_panel._on_toggle_validation()
|
||||
held_out = recorder.fit("fn")
|
||||
assert held_out.validation_points == 1
|
||||
assert held_out.validation_error_n > 0.05, held_out.validation_error_n
|
||||
assert recorder.points[1].is_validation
|
||||
assert np.isnan(recorder.points[1].residual_n), "validation point entered the fit"
|
||||
|
||||
window.cal_panel._on_toggle_validation()
|
||||
window.cal_panel._on_toggle_excluded()
|
||||
assert recorder.fit("fn").max_residual_n < worst, "exclusion did not tighten the fit"
|
||||
assert recorder.points[1].excluded
|
||||
# An excluded point leaves the export store but stays on record.
|
||||
assert len(window.cal_panel._store.channel("fn")) == 3
|
||||
|
||||
window.cal_panel.table.setCurrentCell(2, 0)
|
||||
window.cal_panel._on_toggle_validation()
|
||||
held_out = recorder.fit("fn")
|
||||
assert held_out.validation_points == 1
|
||||
assert recorder.points[2].is_validation
|
||||
assert np.isnan(recorder.points[2].residual_n), "validation point entered the fit"
|
||||
# The exported model no longer passes through the excluded point.
|
||||
from orisys_app_shared.force.calibration_io import (
|
||||
build_export_model,
|
||||
predict_channel,
|
||||
)
|
||||
|
||||
store = window.cal_panel._store
|
||||
model = build_export_model({key: store.channel(key) for key in ("fn", "fx", "fy")})
|
||||
assert abs(predict_channel(model, "fn", 400.0) - 2.0) > 0.05
|
||||
|
||||
|
||||
def test_calibration_point_dialog_shows_three_axes(make_window):
|
||||
|
||||
Reference in New Issue
Block a user