73592f085f
What
- record/index.py: RecordingIndex streams forces.csv once and keeps only the
small things — one time, one validity flag, one frame id per row plus a byte
offset every 4096 rows (~6.5 MB per 500k rows). row_range() bisects the times,
read_rows() seeks to the nearest checkpoint and scans forward, and overview()
folds every row into its bucket during the same pass to build a per-axis
min/max envelope.
- read_rows(max_points, unit) decimates **while streaming**: each bucket keeps
the extreme row of every axis in the requested unit, so a long interval never
has to exist in memory in full and a shear spike survives a flat normal channel.
- RecordingReader: info() decimates above max_rows (default 200k) and marks
itself decimated, with total_rows and a duration taken from the frame index
rather than from the view; window()/window_all()/overview()/statistics() read
the interval being asked about. list/window semantics unchanged for small
recordings.
- force/source.py: StoreCurveSource (live memory) and RecordingCurveSource (disk
interval + cached envelope) implement one interface — extent, window,
envelope, statistics, duration_s — so ForcePlotsPanel asks a *source* instead
of knowing where the data lives.
- force_plots: reads through the source (window, envelope, statistics, extent),
which also replaces its last direct store accesses.
- playback_dialog: draws the whole-session view through window_all() in the unit
being shown (bounded by CHART_MAX_POINTS), and says so when the view is
decimated ("显示 N / 共 M 行") instead of letting the operator count points.
- experiment._run: a failure that lands while the files are opening can no longer
be overwritten by the writer's own transition to "recording" (found by a flaky
test; the state check now covers STATE_FAILED).
Why
The store releases old blocks above ~4.6 h and the replay loaded every CSV row
into Python lists before drawing anything, so neither the live browser nor the
replay could show an earlier interval of a long recording. The index makes the
disk the data source and memory a cache of what is on screen.
Verification
- shared/tests/test_history_source.py (new, 8 cases) over a 12 000-row recording:
the index reports its rows and reads a bounded interval; a large recording is
marked decimated with its true duration and total rows; a small one is not; an
interval read sees the exact rows including a single peak; a decimated window
keeps the peak; the envelope fits the whole session in ≤200 points, keeps the
spike and is cached; the disk and memory sources agree on extent, values and
statistics; the whole session can be read without holding it all. Shared
suites: 146 tests, 0 failures (three consecutive runs).
- apps/studio/tools/ui_check.py: the replay section now checks the index row
count, that an interval read spans only the interval, the envelope size and
total_rows. 324 checks, 0 failures.
- apps/studio/tests/test_ui_smoke.py: 43 tests, 36 pass; the 7 failures are the
unchanged sandbox/harness artifacts.
- D:/orisys_ws/_review_phase12/reproduce.py: all ten lines unchanged;
compileall, locale and prefs parity clean.
- apps/studio/docs/record-channel.md §8 documents the index, the reader API and
the unified source, and lists what is still hardware work.
Not in this change
- Maximum RSS / backlog / loss measurements at real resolution on the target ARM
board (needs the hardware), and range CSV export.
314 lines
16 KiB
JSON
314 lines
16 KiB
JSON
{
|
||
"app": {
|
||
"title": "Orisys Studio",
|
||
"about_title": "关于",
|
||
"about_body": "Orisys Studio 桌面应用\n版本: {app_version}",
|
||
"scaffold_hint": "脚手架界面 — 采集功能稍后加入"
|
||
},
|
||
"brand": {
|
||
"name": "ORISYS",
|
||
"tagline": "叠动科技"
|
||
},
|
||
"menu": {
|
||
"file": "文件",
|
||
"open_video": "打开视频…",
|
||
"reveal_config": "在资源管理器中显示配置…",
|
||
"playback": "打开录制回放…",
|
||
"playback_latest": "回放最近一次录制",
|
||
"view": "视图",
|
||
"theme": "主题",
|
||
"theme_dark": "深色",
|
||
"theme_light": "浅色",
|
||
"language": "语言",
|
||
"lang_zh": "简体中文",
|
||
"lang_en": "English",
|
||
"calibration_panel": "标定面板",
|
||
"finger_3d": "手指 3D 视图",
|
||
"show_fps": "显示 FPS",
|
||
"plot_settings": "绘图设置…",
|
||
"rotate_arrows": "旋转箭头图 90°",
|
||
"run": "运行 / 停止",
|
||
"stop": "取消采集",
|
||
"preview": "预览 / 停止预览",
|
||
"reset": "重置追踪器",
|
||
"help": "帮助",
|
||
"about": "关于"
|
||
},
|
||
"dialog": {
|
||
"plot_settings_title": "绘图设置",
|
||
"plot_window": "时间窗口",
|
||
"plot_window_tip": "力曲线时间窗口(秒)",
|
||
"plot_fn_max": "FN 上限",
|
||
"plot_fn_max_tip": "法向力纵轴上限(牛顿模式)",
|
||
"plot_shear_max": "剪切上限",
|
||
"plot_shear_max_tip": "剪切力 FX/FY 纵轴 ±上限(牛顿模式)",
|
||
"plot_fn_raw_max": "FN 原始上限",
|
||
"plot_fn_raw_max_tip": "未标定(原始)模式下的法向力纵轴上限。0 = 按数据自动量程;默认值按实测峰值设定,使静止时的抖动在图中不可见",
|
||
"plot_shear_raw_max": "原始剪切上限",
|
||
"plot_shear_raw_max_tip": "未标定(原始)模式下的剪切力 FX/FY 纵轴 ±上限。0 = 自动量程",
|
||
"reset_defaults": "恢复默认",
|
||
"ok": "确定",
|
||
"cancel": "取消"
|
||
},
|
||
"toolbar": {
|
||
"source": "来源",
|
||
"source_camera": "摄像机",
|
||
"source_video": "视频文件",
|
||
"search_cameras": "搜索",
|
||
"cam_id": "摄像机",
|
||
"browse_video": "浏览…",
|
||
"video_placeholder": "未选择视频",
|
||
"preview": "预览",
|
||
"preview_stop": "停止预览",
|
||
"run": "运行",
|
||
"stop": "停止",
|
||
"run_label": "▶ 运行",
|
||
"stop_label": "■ 停止",
|
||
"cancel_label": "■ 取消连接",
|
||
"cancel_tip": "中断连接(连接卡住时随时可用,快捷键 Esc)",
|
||
"run_unavailable": "当前不可运行",
|
||
"run_disabled_video": "请先选择一个视频文件",
|
||
"run_disabled_sensor": "此传感器类型暂不支持运行",
|
||
"reset": "重置",
|
||
"units": "单位",
|
||
"units_newton": "牛顿",
|
||
"units_raw": "原始"
|
||
},
|
||
"units": {
|
||
"newton_short": "N",
|
||
"raw_short": "raw"
|
||
},
|
||
"sensor": {
|
||
"type": "产品",
|
||
"config": "配置",
|
||
"thumb_placeholder": "产品型号"
|
||
},
|
||
"panels": {
|
||
"arrow": "形变箭头",
|
||
"view_arrows_pure": "箭头",
|
||
"view_arrows_stitched": "箭头/采集图像",
|
||
"view_depth": "深度图",
|
||
"view_mode_tip": "运行时的图像显示模式",
|
||
"arrow_empty": "尚未采集图像\n请选择视频或摄像机后点击「运行」",
|
||
"arrow_empty_tip": "运行或预览后在此显示形变箭头图",
|
||
"depth_unavailable": "本帧没有深度数据\n(不是深度为零,也未回退显示普通形变图)",
|
||
"depth_scale": "深度色标 {low} – {high}",
|
||
"depth_scale_tip": "本会话固定色标:色标范围锁定在首个有效深度帧,便于跨帧比较;灰色区域表示无深度数据",
|
||
"plots": "力曲线图",
|
||
"cal": "标定",
|
||
"mesh3d": "三维网格",
|
||
"mesh3d_empty": "手指三维视图占位 —\nOpen3D 网格稍后加入",
|
||
"mesh3d_empty_tip": "手指传感器的三维网格预览"
|
||
},
|
||
"chart": {
|
||
"fn": "FN",
|
||
"fx": "FX",
|
||
"fy": "FY",
|
||
"no_data": "—",
|
||
"mode_follow": "跟随实时",
|
||
"mode_browse": "浏览历史(显示暂停,采集与记录继续)",
|
||
"return_latest": "返回最新",
|
||
"return_latest_tip": "退出历史浏览,恢复跟随最新数据",
|
||
"overview": "全程总览",
|
||
"overview_tip": "把整个会话缩放到一屏;拖动下方总览条可移动视窗",
|
||
"box_zoom": "框选",
|
||
"box_zoom_tip": "切换为框选放大(拖动图表框选区域)",
|
||
"selection": "选区 {duration}s / {samples} 点 — {detail}",
|
||
"selection_empty": "选区无有效样本",
|
||
"value_unavailable": "不可用",
|
||
"value_unavailable_tip": "{channel} 轴未加载标定曲线,因此没有该单位的数值(不会用原始值冒充)",
|
||
"browse_only_tip": "拖动或滚轮即可进入历史浏览"
|
||
},
|
||
"layout": {
|
||
"default": "自由布局",
|
||
"preset_2d_name": "二维模式(深度图 + 曲线)",
|
||
"preset_2d": "已切换到二维模式:左侧深度图、右侧三轴曲线,共用时间轴",
|
||
"preset_default": "已恢复自由布局"
|
||
},
|
||
"playback": {
|
||
"title": "录制回放",
|
||
"open": "打开录制目录…",
|
||
"open_failed": "无法打开该录制: {error}",
|
||
"none_found": "outputs/recordings 下没有找到录制",
|
||
"loaded": "{name} — {frames} 帧 / {duration} s",
|
||
"empty": "选择一个录制目录开始回放",
|
||
"position": "{position} / {duration} s",
|
||
"show_newton": "显示牛顿值",
|
||
"show_raw": "显示原始值",
|
||
"recompute_depth": "重算深度",
|
||
"recompute_depth_tip": "深度未随录制保存,按录像与配置离线重算当前帧",
|
||
"frame_dropped": "该帧未被写入视频(记录队列曾溢出)",
|
||
"frame_unreadable": "该帧无法从视频中解码",
|
||
"depth_hint": "深度未存储:点击「重算深度」按原视频与配置离线重算",
|
||
"depth_running": "正在重算深度…(在后台线程)",
|
||
"depth_busy": "上一次重算尚未完成",
|
||
"depth_empty": "该帧没有深度数据",
|
||
"depth_failed": "深度重算失败: {detail}",
|
||
"depth_sdk_unavailable": "无法重算:SDK 不可用",
|
||
"depth_config_missing": "无法重算:录制未记录传感器配置",
|
||
"depth_config_file_missing": "无法重算:配置文件的原始路径已不存在",
|
||
"depth_no_video": "无法重算:该录制没有视频分段",
|
||
"depth_not_recomputed": "当前游标位置尚未重算深度\n(不会沿用其他帧的结果)",
|
||
"depth_recomputing": "正在为当前游标位置重算深度…",
|
||
"depth_frame_not_recorded": "该帧未录制:数据行在队列溢出时丢失,只有索引记录",
|
||
"depth_frame_not_in_video": "该帧未写入视频,无法重算深度",
|
||
"depth_state_across_segment": "重算无法跨分段延续算法状态:本段从段首重放",
|
||
"depth_state_approximate": "本段缺少归零或预卷历史,算法参考状态为近似重建",
|
||
"depth_warnings": "重算提示: {detail}",
|
||
"warnings": "回放提示: {detail}",
|
||
"decimated": "该录制较长,曲线为降采样视图(显示 {shown} / 共 {total} 行;区间数据按需从磁盘读取)",
|
||
"warn_config_changed": "配置文件与录制时不一致,重算出的深度可能与当时不同",
|
||
"warn_sdk_changed": "SDK 版本与录制时不一致,重算结果可能不同",
|
||
"warn_config_missing": "找不到配置哈希,无法校验重算一致性",
|
||
"warn_depth_recomputed": "深度由离线重算得到,不是当时保存的数据",
|
||
"warn_rows_skipped": "有无法解析的 CSV 行被跳过",
|
||
"warn_frames_dropped": "有帧在队列溢出时丢失,仅保留索引记录(回放显示为“该帧未录制”)",
|
||
"warn_out_of_order": "该录制由旧版本写入,行序不是时间序;回放已按时间重排,区间边界可能与当时不同",
|
||
"warn_state_across_segment": "重算不能跨分段延续算法状态",
|
||
"warn_state_approximate": "算法参考状态为近似重建",
|
||
"warn_state_approximate_after_rewind": "为回到更早的帧重新打开了视频,参考状态可能偏离当时",
|
||
"warn_state_too_far_back": "归零点过于久远,需要读取的帧数超出上限,已按近似状态重建",
|
||
"warn_reset_frame_not_indexed": "归零帧不在帧索引中,无法从该点重建状态",
|
||
"warn_reset_frame_not_in_video": "归零帧未写入视频,无法从该点重建状态",
|
||
"warn_reset_not_applied": "重放无法重新执行归零,参考状态可能偏离当时"
|
||
},
|
||
"record": {
|
||
"label": "记录",
|
||
"mode_video_data": "视频 + 数据",
|
||
"mode_data_only": "仅数据",
|
||
"mode_tip": "视频记录的是处理前的相机输入图像(不含箭头与文字叠加)。MP4 是有损封装,不等于无损原始数据。",
|
||
"start_label": "● 开始记录",
|
||
"stop_label": "■ 停止记录",
|
||
"start_tip": "开始记录本实验:视频、力数据与元数据写入同一目录",
|
||
"stop_tip": "停止记录并写入 session.json(随时可用)",
|
||
"requires_run": "请先点击「运行」开始采集,再开始记录",
|
||
"started": "已开始记录({mode})",
|
||
"start_failed": "无法开始记录: {error}",
|
||
"finished": "记录完成 — {frames} 帧 / {segments} 段,已写入 {path}",
|
||
"finished_with_problems": "记录完成,但存在问题: {detail}",
|
||
"closing": "正在关闭记录文件…",
|
||
"failed_state": "记录已失败(详见摘要与日志)",
|
||
"alarm": "记录异常: {detail}"
|
||
},
|
||
"cal": {
|
||
"weight": "砝码重量 (N)",
|
||
"duration": "采集时长",
|
||
"record": "记录",
|
||
"recording_stop": "停止",
|
||
"record_disabled": "请先点击「运行」开始采集,再记录标定样本",
|
||
"record_cancelled": "已取消本次采集",
|
||
"empty_hint": "当前通道尚无样本:运行后选择通道并点击「记录」。",
|
||
"delete_row": "删除行",
|
||
"clear_channel": "清空通道",
|
||
"clear_all": "全部清空",
|
||
"export": "导出系数…",
|
||
"export_tip": "只写出 SDK 标定系数文件,不改变运行中的设备",
|
||
"apply": "应用到设备",
|
||
"apply_tip": "把最近导出的系数下发到运行中的设备(需再次确认)",
|
||
"apply_no_export": "还没有导出的系数文件,请先「导出系数」",
|
||
"save_record": "保存标定记录…",
|
||
"save_record_tip": "保存本次标定会话的全部点与三轴完整采样(审计数据),与系数文件相互独立",
|
||
"save_record_empty": "还没有标定点,无法保存标定记录",
|
||
"save_record_ok": "已保存标定记录 {path}\n{points} 个点,共 {samples} 条三轴采样",
|
||
"save_record_failed": "保存标定记录失败: {error}",
|
||
"open_record": "打开标定记录…",
|
||
"open_record_tip": "重新打开已保存的标定会话,检查每个点的完整采样与诊断状态",
|
||
"open_record_filter": "标定记录 (*.json)",
|
||
"open_record_ok": "已载入 {path}({points} 个点)",
|
||
"open_record_device": "记录设备: {device}",
|
||
"open_record_warnings": "注意: {detail}",
|
||
"open_record_failed": "打开标定记录失败: {error}",
|
||
"apply_mismatch_title": "标定与目标设备不匹配",
|
||
"apply_mismatch": "该标定文件属于: {device}\n不匹配项: {detail}\n\n当前目标: {sensor_type} / {config}\n\n继续应用会把不适用于本设备的系数写入运行中的设备。",
|
||
"apply_mismatch_skipped": "已取消应用(标定身份与目标设备不匹配)",
|
||
"sn": "序列号",
|
||
"sn_placeholder": "序列号(可选)",
|
||
"live_raw_label": "实时原始值",
|
||
"live_raw": "实时原始值 FN={fn} FX={fx} FY={fy}",
|
||
"col_weight": "参考载荷",
|
||
"col_fn": "FN 均值",
|
||
"col_fx": "FX 均值",
|
||
"col_fy": "FY 均值",
|
||
"col_stability": "稳定性",
|
||
"col_state": "状态",
|
||
"col_raw": "原始均值",
|
||
"col_n": "n",
|
||
"col_std": "σ",
|
||
"state_ok": "正常",
|
||
"state_excluded": "已排除",
|
||
"state_validation": "验证点",
|
||
"state_exclude": "排除",
|
||
"state_exclude_tip": "把选中点排除出拟合与导出(记录保留,可恢复)",
|
||
"state_restore": "恢复",
|
||
"mark_validation": "设为验证点",
|
||
"mark_validation_tip": "把选中点留作独立验证点:不参与拟合,用于检验拟合",
|
||
"excluded": "该点已排除出拟合",
|
||
"restored": "该点已恢复",
|
||
"marked_validation": "该点已标记为验证点",
|
||
"unmarked_validation": "该点已取消验证点标记",
|
||
"fit_insufficient": "拟合需要至少 2 个有效点(当前 {count} 个)",
|
||
"fit_summary": "导出模型 {channel}: {method}({count} 点 / {breakpoints} 个折点,最大残差 {residual} N)",
|
||
"fit_validation": "独立验证 {count} 点,最大偏差 {error} N",
|
||
"method_piecewise_linear": "分段线性",
|
||
"method_linear": "线性",
|
||
"point_title": "标定点 {channel} @ {load} N",
|
||
"point_channel": "加载轴",
|
||
"point_direction": "方向",
|
||
"point_round": "轮次",
|
||
"point_duration": "实际采样时长",
|
||
"point_duration_value": "{actual} s(请求 {requested} s)",
|
||
"point_samples": "样本数",
|
||
"point_samples_value": "有效 {valid} / 共 {total}",
|
||
"point_axes": "三轴均值 ± 标准差",
|
||
"point_residual": "拟合残差",
|
||
"col_actions": "",
|
||
"recording": "正在采集标定样本…",
|
||
"recording_stop_tip": "点击可取消本次标定采集(随时可用)",
|
||
"record_ok": "已记录 weight={weight} raw={raw}",
|
||
"record_failed_few": "采集失败 — 样本过少",
|
||
"export_ok": "已导出 {path}",
|
||
"export_local": "同时写入 {path}",
|
||
"export_warnings": "跳过: {detail}",
|
||
"export_failed": "导出失败: {error}",
|
||
"export_no_model": "没有可导出的模型: {detail}",
|
||
"apply_title": "应用标定",
|
||
"apply_confirm": "将刚导出的标定应用到当前设备?\n\n设备类型: {sensor_type}\n配置: {config}\n\n选择「否」只保留导出文件,不改动运行中的设备。",
|
||
"apply_skipped": "已导出,未应用到设备",
|
||
"apply_pending": "已请求把标定应用到 {sensor_type},等待设备确认…",
|
||
"applied": "设备已确认标定生效(版本 {version})",
|
||
"applied_none": "设备已确认:当前没有加载标定(该项留空)",
|
||
"export_only": "已导出系数 {path}(未应用到设备)",
|
||
"apply_no_target": "没有可应用的设备(请先运行一次采集)"
|
||
},
|
||
"status": {
|
||
"ready": "就绪",
|
||
"phase_idle": "空闲",
|
||
"phase_connecting": "连接中",
|
||
"phase_running": "运行中",
|
||
"phase_error": "出错",
|
||
"camera_none": "未发现摄像机",
|
||
"camera_found": "发现 {count} 个摄像机: {names}",
|
||
"preview_started": "预览已启动(摄像机 {camera_id})",
|
||
"preview_stopped": "预览已停止",
|
||
"preview_failed": "预览失败: {error}",
|
||
"connecting": "正在连接…",
|
||
"connect_slow": "连接耗时较长,可点击「取消连接」或按 Esc 中断",
|
||
"run_started": "运行中({source})",
|
||
"run_stopped": "已停止",
|
||
"run_stopping": "正在停止…(正在释放设备)",
|
||
"run_stopping_cancelled": "已取消连接(正在释放设备)",
|
||
"sample_invalid": "采样无效(第 {count} 次):{reason} — 该样本已标记无效,未计入曲线与标定",
|
||
"samples_dropped": "记录缓冲已达上限,最早的 {count} 个样本已被释放(全程记录不再完整)",
|
||
"run_failed": "启动失败: {error}",
|
||
"video_ended": "视频已结束",
|
||
"camera_disconnected": "摄像机已断开",
|
||
"reset_ok": "追踪器已重置",
|
||
"reset_pending": "已请求归零,等待采集线程确认…",
|
||
"finger_run_unavailable": "此传感器类型暂不支持运行",
|
||
"video_required": "请先选择视频文件",
|
||
"config_missing": "未找到传感器配置: {type}",
|
||
"units_raw_fallback": "无标定数据 — 显示原始单位",
|
||
"reveal_failed": "无法打开配置位置: {error}"
|
||
}
|
||
}
|