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.
8.0 KiB
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.
{
"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_assetsare required to process at all. A missing one fails the profile.assetsare 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_backendorschema_versionraisesDescriptorError. 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
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.
NONEis a choice, not a failure: raw force stays available and the calibration fingerprint recordsnone.- An explicitly selected calibration that is missing or unparseable is an error. It is never replaced by another file.
- Applicability is
declaredwhen the document carriesmeta.sensor_type,unknownwhen legacy metadata is absent, ornone.unknownmeans it applies but its fit is unverified - which is what the shippedboxcalibration 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.