examples: make StreamPipeline the recommended entry, archive the manual loop
The two canonical examples taught the advanced path: they sequenced get_img() -> compute_deformation() -> compute_contact() -> read_info() by hand and unpacked a 13-element tuple positionally, which is what led developers to reach for the compute backend, the capture queue and the stitch mode. The managed alternative already existed and was documented nowhere. Library (add-only, nothing removed): - FrameSnapshot.image_size_raw carries the capture resolution, so a caller can compare it with the processed frame instead of reaching into Sensor. - StreamPipeline.reset() resets the tracker and force filters *safely*. It is queued and applied by the flow stage before its next frame, never inline: Sensor.reset rebases initial_img on the current Sensor.img, which the flow stage is reading concurrently, so a caller-thread reset would capture a half-written reference frame. Idempotent. - TactileProcessor.pipeline(produce) and FingerRuntime.pipeline() build a pipeline without the caller naming a backend concept; the latter is `with runtime.pipeline(contact=True) as pipe`. Examples: - examples/basic_pipeline.py and examples/finger_pipeline.py rewritten onto StreamPipeline + FrameSnapshot: one object, one call per frame, no tuple, and no compute backend, queue or stitch setting in sight. - examples/archive/basic_pipeline_manual.py and _finger_pipeline_manual.py keep the previous versions byte-for-byte apart from a supersede header, so code copied from them keeps working. The finger header also records the archived --show defect (store_true with default=True, so it could never be disabled). - examples/_display.py holds the shared display helpers, so an example no longer hand-rolls depth normalisation and colormap application. - examples/README.md leads with the recommended pair and documents the manual form as still-supported, and states that examples/gui/ is moving to orisys-studio. Tests: three added to tests/test_pipeline.py, covering the reset queue (it must not run on the caller's thread and must land before the first compute_flow), image_size_raw, and the None case when a producer reports no size. Verified the reset test fails when reset() is made inline, so it discriminates. Verified end-to-end against real media: basic_pipeline on data/sample/s1_480p.mp4 (box) reaches ~113 FPS and exits 0; finger_pipeline on data/dd02-0703/1.mp4 (finger) reaches ~140 FPS with mask coverage 0.41 and exits 0. Both archived manual versions still run. tests/test_pipeline.py: 20 passed. Full suite: 342 passed, 3 failed - all three failures reproduce on the pristine tree (import precedence, longrun timing, and a stale ruff version literal in docs/code-review.md) and are unrelated to this change.
This commit is contained in:
+60
-48
@@ -1,75 +1,87 @@
|
||||
# Examples
|
||||
|
||||
Runnable demos live at the `examples/` root. Shared helpers stay in
|
||||
`pipeline/`, `viz/`, and `gui/`. Run commands from the repository root.
|
||||
Runnable demos live at the `examples/` root. Shared helpers stay in `pipeline/`, `viz/`, and
|
||||
`gui/`. Run commands from the repository root.
|
||||
|
||||
## Product Entry Points
|
||||
## Start here
|
||||
|
||||
Both recommended examples create one object, then read a frame at a time. Everything else -
|
||||
camera reading, stitching, preprocessing, the optical-flow and force stages - happens inside
|
||||
`StreamPipeline`, and each frame arrives as one `FrameSnapshot`. There is no stage sequence to
|
||||
remember and no tuple to unpack, and no backend setting to tune.
|
||||
|
||||
| Product | Entry | Default config |
|
||||
|---------|-------|----------------|
|
||||
| ORY-BOX | `basic_pipeline.py` | `box` |
|
||||
| ORY-MINI | `mini_pipeline.py` | `mini` |
|
||||
| ORY-FINGER (2D) | `finger_pipeline.py` | `finger` |
|
||||
| ORY-FINGER (3D Qt) | `finger_pipeline_3dview.py` | `finger` |
|
||||
| ORY-MINI | `basic_pipeline.py -c mini` | `mini` |
|
||||
| ORY-FINGER | `finger_pipeline.py` | `finger` |
|
||||
|
||||
`finger` is the public ORY-FINGER config stem. `dd02` remains a compatibility alias for `dd02-ov`.
|
||||
```python
|
||||
# ORY-BOX / ORY-MINI
|
||||
sensor = orisys.Sensor(0, config_name="box")
|
||||
with orisys.StreamPipeline(sensor, sensor.get_img, contact=True) as pipe:
|
||||
while (snap := pipe.wait()) is not None:
|
||||
print(snap.normal_force, snap.shear_x, snap.shear_y)
|
||||
|
||||
## Typical Usage
|
||||
|
||||
ORY-BOX:
|
||||
|
||||
```bash
|
||||
python examples/basic_pipeline.py -v 0 -c box
|
||||
# ORY-FINGER - the runtime owns camera, Blender export, unroll map and valid mask
|
||||
runtime = FingerRuntime.open(config="finger", video=0)
|
||||
with runtime.pipeline(contact=True) as pipe:
|
||||
while (snap := pipe.wait()) is not None:
|
||||
print(snap.normal_force, snap.shear_x, snap.shear_y)
|
||||
```
|
||||
|
||||
ORY-MINI:
|
||||
|
||||
```bash
|
||||
python examples/mini_pipeline.py -v 0 -c mini
|
||||
```
|
||||
|
||||
ORY-FINGER 2D:
|
||||
`contact=True` runs contact analysis; `include=(...)` chooses which arrays each snapshot carries
|
||||
(`image`, `flow`, `vnormal`, `vshear`, `depth`, `centroid`, `contour`). Arrays you do not include
|
||||
are `None`, so you pay only for what you use. `pipe.reset()` resets the tracker safely from your
|
||||
loop; `snap.image_size_raw` reports the capture resolution before UV unroll or resize.
|
||||
|
||||
### Typical usage
|
||||
|
||||
```bash
|
||||
python examples/basic_pipeline.py -v 0 -c box # first camera, ORY-BOX
|
||||
python examples/basic_pipeline.py -v 0 -c mini # ORY-MINI
|
||||
python examples/basic_pipeline.py -v recording.mp4 -c box
|
||||
python examples/basic_pipeline.py --no-show # headless: print only
|
||||
python examples/finger_pipeline.py --camera_id 0 --config finger
|
||||
python examples/finger_pipeline.py --video data/dd02-0703/1.mp4
|
||||
```
|
||||
|
||||
ORY-FINGER video file:
|
||||
Keys in both: `q` quits, `r` resets the tracker.
|
||||
|
||||
```bash
|
||||
python examples/finger_pipeline.py --video ./data/sample.mp4 --config finger
|
||||
## The manual form, for when you need the stages
|
||||
|
||||
`examples/archive/` holds the previous versions of these two examples. They call the stages by
|
||||
hand:
|
||||
|
||||
```python
|
||||
sensor.get_img()
|
||||
sensor.compute_deformation()
|
||||
sensor.compute_contact()
|
||||
fps, flow, vnormal, img, ... = sensor.read_info( ... ) # positional, 13 elements
|
||||
```
|
||||
|
||||
ORY-FINGER 3D Qt dashboard:
|
||||
That path is still supported and still correct - it is the right choice if you are implementing
|
||||
your own scheduling. It is no longer the recommended starting point, and the archived files carry
|
||||
a header explaining the difference. They are kept so that code copied from them keeps working.
|
||||
|
||||
```bash
|
||||
pip install -e ".[gui]"
|
||||
python examples/finger_pipeline_3dview.py --video 0 --config finger
|
||||
```
|
||||
## Other root examples
|
||||
|
||||
## Other Root Examples
|
||||
|
||||
- `plot_data.py` — single-sensor OpenCV views plus PyQtGraph force charts
|
||||
- `two_sensor_pipeline.py` — two-sensor multiprocessing demo
|
||||
- `multi_stream.py` - several sensors at once, using `StreamPipeline` with `StreamRecorder`
|
||||
- `two_sensor_pipeline.py` - two-sensor multiprocessing demo, manual staging
|
||||
- `plot_data.py` - single-sensor OpenCV views plus PyQtGraph force charts
|
||||
- `finger_pipeline_3dview.py` - the Qt/open3d dashboard; see the note below
|
||||
|
||||
## Requirements
|
||||
|
||||
- SDK installed from the repository root, for example `pip install -e .`
|
||||
- camera index or test video path
|
||||
- matching config names under `./config/` (or packaged `orisys/configs/`)
|
||||
- SDK installed from the repository root: `pip install -e .`
|
||||
- a camera index or a test video path
|
||||
- matching config names under `./config/` or the packaged `orisys/configs/`
|
||||
|
||||
Qt / PyQtGraph examples additionally require:
|
||||
Qt / PyQtGraph examples additionally need `pip install -e ".[gui]"`.
|
||||
|
||||
```bash
|
||||
pip install -e ".[gui]"
|
||||
```
|
||||
## The GUI is moving out
|
||||
|
||||
## Notes
|
||||
|
||||
- `finger_pipeline_3dview.py` is the main interactive finger demo.
|
||||
It uses `gui/app_paths.py` so the same entry works in editable and packaged runs.
|
||||
- Dashboard controls: click **启动** to start acquisition; use **Record CSV / Stop CSV**
|
||||
and the CSV save-directory browser to log contact/force data; `Q` quits, `R` resets,
|
||||
`S` starts video recording, `E` stops video recording, and `Esc` toggles fullscreen.
|
||||
- `finger_pipeline.py` is the leanest finger example if you want to
|
||||
validate UV unroll + force/contact processing without the full dashboard.
|
||||
`examples/gui/` - the 2D viewer, the 3D dashboard and their widgets - is a full Qt + open3d
|
||||
application, not an example. It is being moved to the **orisys-studio** application, which is
|
||||
where the GUI belongs. Until that lands, `finger_pipeline_3dview.py` and `examples/gui/` still
|
||||
work as before; new work should not build on them.
|
||||
|
||||
Reference in New Issue
Block a user