230b04a79d
Product name decided the pipeline: Mini worked live but replay refused it. A bundle descriptor now declares profile_id, product_family and processing_backend, with computational and visualization assets kept apart, and one resolver serves live and recorded input. Legacy bundles load through a documented adapter that never guesses a backend from a folder name.
162 lines
8.0 KiB
Markdown
162 lines
8.0 KiB
Markdown
# Sensor bundles and the processing contract
|
|
|
|
A sensor bundle is a folder that describes one processing profile. This document owns the bundle
|
|
descriptor, the resolver, the backend registry and the fingerprints. `sensor.json` keeps its
|
|
existing meaning: it is the processing configuration, and its `type` field is the image-processing
|
|
arrangement (`compound_eye`, `single_camera`, `uv_unroll`) - never a product family.
|
|
|
|
## Why the descriptor exists
|
|
|
|
"Which pipeline processes these frames" used to be inferred from a product name. Those are four
|
|
different things, and collapsing them is what made Mini work live but be refused in replay:
|
|
|
|
| Concept | Meaning | Example |
|
|
|---|---|---|
|
|
| Product family | What the operator calls it | Box, Mini, Finger |
|
|
| Processing backend | Which SDK pipeline processes the images | `compound_eye`, `uv_unroll` |
|
|
| Configuration profile | Camera, geometry and algorithm settings for one design revision | `orisys.mini.prototype-r1` |
|
|
| Device instance | The physical unit | Serial number, its calibration |
|
|
|
|
Several families can share one backend, one family can have several profiles, and a recorded clip
|
|
has no device instance at all - only a profile and, optionally, a compatible calibration.
|
|
|
|
## Descriptor
|
|
|
|
`manifest.json` in the bundle root. Every field except the five identifying keys is optional, so a
|
|
bundle with no manifest still resolves through the compatibility adapter.
|
|
|
|
```json
|
|
{
|
|
"schema_version": 1,
|
|
"profile_id": "orisys.mini.prototype-r1",
|
|
"display_name": "Mini prototype R1",
|
|
"product_family": "mini",
|
|
"processing_backend": "compound_eye",
|
|
"sensor_json": "sensor.json",
|
|
"default_calibration": "force_calibration.json",
|
|
"computational_assets": { "unroll_map": "assets/30fps-0703/unroll_map_uv.npz" },
|
|
"assets": { "scene": "scene/finger-display-1.0.0.oryscene" }
|
|
}
|
|
```
|
|
|
|
- **`computational_assets`** are required to process at all. A missing one fails the profile.
|
|
- **`assets`** are visualization only. A missing one is reported and never blocks processing - a
|
|
missing 3D scene is not a missing deformation field.
|
|
- Paths resolve relative to the bundle root, so moving or renaming the folder changes nothing.
|
|
Absolute paths and `..` escapes are rejected at parse time.
|
|
- An unsupported `processing_backend` or `schema_version` raises `DescriptorError`. The supported
|
|
descriptor schema version is 1.
|
|
|
|
## Compatibility for existing bundles
|
|
|
|
`resolve_bundle` reads a new-shape descriptor when `schema_version` is present. Otherwise - no
|
|
manifest, or the older `name`/`sensor_json`/`assets_dir`/`viz_pose` shape - the adapter describes
|
|
it. That is the only place a folder name is consulted, and only for the built-in set:
|
|
|
|
| Folder | Product family | Backend (when the config does not decide) |
|
|
|---|---|---|
|
|
| `box` | box | `compound_eye` |
|
|
| `mini` | mini | `compound_eye` |
|
|
| `finger` | finger | `uv_unroll` |
|
|
| `finger-mems` | finger-mems | `compound_eye` |
|
|
|
|
For a bundle without a descriptor the backend follows the configuration, using the rule
|
|
`Sensor._load_config` already applies: `type` when present, else `compound_eye` when the config has
|
|
a `stitcher` block, else `single_camera`. Only `finger` declares `type` today; `box`, `mini` and
|
|
`finger-mems` are decided by the stitcher. A folder that is neither built-in nor carries a
|
|
descriptor is rejected - never routed to `compound_eye` because box works that way.
|
|
|
|
## Resolver
|
|
|
|
```python
|
|
from orisys import resolve_bundle, validate_profile, create_processor, CalibrationSelection
|
|
|
|
profile = resolve_bundle("sensors/mini") # or any bundle folder
|
|
profile = resolve_bundle("sensors/mini", calibration_selection=CalibrationSelection.NONE)
|
|
session = validate_profile(profile) # re-check against the filesystem now
|
|
rebuilt = create_processor(profile, source=0) # live camera
|
|
rebuilt = create_processor(profile, source="clip.mp4") # recorded frames, same semantics
|
|
```
|
|
|
|
`resolve_bundle(bundle_path, *, calibration_selection=None) -> ResolvedSensorProfile`. The profile
|
|
is plain data - no Qt, no application preferences, no device handle - so the same object drives a
|
|
camera and a recorded clip. `_set_calibration` on a device affects only its own calibration
|
|
selection.
|
|
|
|
`validate_profile(profile) -> ValidationReport` re-reads the filesystem. It reports
|
|
`fingerprint.stale` when a computational asset changed since the profile was resolved.
|
|
|
|
`create_processor(profile, *, sensor=None, source=None, include=..., contact=...)` returns a
|
|
`ProcessingSession` with `start`/`wait`/`latest`/`reset`/`pause`/`resume`/`stop`. It raises
|
|
`ProcessorCreationError` naming what is missing rather than returning a session that does nothing.
|
|
|
|
### Backends
|
|
|
|
| Identifier | Requires | Produces |
|
|
|---|---|---|
|
|
| `compound_eye` | `camera`, `stitcher`, `tracker` config | stitched frame, flow, force |
|
|
| `single_camera` | `camera` config | frames only - no deformation field, so no force |
|
|
| `uv_unroll` | `camera` config, the mapped computational asset | unrolled frame, flow, force |
|
|
|
|
`known_backends()` and `spec_for(...)` are the registry. `uv_unroll` is not yet materialised from a
|
|
profile alone, because it needs geometry, a UV domain, a tactile processor and a capture object
|
|
assembled together - that is `orisys.finger.FingerRuntime.open`'s shape. `create_processor` says so
|
|
instead of pretending.
|
|
|
|
## Capabilities
|
|
|
|
Each capability is decided by the facts it depends on, not by the presence of a file:
|
|
|
|
| Capability | Unavailable when |
|
|
|---|---|
|
|
| Video processing | the configuration cannot be loaded |
|
|
| Optical flow | no `tracker` block |
|
|
| Raw force | the backend produces no deformation field |
|
|
| Calibrated force | per axis: the calibration has no curve for it |
|
|
| Deformation | the backend computes no deformation field |
|
|
| 3D visualization | no scene asset is declared, or the declared one is missing |
|
|
|
|
Deformation is relative unless a calibration says otherwise; it is reported with its meaning rather
|
|
than as a bare "available". `ValidationReport.state` is `ok`, `degraded` (usable, something
|
|
optional is missing) or `failed`.
|
|
|
|
## Calibration
|
|
|
|
`CalibrationSelection` is `BUNDLE_DEFAULT` (the default), `NONE`, or an explicit path.
|
|
|
|
- `NONE` is a choice, not a failure: raw force stays available and the calibration fingerprint
|
|
records `none`.
|
|
- An explicitly selected calibration that is missing or unparseable is an **error**. It is never
|
|
replaced by another file.
|
|
- Applicability is `declared` when the document carries `meta.sensor_type`, `unknown` when legacy
|
|
metadata is absent, or `none`. `unknown` means it applies but its fit is unverified - which is
|
|
what the shipped `box` calibration reports.
|
|
|
|
| Read | Meaning |
|
|
|---|---|
|
|
| `FrameSnapshot.normal_force_raw` / `shear_x_raw` / `shear_y_raw` | device counts, pre-calibration |
|
|
| `FrameSnapshot.normal_force` / `shear_x` / `shear_y` | newtons when a curve applies, counts otherwise |
|
|
| `FrameSnapshot.depth`, `centroid`, `contour`, `vnormal` | relative quantities, not newtons |
|
|
|
|
`Sensor.force_triplets()` and `_force_raw` remain the raw and unfiltered view.
|
|
|
|
## Fingerprints
|
|
|
|
`processing_fingerprint` hashes the effective backend, the paired data, and each computational asset's role name with
|
|
its content hash. It excludes the bundle location, `display_name`, and every visualization asset: a
|
|
relocated or cosmetically renamed bundle keeps its fingerprint, and a missing scene never changes it.
|
|
It includes the config minus `name` and `version`.
|
|
|
|
`calibration_fingerprint` hashes the selected document's content, or the constant `none`.
|
|
|
|
`profile.inventory()` returns the reproducible snapshot: bundle-relative paths, asset hashes and
|
|
both fingerprints. Compare an inventory, not a path, to decide whether two installations would
|
|
compute the same numbers.
|
|
|
|
## Resetting the reference
|
|
|
|
Both backends process externally supplied frames through the same profile. After a gap in the
|
|
input - a pause, a seek, a scene change - call `session.reset()`. It is queued to the stage that
|
|
owns the tracker, so the reference frame is rebased on the frame about to be computed rather than
|
|
on a buffer the pipeline is still reading.
|