统一测点坐标、保持窗口和试验目录,补齐逐点初步分析。定向测试与真实视频闭环通过;两项轮询失败在修改前版本同样复现。
7.0 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 |
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 |