- 标签顺序:几何与安装 放到 测试序列 之前 - 序列点增加可选 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
7.2 KiB
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;
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
{
"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.xreaders are expected to accept1.yfory <= xon 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 |