6.7 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 导入序列….
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 |