92da51217c
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.
234 lines
10 KiB
JSON
234 lines
10 KiB
JSON
{
|
||
"app": {
|
||
"title": "Orisys Studio",
|
||
"about_title": "About",
|
||
"about_body": "Orisys Studio desktop application\nVersion: {app_version}",
|
||
"scaffold_hint": "Scaffold shell — acquisition coming later"
|
||
},
|
||
"brand": {
|
||
"name": "ORISYS",
|
||
"tagline": "DieDong Technology"
|
||
},
|
||
"menu": {
|
||
"file": "File",
|
||
"open_video": "Open video…",
|
||
"reveal_config": "Reveal sensor config…",
|
||
"view": "View",
|
||
"theme": "Theme",
|
||
"theme_dark": "Dark",
|
||
"theme_light": "Light",
|
||
"language": "Language",
|
||
"lang_zh": "简体中文",
|
||
"lang_en": "English",
|
||
"calibration_panel": "Calibration panel",
|
||
"finger_3d": "Finger 3D view",
|
||
"show_fps": "Show FPS",
|
||
"plot_settings": "Plot settings…",
|
||
"rotate_arrows": "Rotate arrows 90°",
|
||
"run": "Run / Stop",
|
||
"stop": "Cancel acquisition",
|
||
"preview": "Preview / Stop preview",
|
||
"reset": "Reset tracker",
|
||
"help": "Help",
|
||
"about": "About"
|
||
},
|
||
"dialog": {
|
||
"plot_settings_title": "Plot settings",
|
||
"plot_window": "Time window",
|
||
"plot_window_tip": "Force plot time window (seconds)",
|
||
"plot_fn_max": "FN max",
|
||
"plot_fn_max_tip": "Normal force axis max (Newton mode)",
|
||
"plot_shear_max": "Shear max",
|
||
"plot_shear_max_tip": "Shear force axis ±max for FX/FY (Newton mode)",
|
||
"plot_fn_raw_max": "FN raw max",
|
||
"plot_fn_raw_max_tip": "Normal-force axis max in raw (uncalibrated) mode. 0 = auto range on the data; the default is sized so idle jitter is invisible in the plot",
|
||
"plot_shear_raw_max": "Shear raw max",
|
||
"plot_shear_raw_max_tip": "Shear-force axis ±max for FX/FY in raw (uncalibrated) mode. 0 = auto range",
|
||
"reset_defaults": "Reset to defaults",
|
||
"ok": "OK",
|
||
"cancel": "Cancel"
|
||
},
|
||
"toolbar": {
|
||
"source": "Source",
|
||
"source_camera": "Camera",
|
||
"source_video": "Video file",
|
||
"search_cameras": "Search",
|
||
"cam_id": "Cam ID",
|
||
"browse_video": "Browse…",
|
||
"video_placeholder": "No video selected",
|
||
"preview": "Preview",
|
||
"preview_stop": "Stop preview",
|
||
"run": "Run",
|
||
"stop": "Stop",
|
||
"run_label": "▶ Run",
|
||
"stop_label": "■ Stop",
|
||
"cancel_label": "■ Cancel",
|
||
"cancel_tip": "Abort the connection (always available while connecting, shortcut Esc)",
|
||
"run_unavailable": "Not available yet",
|
||
"run_disabled_video": "Select a video file first",
|
||
"run_disabled_sensor": "Run is not available for this sensor type",
|
||
"reset": "Reset",
|
||
"units": "Units",
|
||
"units_newton": "Newton",
|
||
"units_raw": "Raw"
|
||
},
|
||
"units": {
|
||
"newton_short": "N",
|
||
"raw_short": "raw"
|
||
},
|
||
"sensor": {
|
||
"type": "Product",
|
||
"config": "Config",
|
||
"thumb_placeholder": "Product model"
|
||
},
|
||
"panels": {
|
||
"arrow": "2D arrows",
|
||
"view_arrows_pure": "Arrows",
|
||
"view_arrows_stitched": "Arrows / Captured",
|
||
"view_depth": "Depth map",
|
||
"view_mode_tip": "Image display mode while running",
|
||
"arrow_empty": "No image yet\nPick a video or camera, then press Run",
|
||
"arrow_empty_tip": "The deformation arrow image appears here while running or previewing",
|
||
"depth_unavailable": "No depth data for this frame\n(not a depth of zero, and no silent fallback to the processed image)",
|
||
"depth_scale": "Depth scale {low} – {high}",
|
||
"depth_scale_tip": "Fixed for this session: the range is latched to the first frame that carried depth so frames stay comparable. Grey marks samples with no depth.",
|
||
"plots": "Force Charts",
|
||
"cal": "Calibration",
|
||
"mesh3d": "3D mesh",
|
||
"mesh3d_empty": "Finger 3D view placeholder —\nOpen3D mesh coming later",
|
||
"mesh3d_empty_tip": "3D mesh preview for finger sensors"
|
||
},
|
||
"chart": {
|
||
"fn": "FN",
|
||
"fx": "FX",
|
||
"fy": "FY",
|
||
"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",
|
||
"mode_video_data": "Video + data",
|
||
"mode_data_only": "Data only",
|
||
"mode_tip": "Video records the pre-processing camera input (no arrows or text overlays). MP4 is a lossy container, not a lossless copy of the raw data.",
|
||
"start_label": "● Start recording",
|
||
"stop_label": "■ Stop recording",
|
||
"start_tip": "Record this experiment: video, force data and metadata into one directory",
|
||
"stop_tip": "Stop recording and write session.json (always available)",
|
||
"requires_run": "Press Run to start acquisition before recording",
|
||
"started": "Recording started ({mode})",
|
||
"start_failed": "Could not start recording: {error}",
|
||
"finished": "Recording finished — {frames} frames / {segments} segment(s) in {path}",
|
||
"finished_with_problems": "Recording finished with problems: {detail}",
|
||
"alarm": "Recording problem: {detail}"
|
||
},
|
||
"cal": {
|
||
"weight": "Weight (N)",
|
||
"duration": "Record duration",
|
||
"record": "Record",
|
||
"recording_stop": "Stop",
|
||
"record_disabled": "Press Run to start acquisition before recording samples",
|
||
"record_cancelled": "Recording cancelled",
|
||
"empty_hint": "No samples for this channel yet: run, pick a channel and press Record.",
|
||
"delete_row": "Delete row",
|
||
"clear_channel": "Clear channel",
|
||
"clear_all": "Clear all",
|
||
"export": "Export…", "sn": "SN",
|
||
"sn_placeholder": "Serial number (optional)",
|
||
"live_raw_label": "Live raw",
|
||
"live_raw": "Live raw FN={fn} FX={fx} FY={fy}",
|
||
"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)",
|
||
"record_ok": "Recorded weight={weight} raw={raw}",
|
||
"record_failed_few": "Recording failed — too few samples",
|
||
"export_ok": "Exported {path}",
|
||
"export_local": "Also wrote {path}",
|
||
"export_warnings": "Skipped: {detail}",
|
||
"export_failed": "Export failed: {error}",
|
||
"apply_title": "Apply calibration",
|
||
"apply_confirm": "Apply the calibration just exported to the current device?\n\nSensor type: {sensor_type}\nConfig: {config}\n\nChoosing No keeps the exported file and leaves the running device untouched.",
|
||
"apply_skipped": "Exported; not applied to the device",
|
||
"apply_done": "Calibration applied to {sensor_type}",
|
||
"apply_no_target": "No device to apply to (run an acquisition first)"
|
||
},
|
||
"status": {
|
||
"ready": "Ready",
|
||
"phase_idle": "Idle",
|
||
"phase_connecting": "Connecting",
|
||
"phase_running": "Running",
|
||
"phase_error": "Error",
|
||
"camera_none": "No cameras found",
|
||
"camera_found": "Found {count} camera(s): {names}",
|
||
"preview_started": "Preview started (camera {camera_id})",
|
||
"preview_stopped": "Preview stopped",
|
||
"preview_failed": "Preview failed: {error}",
|
||
"connecting": "Connecting…",
|
||
"connect_slow": "This is taking a while — click “Cancel” or press Esc to abort",
|
||
"run_started": "Running ({source})",
|
||
"run_stopped": "Stopped",
|
||
"run_stopping": "Stopping… (releasing the device)",
|
||
"run_stopping_cancelled": "Connection cancelled (releasing the device)",
|
||
"sample_invalid": "Invalid sample (#{count}): {reason} — marked invalid and excluded from the curves and from calibration",
|
||
"samples_dropped": "Record buffer is full; the oldest {count} samples were released (the full session is no longer complete)",
|
||
"run_failed": "Run failed: {error}",
|
||
"video_ended": "Video ended",
|
||
"camera_disconnected": "Camera disconnected",
|
||
"reset_ok": "Tracker reset",
|
||
"finger_run_unavailable": "Run is not available for this sensor type",
|
||
"video_required": "Select a video file first",
|
||
"config_missing": "Sensor config not found for {type}",
|
||
"units_raw_fallback": "Calibration unavailable — showing raw units",
|
||
"reveal_failed": "Could not reveal config: {error}"
|
||
}
|
||
}
|