ff5f089dc1
- 标签顺序:几何与安装 放到 测试序列 之前 - 序列点增加可选 uv_px(向后兼容),点表新增 U/V 列;加入所选点、删除所选、 送到标定页(按 U/V 经同一套求解加入标定队列并切换页面) - 已选点结果改显示在选点区状态行,不再写入全局顶部状态条 - 序列页左上区改紧凑:名称与备注同一行,分“阵列”“运动参数”两页; 运动参数每行三组,输入框改用 compactField - 新增阵列工具(array_plan.py、ui/array_tool.py):UV 规则网格(可蛇形遍历)与 阶段预设,逐点检查是否在曲面内、是否留有边距,候选点实时预览在 2D 图上, 只有可用点才能加入,已在序列里的点不重复加入 - 2D 平面图:标记改为缩放后再绘制并加深色描边,修复缩小后点不清; 底图可选 原图/灰度/变暗/仅轮廓,点色可选 黄/白/青/橙/灰(默认灰度+黄,写入偏好); 序列点带编号并连线显示遍历顺序,选中行高亮 - 新增测试 test_sequence_uv、test_sequence_to_capture、test_array_plan、test_array_tool
174 lines
7.2 KiB
Markdown
174 lines
7.2 KiB
Markdown
# Calibration sequence format (`.oryseq`)
|
|
|
|
A calibration sequence is a small JSON document: the **profile** that generated
|
|
the points, plus the **points** themselves. It is written by
|
|
`导出序列…` in the Sequence tab and read by `导入序列…`.
|
|
|
|
The Finger simulation page separately exports an operator CSV list with centred
|
|
UV pixels, sensor/platform millimetres and mapping identity. It uses the
|
|
[manual calibration coordinate convention](calibration-data-format.zh.md);
|
|
that list is not an `.oryseq` and is not imported into the acquisition page.
|
|
|
|
Why JSON and not a binary container: a sequence is at most a few hundred points
|
|
of text. It is read by a human, diffed in review, and emailed to whoever runs the
|
|
bench. Binary would buy nothing and cost all three. (The *acquired data* of a run
|
|
is a different problem — per-point time series are large and will need a sidecar;
|
|
that decision belongs to workflow P8, not here.)
|
|
|
|
## Conventions
|
|
|
|
The format follows this repository's existing documents rather than inventing a
|
|
convention. Where it matches `session.json` and `force_calibration.json`, that is
|
|
deliberate:
|
|
|
|
| Convention | Value |
|
|
|---|---|
|
|
| Version field | `"format_version": "1.0"` — a dotted **string**, compared as a tuple |
|
|
| Identity | `"kind": "autocal.sequence"` — checked on import |
|
|
| Extension | `.oryseq` by convention; import accepts any `.json` |
|
|
| Encoding | UTF-8, `indent=2`, `ensure_ascii=False`, trailing newline |
|
|
| Write | temp file then `Path.replace` — a half-written sequence that still parses looks like a design |
|
|
|
|
## The document
|
|
|
|
```json
|
|
{
|
|
"format_version": "1.0",
|
|
"kind": "autocal.sequence",
|
|
"created_at": "2026-09-20T10:31:00+00:00",
|
|
"name": "5 点灵敏度扫描",
|
|
"note": "",
|
|
"versions": { "app": "0.1.0", "shared": "0.1.0", "shared_api": "0.1" },
|
|
"device": {
|
|
"sensor_type": "box",
|
|
"config_ref": "…\\config\\sensors\\box\\sensor.json",
|
|
"geometry_id": "finger-1.0.0",
|
|
"probe_tip_set": true
|
|
},
|
|
"frame": { "targets": "platform", "length_unit": "mm", "units_per_mm": 10.0 },
|
|
"profile": {
|
|
"mode": "flat", "pitch_mm": 2.0, "margin_mm": 1.0, "radius_mm": 0.0,
|
|
"clear_height_mm": 5.0, "approach_speed_mm_s": 2.0, "press_speed_mm_s": 0.5,
|
|
"press_depth_mm": 0.2, "dwell_s": 0.5, "settle_window_s": 0.5,
|
|
"repeats": 3, "order": "serpentine"
|
|
},
|
|
"points": [
|
|
{
|
|
"id": 1,
|
|
"target_mm": { "x": 0.0, "y": 0.0, "z": 12.4 },
|
|
"sensor_mm": { "x": 0.0, "y": 0.0, "z": 0.0 },
|
|
"clearance_mm": 5.0,
|
|
"enabled": true,
|
|
"note": ""
|
|
}
|
|
],
|
|
"summary": {
|
|
"count": 12, "enabled": 12,
|
|
"min_mm": { "x": 0.0, "y": 0.0, "z": 12.4 },
|
|
"max_mm": { "x": 10.0, "y": 4.0, "z": 12.4 },
|
|
"span_mm": { "x": 10.0, "y": 4.0, "z": 0.0 },
|
|
"pitch_mm": 2.0,
|
|
"estimated_duration_s": 380.0
|
|
}
|
|
}
|
|
```
|
|
|
|
## Fields
|
|
|
|
### `frame` — required
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `targets` | the frame `target_mm` is expressed in; today always `"platform"` |
|
|
| `length_unit` | `"mm"`; anything else is refused |
|
|
| `units_per_mm` | the pulse equivalent the points were built with |
|
|
|
|
A sequence whose numbers do not say whether they are millimetres or user units is
|
|
a sequence that will eventually be read wrong. `units_per_mm` is recorded so a
|
|
file from a differently-configured axis is detectable rather than silently
|
|
believed.
|
|
|
|
### `profile` — required
|
|
|
|
The twelve fields the Sequence tab edits, and nothing else. A field absent from
|
|
the file is **kept at its current value and reported**, never silently defaulted
|
|
to another bench's number.
|
|
|
|
### `points` — required, may be empty
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `id` | 1-based, stable within the file; used for run progress and resume |
|
|
| `target_mm` | where the axes go — required, with all three of `x`, `y`, `z` |
|
|
| `sensor_mm` | where that point lands on the sensor; optional but written |
|
|
| `uv_px` | `{u, v}`: the point's place on the flat map, in pixels from the map origin; optional (added without a version change, older files simply lack it). The capture page takes its points by U/V, so a point with `uv_px` can be sent there directly |
|
|
| `clearance_mm` | the standoff for this point |
|
|
| `enabled` | false = designed but deliberately skipped |
|
|
| `note` | free text |
|
|
|
|
Both positions are stored on purpose. The platform target is what the run uses;
|
|
the sensor position is what makes the point checkable against the surface.
|
|
Deriving one from the other on every load would silently change what the points
|
|
mean the moment a sensor is re-fixtured — and a re-fixture is exactly when a
|
|
stored transform should be *noticed*, not recomputed from.
|
|
|
|
### `summary` — derived, not authoritative
|
|
|
|
Present so a human reading the file sees the point count and travel span without
|
|
counting rows, and so a diff is legible. It is **recomputed on load**; a mismatch
|
|
is reported as a warning and the recomputed values win. That is how a hand-edited
|
|
document announces itself instead of quietly disagreeing with its own contents.
|
|
|
|
`estimated_duration_s` assumes a constant traverse rate
|
|
(`sequence_io.ASSUMED_TRAVERSE_MM_S`) and ignores acceleration. It is a planning
|
|
figure and is labelled an estimate wherever it is shown.
|
|
|
|
### `device` — a reference, not a requirement
|
|
|
|
Records which sensor the sequence was designed for. Importing a sequence for
|
|
another sensor is **allowed and warned about**, because re-fixturing
|
|
deliberately is a normal thing to do.
|
|
|
|
## Import rules
|
|
|
|
Every rejection names the specific problem — `invalid file` tells an operator
|
|
nothing.
|
|
|
|
| Condition | Behaviour |
|
|
|---|---|
|
|
| not JSON | refused, with line and column |
|
|
| top level not an object | refused |
|
|
| `kind` missing or different | refused, naming what was found |
|
|
| `format_version` missing or not a string | refused |
|
|
| `format_version` newer than this build | refused, naming both versions |
|
|
| `frame` missing, or `length_unit` not `mm` | refused |
|
|
| `profile` not an object | refused |
|
|
| a profile key absent | imported; the current value is kept and the key is reported |
|
|
| `points` absent | imported as profile-only, and reported |
|
|
| a point without `target_mm.x/y/z` | refused, naming the point and the axis |
|
|
| `summary` disagreeing with `points` | imported; recomputed, and reported |
|
|
| current design is unsaved | the operator is asked before it is replaced |
|
|
|
|
Version strings are compared as tuples, never as strings: `"1.10"` is newer than
|
|
`"1.9"`, and a string comparison would make a newer file look older and load it
|
|
as if it were supported.
|
|
|
|
## Compatibility rules
|
|
|
|
- **Adding an optional key** does not change `format_version`.
|
|
- **Adding a required key, or changing a field's meaning or unit**, does.
|
|
- A reader must refuse a document whose version it does not support rather than
|
|
interpreting the parts it recognises — a sequence half-applied is worse than
|
|
one rejected.
|
|
- `1.x` readers are expected to accept `1.y` for `y <= x` on the same major
|
|
version and report anything they did not use.
|
|
|
|
## Where the pieces live
|
|
|
|
| Concern | Module |
|
|
|---|---|
|
|
| The document, validation, the derived summary | `apps/autocal/src/orisys_autocal/sequence_io.py` |
|
|
| The design screen and its controls | `apps/autocal/src/orisys_autocal/ui/sequence_panel.py` |
|
|
| The file dialogs and the unsaved-work guard | `apps/autocal/src/orisys_autocal/ui/window.py` |
|
|
| Why a sequence is an artifact (P8) | `apps/autocal/docs/workflow.md` |
|