add session browsing, calibration diagnostics and the 2D layout preset
Phase 2 of the review: browse the whole session instead of only the rolling window, diagnose a calibration from the recorded points, and provide the reviewed two-panel layout. What - SessionStore gains extent(), visible(t0, t1, max_points) and statistics(t0, t1, unit). visible() walks only the blocks that overlap the window and decimates by taking each bucket's minimum and maximum, so a spike survives decimation and browsing a long session does not copy all of it. - ForcePlotsPanel gains follow/browse modes, a whole-session overview strip with the current window, pan and wheel zoom, a rubber-band zoom toggle, a shared time cursor across all three channels, and a selection readout (duration, valid samples, per-axis peak/mean/std). - CalibrationPoint records a bounded per-axis trace, an excluded flag, a validation flag and its fit residual. CalibrationRecorder gains set_excluded/set_validation/fit; the fit is a least-squares line over the non-excluded, non-validation points with per-point residuals and an independent validation error. - The calibration table becomes the reviewed diagnostic view (reference load | FN | FX | FY | stability | state) with the residual shown next to the loaded axis, plus exclude/restore and mark-validation actions and a fit summary line. Double clicking a row opens the point's three-axis trace. - Add a layout preset: free layout or 2D mode (depth map on the left, force charts on the right, calibration folded away), persisted in preferences. Why - Browsing is a display pause, not a capture pause: the record channel keeps running while the charts show an older window. - Excluding a point removes it from the fit and from the exported calibration but keeps it on record, so the evidence for the decision is not destroyed. - The non-loaded axes are recorded but never given a reference load: their true load is unknown during a single-axis calibration, so it stays empty. Verification - shared/tests/test_record_channel.py grows to 18 cases, covering extent, peak-preserving decimation, windowed reads, inverted/empty windows, unit-aware statistics and the time origin. - tools/ui_check.py gains "session browsing" and "layout preset" sections (223 checks, 0 failures overall). - apps/studio/tests/test_ui_smoke.py covers the same invariants. Not in this change: synchronised playback of video, depth and curves. It needs a decision on how depth is stored for replay (per-frame PNG, reduced-rate PNG, or offline recomputation); the cursor plumbing (cursor_moved / set_cursor) is already in place for it.
This commit is contained in:
@@ -102,7 +102,24 @@
|
||||
"fn": "FN",
|
||||
"fx": "FX",
|
||||
"fy": "FY",
|
||||
"no_data": "—"
|
||||
"no_data": "—",
|
||||
"mode_follow": "Following live",
|
||||
"mode_browse": "Browsing history (display paused; capture and recording continue)",
|
||||
"return_latest": "Back to latest",
|
||||
"return_latest_tip": "Leave history browsing and follow the newest data again",
|
||||
"overview": "Whole session",
|
||||
"overview_tip": "Fit the entire session; drag the overview strip to move the window",
|
||||
"box_zoom": "Box zoom",
|
||||
"box_zoom_tip": "Switch to rubber-band zoom (drag a region on the chart)",
|
||||
"selection": "Selection {duration}s / {samples} samples — {detail}",
|
||||
"selection_empty": "No valid samples in the selection",
|
||||
"browse_only_tip": "Drag or scroll on the chart to browse history"
|
||||
},
|
||||
"layout": {
|
||||
"default": "Free layout",
|
||||
"preset_2d_name": "2D mode (depth + charts)",
|
||||
"preset_2d": "2D mode: depth map on the left, force charts on the right, one shared time axis",
|
||||
"preset_default": "Free layout restored"
|
||||
},
|
||||
"record": {
|
||||
"label": "Record",
|
||||
@@ -135,10 +152,40 @@
|
||||
"sn_placeholder": "Serial number (optional)",
|
||||
"live_raw_label": "Live raw",
|
||||
"live_raw": "Live raw FN={fn} FX={fx} FY={fy}",
|
||||
"col_weight": "Weight (N)",
|
||||
"col_weight": "Reference load",
|
||||
"col_fn": "FN mean",
|
||||
"col_fx": "FX mean",
|
||||
"col_fy": "FY mean",
|
||||
"col_stability": "Stability",
|
||||
"col_state": "State",
|
||||
"col_raw": "Raw mean",
|
||||
"col_n": "n",
|
||||
"col_std": "σ",
|
||||
"state_ok": "OK",
|
||||
"state_excluded": "Excluded",
|
||||
"state_validation": "Validation",
|
||||
"state_exclude": "Exclude",
|
||||
"state_exclude_tip": "Drop the selected point from the fit and the export (kept on record, reversible)",
|
||||
"state_restore": "Restore",
|
||||
"mark_validation": "Mark validation",
|
||||
"mark_validation_tip": "Hold the selected point out of the fit and use it to test the fit",
|
||||
"excluded": "Point excluded from the fit",
|
||||
"restored": "Point restored",
|
||||
"marked_validation": "Point marked as a validation point",
|
||||
"unmarked_validation": "Validation mark removed",
|
||||
"fit_insufficient": "A fit needs at least 2 usable points (currently {count})",
|
||||
"fit_summary": "Fit {channel}: N = {gain} × raw + {offset} ({count} points, max residual {residual} N)",
|
||||
"fit_validation": "Independent validation on {count} point(s), max deviation {error} N",
|
||||
"point_title": "Calibration point {channel} @ {load} N",
|
||||
"point_channel": "Loaded axis",
|
||||
"point_direction": "Direction",
|
||||
"point_round": "Round",
|
||||
"point_duration": "Actual sampling time",
|
||||
"point_duration_value": "{actual} s (requested {requested} s)",
|
||||
"point_samples": "Samples",
|
||||
"point_samples_value": "{valid} valid / {total} total",
|
||||
"point_axes": "Three-axis mean ± std",
|
||||
"point_residual": "Fit residual",
|
||||
"col_actions": "",
|
||||
"recording": "Recording calibration samples…",
|
||||
"recording_stop_tip": "Click to cancel this calibration recording (always available)",
|
||||
|
||||
Reference in New Issue
Block a user