Files
orisys-sdk/docs/calibration-model-contract.zh.md
ykcheungab ff7614a6a0 接入力标定 schema 3 与多点空间增益模型
保留旧格式和 cal_path,校验 SN、特征身份及坐标附件,按帧返回逐轴状态。相关测试 263 项通过,已核对存量预测并实测 180 帧录像。
2026-10-18 12:56:40 +08:00

249 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 统一力标定模型契约
Purpose: reference. Scope: SDK 标定文件选择、SN 绑定和推断接口。
状态:2026-10-18 已实现 schema 3 的空间/全局增益;ML 后端仅保留契约,尚未实现。
保持 `cal_path` 与现有单点文件;新增空间模型和机器学习模型使用同一入口。
## 现状与边界
现有实现见 [calibration.py](../src/orisys/calibration.py)、
[Sensor](../src/orisys/sensor.py)、[profile resolver](../src/orisys/sensors/calibration.py)。
当前读 schema 1/2,按 SN 加载要求三个通道,转换只接收三个力标量。
`ForceReadout.apply` 会分别转换滤波前后数据,不能直接挂接带状态的空间或 ML 模型。
| 所有者 | 责任 |
|---|---|
| 离线实验 / AutoCal | 采集、训练、验证、导出模型;将实验坐标转换为契约坐标 |
| SDK | 选文件、校验身份与输入、准备特征、推断、输出逐轴有效性 |
| 应用 | 提供 SN 与文件选择,展示状态;不自己套用第二次增益 |
| 设计资产 | 几何与坐标定义;不提供每台设备的实测标定值 |
SDK 不训练模型、不导入应用代码、不读取训练 CSV,不运行文件内指定的 Python。
实验 `finger_multical.experiment_model/1` 必须显式导出,不能直接当 SDK 文件加载。
## 文件选择:保留路径,文件决定模型
不增加 `model_type` 构造参数或全局活动模型。`Sensor`、`FingerRuntime.open` 和
profile 创建处理器统一复用内部选择器;现有入口参数只映射为选择请求。
| 请求 | 文件与行为 |
|---|---|
| 非空 `cal_path` | 精确加载该文件;缺失、错误或不兼容时报错,不换文件 |
| `calibration_dir` + `device_id` | 保持 `<dir>/<sensor_type>/<device_id>.json`;缺文件保持 raw 并报告 missing,不回退产品模板 |
| 两者都未提供 | 保持产品包默认解析;有 descriptor 时用 `default_calibration`,旧包保留既有候选顺序 |
| profile 的 `CalibrationSelection.NONE` | 明确不标定;不赋予 `cal_path="none"` 新语义 |
`cal_path` 与 `calibration_dir` 仍互斥。构造入口 `device_id` 对应文件 `identity.sn`,
不新造另一个 SN 字段。未指定设备的录像可显式选文件,但实际 SN 一致性标记为 unknown。
有 `device_id` 时严格匹配 `sensor_type`、`sn`;不根据相机编号猜 SN。
保留 `device_id` 单独传入时的参数错误;目录本身不存在也报错,不能当成 SN 文件缺失。
```python
# 沿用现有调用参数;cal_path 可以选择 schema 1/2/3 文件。
runtime = FingerRuntime.open(
config="finger", video="test.mp4",
device_id="DD02-GL-001",
cal_path="calibrations/finger/DD02-GL-001.json",
)
# 归零值来自本次无载窗口,不是模型内固定常量。
runtime.set_calibration_reference({"fn": measured_zero_raw}, reference_id="unloaded-1")
# 自动按 SN 选择仍使用 calibration_dir="calibrations",不同时传 cal_path。
```
同一 SN 比较不同模型时,显式指定不同 JSON;默认 SN 文件只代表一个选定版本。
不扫描目录选“最新模型”,不在每帧重新加载。
## 最小文件:schema 3
复用 `force_calibration.json` 文件角色,增加 schema 3;文件名不参与模型类型判断。
以下是三点结构示例,数值和 SHA 占位值不是设备标定结果。
```json
{
"schema_version": 3,
"unit": "N",
"identity": {"sensor_type": "finger", "sn": "DD02-GL-001"},
"pipeline": {
"stitch_resolution": [700, 750],
"dis_mode": 4,
"hhd_downsample": 3,
"source_scale": 0.42,
"filters": {"measurement": {"flow_ema_alpha": 1.0}},
"feature_signature_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"geometry_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"coordinates": {
"frame": "autocal_flat",
"unit": "px",
"origin_xy": [511, 1021],
"map_sha256": "0000000000000000000000000000000000000000000000000000000000000000"
}
},
"model": {
"type": "spatial_gain/1",
"input_profile": "fn_delta_autocal_uv/1",
"output_frame": "sdk_force/1",
"outputs": ["fn"],
"params": {
"points_uv": [[-145, 604], [145, 604], [5, 430]],
"gains_N_per_raw": [0.00015, 0.00020, 0.00025],
"triangles": [[0, 1, 2]],
"valid_force_range_N": [0.0, 3.0]
}
}
}
```
| 字段 | 规则 |
|---|---|
| `schema_version` | 整数 3;未知版本拒绝 |
| `unit` | 固定 `N` |
| `identity` | `sensor_type`、非空 `sn` 必填;文件位置不能替代身份 |
| `pipeline` | 输入 profile 要求的实际处理参数、几何和坐标身份;缺少必需项或不匹配则拒绝 |
| `model.type` | SDK 已注册的版本化标识;决定参数解析和推断,不是模块路径 |
| `input_profile` | SDK 已实现的特征、单位、形状、预处理和时序契约 |
| `output_frame` | `sdk_force/1` 延续对应处理 profile 的 FN/FX/FY 符号和基方向;不是世界坐标合力模长 |
| `outputs` | 非空、无重复的 `fn/fx/fy` 子集;支持 FN-only,不冒充三轴标定 |
| `params` | 模型类型私有参数;通用加载器不解释点数或网络层 |
`provenance` 可选,保存数据摘要、训练版本和验证指标;不作为运行时输入。
其余未知语义字段拒绝,不能靠忽略字段误读新模型。新增模型类型不必升级外层 schema;
修改输入或算法语义必须升级相应 `/1` 标识。
`geometry_sha256` 是所用计算 mesh 文件字节的 SHA-256;`map_sha256` 是 SDK 坐标映射定义的
附件字节摘要,包含坐标转换结果,不能只比较图幅。pipeline 的必需键由输入 profile 固定,
覆盖该 backend 所有影响测量的设置;只检查示例中的部分配置不算兼容校验。
新增 profile 必须定义闭合的签名字段表。首个 profile 的 `feature_signature_sha256` 摘要
对象包含 `feature_version`、`processing_backend`、有效的图像预处理/光流/HHD 配置和计算资产哈希;
不含文件绝对路径、SN、显示资源或力读数滤波。以 UTF-8、键排序、无多余空格的 JSON 计算 SHA-256。
特征定义或单位改变须升级 `feature_version`,配置覆盖后重新计算,不能用配置文件名代替。
实现由 `calibration_context.build_context` 统一生成,特征版本为 `orisys_hhd_raw/1`。
## 空间增益模型
`spatial_gain/1` 固定输出 `fn`,使用 `fn_delta_autocal_uv/1`:
- 输入为同一帧的未经过力读数滤波的 FN raw、有效会话零点及接触 U/V。
上游光流滤波属于 pipeline 身份;不得混用不同特征链。
`delta_raw`、U、V 为有限浮点标量,前者单位 raw,后两者为上述有符号像素。
- SDK 会话准备 `delta_raw = fn_raw - zero_raw`,模型不再次扣零。
零点必须来自明确的归零操作或已标注窗口;模型文件不保存某段录像的启动零点。
- U 向右、V 向上,`x=origin_x+U`、`y=origin_y-V`。
SDK 提供并校验坐标转换;不能让模型偷偷按比例换算,不能运行时依赖 AutoCal。
- `points_uv` 为 N×2,`gains_N_per_raw` 长度 N;N≥3 且非共线、坐标唯一、增益有限且为正。
`triangles` 为任意数量的三索引,必须构成支持点凸包内完整、无重叠的非退化三角网。
保存连接关系以避免重新剖分引入变化;点数由数组决定,不固定为 9/15。
- 对三角形内查询,`g=Σw_i*g_i`,`fn_N=g*delta_raw`;不插值倒数,不外推。
未覆盖、位置失败、归零无效均返回无效,不能偷偷改用 M0。
- `valid_force_range_N` 必填。估计超范围时保留数值并标记 `out_of_range`,不截断。
FingerRuntime 使用 `artifacts` 中 `role="coordinate_map"` 的只读 NPZ:
`uv_by_output_px` 为 H×W×2 有符号 UV,`valid_mask` 为 H×W bool;可附 `origin_xy` 与
`flat_mapping_sha256`。`pipeline.output_domain_sha256` 绑定实际展开的 remap、mask 和 world_xyz;
形状、几何或特征身份不同则在打开视频前拒绝。离线直接提供 UV 的模型可以没有映射附件。
单点/两点数据不自动切换算法;可导出独立的 `global_gain/1`,参数为各输出轴的增益和有效范围,
输入 profile 为 `force_delta/1`,由 SDK 按同一归零语义准备对应 raw 增量。
这不同于旧曲线的绝对 raw 输入;兼容适配器不得替旧文件额外扣零。
## 机器学习扩展
同一 SN、路径和外层字段不变,只替换 `model`。下面只预留 `onnx/1` 示例,
不表示 SDK 已支持 ONNX;后端实现和输入 profile 验证通过前必须拒绝加载。
```json
{
"type": "onnx/1",
"input_profile": "fn_delta_autocal_uv/1",
"output_frame": "sdk_force/1",
"outputs": ["fn"],
"params": {
"input_tensor": "features",
"output_tensor": "force_N",
"feature_order": ["delta_raw", "u", "v"],
"dtype": "float32",
"shape": [1, 3],
"valid_force_range_N": [0.0, 3.0]
},
"artifacts": [
{
"role": "weights",
"path": "DD02-GL-001/model.onnx",
"sha256": "0000000000000000000000000000000000000000000000000000000000000000"
}
]
}
```
此标量 profile 的规范输入即上述顺序、类型、形状;输出 float32 `[1, len(outputs)]`,
轴序严格按 `outputs`。归一化及单位转换封装在已验证的模型图内,不由应用另做。
`fn_delta_autocal_uv/1` 只允许 `outputs=["fn"]`,不能凭 FN 特征宣称三轴已标定。
需要图像、完整光流或历史序列时新增输入 profile,明确 H/W、通道、掩码、单位和时间窗口;
没有对应特征的处理器拒绝加载,不能用零张量凑齐输入。
`artifacts` 只在有外部文件时必填;路径相对所选 JSON 的父目录,禁止绝对路径、URL、
`..` 或链接逃逸。校验 SHA-256、角色和文件完整性后才初始化受支持的后端。
不加载 pickle、任意 Python 或模型自带扩展库;缺推断依赖须明确报错,不自动安装。
文件与所有附件共同构成内容指纹,不能只哈希 JSON。
附件间依赖也须逐项声明和校验,未声明的外部权重不可加载。
## 每帧推断与有效性
内部统一生命周期如下,`calibration.load` 分流到旧曲线或新模型:
```text
load(selected_file, device_identity, pipeline) -> CalibrationModel
model.predict(frame_id, timestamp_s, profile_features, reference_id) -> CalibrationResult
model.reset(reference_id)
CalibrationResult:
values_N: {axis: finite_float | null}
status: {axis: ok | no_contact | invalid_input | zero_unavailable |
coordinate_unmapped | outside_coverage | out_of_range}
```
FN 空间模型输入仅允许单一接触;当前定位器只返回一个中心,无法证明不存在多接触,
不能宣称已标定总力。全局 `force_delta/1` 不要求定位,未提供接触状态不等同于无接触。
无接触、失败帧的校准输出为空,raw 另行保留;缺失值不是 0 N。
`out_of_range` 有数值但不在验证范围,其余失败状态无数值;未声明轴不可用、单位仍为 raw。
不把某轴 raw 填进 `values_N`,也不只靠全局 `force_unit` 判断三轴单位。
schema 3 的旧数值读数接口以 NaN 表示已声明轴的无效输出,并同步暴露上述状态;
`force_calibrated` 表示已加载模型能力,不表示当前帧有效。
一个模型实例属于一个流;按帧顺序每帧推断一次,读数和录像复用结果。
清零、重连、倒播或更换模型时重置状态;不同 SN 不共享可变状态。
schema 3 先做上游测量与归零、模型推断,再做有效 N 读数的显示滤波;失败帧不进滤波历史。
schema 1/2 仍保留旧滤波/转换顺序。状态模型不能塞入会调用两次的旧 `convert` 回调。
加载诊断至少包含路径、来源、SN 校验状态、模型/输入 profile、轴集合、内容指纹和失败原因。
加载不匹配失败与逐帧暂时无效分开;替换时先完成加载校验,失败不得破坏原活动模型。
Sensor 提供 `bind_calibration_context`、`set_calibration_reference`、`apply_calibration_frame`;
FingerRuntime 自动绑定真实上下文,并转发归零。`FrameSnapshot.force_units` 和
`calibration_status` 随帧保存,不能用稍后读取的 Sensor 状态解释旧帧。
归零与换模型须在计算线程或暂停处理时调用,不承诺并发修改安全。
## 兼容、实现位置与验收
新读取器并存支持 schema 1/2/3;旧文件不重写,SN 校验的旧三通道要求保持。
schema 3 按声明轴校验,允许 FN-only;旧 SDK 遇 schema 3 必须拒绝,不能降级猜读。
回退通过恢复旧文件的 `cal_path`;SN 默认文件切换前保留旧副本,不覆盖唯一证据。
| 修改位置 | 最小工作 |
|---|---|
| `calibration.py` | schema 分流、身份和附件校验;旧曲线适配器 |
| `sensors/calibration.py` | 统一选择器与指纹;支持 SN 请求 |
| 新 `calibration_models.py` | 受控类型表、空间增益与模型生命周期;ML 后端另行实现 |
| `sensor.py`、`finger/runtime.py`、处理器适配层 | 提供同帧位置/零点,缓存逐轴结果和状态 |
| Sensor 与 `ForceReadout` | 复用读数滤波器;schema 3 在推断后逐轴滤波,旧路径不变 |
| 离线导出器 | 实验模型到 schema 3;SDK 不依赖实验目录 |
当前导出入口为 workspace 的 `scripts/finger_multical/export_sdk.py`,输出已有文件时拒绝覆盖。
`data/dd02-gl/force_calibration.v3.json` 与同名 `.coordinates.npz` 是本次实验交付,
`identity.sn="dd02-gl"` 为实验标识,未从硬件读取;原三边缘点与非单调点警告保留于 provenance。
它不是生产包默认文件,需显式 `cal_path` 选择。
实现验收必须覆盖:旧结果与单位不变;显式路径/SN/默认/关闭选择;错误 SN 不串用;
3/14/更多点、退化网格与凸包外;节点和插值预测;零点不重复扣除;缺坐标/无接触;
FN-only 的 FX/FY 不冒充 N;未知模型、附件损坏和依赖缺失;状态模型每帧只调用一次及 reset。
另以独立录像验证物理准确度,契约或单元测试通过不代表标定合格。