22cc59c402
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.
296 lines
14 KiB
JSON
296 lines
14 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…",
|
||
"playback": "Open recording replay…",
|
||
"playback_latest": "Replay most recent recording",
|
||
"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"
|
||
},
|
||
"playback": {
|
||
"title": "Replay recording",
|
||
"open": "Open recording directory…",
|
||
"open_failed": "Could not open that recording: {error}",
|
||
"none_found": "No recording found under outputs/recordings",
|
||
"loaded": "{name} — {frames} frames / {duration} s",
|
||
"empty": "Pick a recording directory to start replay",
|
||
"position": "{position} / {duration} s",
|
||
"show_newton": "Show newtons",
|
||
"show_raw": "Show raw",
|
||
"recompute_depth": "Recompute depth",
|
||
"recompute_depth_tip": "Depth is not stored with a recording; recompute this frame offline from the video and the config",
|
||
"frame_dropped": "This frame never reached the video (the record queue overflowed)",
|
||
"frame_unreadable": "This frame could not be decoded from the video",
|
||
"depth_hint": "Depth is not stored: press Recompute depth to rebuild it from the video and the config",
|
||
"depth_running": "Recomputing depth… (on a worker thread)",
|
||
"depth_busy": "The previous recomputation is still running",
|
||
"depth_empty": "No depth data for this frame",
|
||
"depth_failed": "Depth recomputation failed: {detail}",
|
||
"depth_sdk_unavailable": "Cannot recompute: the SDK is not available",
|
||
"depth_config_missing": "Cannot recompute: the recording has no sensor config reference",
|
||
"depth_config_file_missing": "Cannot recompute: the config file no longer exists at that path",
|
||
"depth_no_video": "Cannot recompute: this recording has no video segment",
|
||
"depth_not_recomputed": "Depth has not been recomputed for this cursor position\n(the previous frame's map is not reused)",
|
||
"depth_recomputing": "Recomputing depth for the current cursor position…",
|
||
"depth_frame_not_recorded": "This frame was not recorded: its data row was lost to a full queue and only the index row remains",
|
||
"depth_frame_not_in_video": "This frame never reached the video, so its depth cannot be recomputed",
|
||
"depth_state_across_segment": "Replay cannot carry the algorithm state across segments: this one restarts at its first frame",
|
||
"depth_state_approximate": "No reset or pre-roll history for this segment: the algorithm state is an approximation",
|
||
"depth_warnings": "Recomputation notes: {detail}",
|
||
"warnings": "Replay notes: {detail}",
|
||
"warn_config_changed": "the config differs from recording time, so the recomputed depth may differ",
|
||
"warn_sdk_changed": "the SDK version differs from recording time, so the recomputation may differ",
|
||
"warn_config_missing": "no config hash was recorded, so recomputation cannot be verified",
|
||
"warn_depth_recomputed": "depth comes from offline recomputation, not from stored data",
|
||
"warn_rows_skipped": "some unparsable CSV rows were skipped",
|
||
"warn_frames_dropped": "frames were lost to a full queue; only their index rows remain (shown as \"not recorded\")",
|
||
"warn_state_across_segment": "the algorithm state cannot be carried across a segment",
|
||
"warn_state_approximate": "the algorithm state is an approximation"
|
||
},
|
||
"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 coefficients…",
|
||
"export_tip": "Write the SDK coefficient file only; the running device is untouched",
|
||
"apply": "Apply to device",
|
||
"apply_tip": "Push the last export to the running device (confirmed again)",
|
||
"apply_no_export": "No exported coefficients yet — export them first",
|
||
"save_record": "Save record…",
|
||
"save_record_tip": "Save every point of this session with its full three-axis samples (audit data), independently of the coefficient file",
|
||
"save_record_empty": "No calibration points to save yet",
|
||
"save_record_ok": "Saved the calibration record to {path}\n{points} point(s), {samples} three-axis samples",
|
||
"save_record_failed": "Could not save the calibration record: {error}",
|
||
"open_record": "Open record…",
|
||
"open_record_tip": "Reopen a saved calibration session to inspect every point's samples and diagnostics",
|
||
"open_record_filter": "Calibration records (*.json)",
|
||
"open_record_ok": "Loaded {path} ({points} point(s))",
|
||
"open_record_warnings": "Note: {detail}",
|
||
"open_record_failed": "Could not open the calibration record: {error}",
|
||
"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": "Exported model {channel}: {method} ({count} points / {breakpoints} breakpoints, max residual {residual} N)",
|
||
"fit_validation": "Independent validation on {count} point(s), max deviation {error} N",
|
||
"method_piecewise_linear": "piecewise linear",
|
||
"method_linear": "linear",
|
||
"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_pending": "Requested calibration for {sensor_type}; waiting for the device to confirm…",
|
||
"applied": "Device confirmed the calibration is in effect (version {version})",
|
||
"export_only": "Exported coefficients to {path} (not applied to the device)",
|
||
"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}"
|
||
}
|
||
}
|