tso-sensorium 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tso_sensorium-0.1.0/LICENSE +22 -0
- tso_sensorium-0.1.0/PKG-INFO +509 -0
- tso_sensorium-0.1.0/README.md +471 -0
- tso_sensorium-0.1.0/pyproject.toml +168 -0
- tso_sensorium-0.1.0/setup.cfg +4 -0
- tso_sensorium-0.1.0/tests/test_resources.py +19 -0
- tso_sensorium-0.1.0/tso_sensorium/__init__.py +8 -0
- tso_sensorium-0.1.0/tso_sensorium/assets/instructions/bowel_retraction_phantom.yaml +76 -0
- tso_sensorium-0.1.0/tso_sensorium/assets/instructions/bowel_retraction_porcine.yaml +76 -0
- tso_sensorium-0.1.0/tso_sensorium/assets/instructions/endoscope_guidance.yaml +27 -0
- tso_sensorium-0.1.0/tso_sensorium/assets/recording_dashboard.html +1556 -0
- tso_sensorium-0.1.0/tso_sensorium/assets/storz_endoscope_calibration.yml +68 -0
- tso_sensorium-0.1.0/tso_sensorium/configuration.py +150 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/__init__.py +31 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/annotations.py +158 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/builder.py +244 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/dataset_builder.py +416 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/dataset_transforms.py +191 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/generation.py +184 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/generation_config.py +344 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/legend.py +157 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/phase_labelers.py +275 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/schema.py +240 -0
- tso_sensorium-0.1.0/tso_sensorium/episodes/table_transforms.py +362 -0
- tso_sensorium-0.1.0/tso_sensorium/export/__init__.py +11 -0
- tso_sensorium-0.1.0/tso_sensorium/export/base.py +47 -0
- tso_sensorium-0.1.0/tso_sensorium/export/csv_writer.py +59 -0
- tso_sensorium-0.1.0/tso_sensorium/export/lerobot_action_update_writer.py +966 -0
- tso_sensorium-0.1.0/tso_sensorium/export/lerobot_writer.py +218 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/__init__.py +30 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/alignment.py +216 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/camera_transform.py +146 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/frame_transforms.py +131 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/image.py +45 -0
- tso_sensorium-0.1.0/tso_sensorium/processing/rectification.py +99 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/__init__.py +13 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/config.py +145 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/core.py +255 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/dashboard.py +370 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/denoising_preview.py +324 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/episode_files.py +77 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/episode_names.py +29 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/library_service.py +754 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/liveness.py +60 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/message_fields.py +55 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/network.py +43 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/playback.py +66 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/__init__.py +33 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/camera_transform_publisher.py +141 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/liveness.py +116 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/random_publisher.py +259 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/record.py +210 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/ros_config.py +37 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/session.py +127 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/video_subscriber.py +165 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros1/web_service.py +54 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros2/__init__.py +9 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros2/liveness.py +118 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros2/record.py +226 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros2/session.py +136 -0
- tso_sensorium-0.1.0/tso_sensorium/recording/ros2/web_service.py +61 -0
- tso_sensorium-0.1.0/tso_sensorium/resources.py +34 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/__init__.py +8 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/annotate.py +55 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/generate_dataset.py +36 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/label_phases.py +33 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/mock_scene.py +60 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/mock_sensors.py +72 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/mock_sensors_ros2.py +78 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/publish_robot_camera_transform.py +120 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/record.py +67 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/record_ui.py +60 -0
- tso_sensorium-0.1.0/tso_sensorium/scripts/record_ui_ros2.py +69 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/PKG-INFO +509 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/SOURCES.txt +77 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/dependency_links.txt +1 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/entry_points.txt +10 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/requires.txt +17 -0
- tso_sensorium-0.1.0/tso_sensorium.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lorenzo Mazza, Ariel Rodriguez, and the National Center for
|
|
4
|
+
Tumor Diseases (NCT) Dresden
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tso-sensorium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sensor data recording, post-processing, and episodic dataset creation for robot learning
|
|
5
|
+
Author-email: Lorenzo Mazza <lorenzo.mazza@nct-dresden.de>, Ariel Rodriguez <ariel.rodriguezjimenez@nct-dresden.de>
|
|
6
|
+
Maintainer-email: Lorenzo Mazza <lorenzo.mazza@nct-dresden.de>, Ariel Rodriguez <ariel.rodriguezjimenez@nct-dresden.de>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Repository, https://github.com/nct-tso-robotics/tso_sensorium
|
|
9
|
+
Keywords: robotics,imitation-learning,dataset,recording,ros
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.19.0
|
|
24
|
+
Requires-Dist: opencv-python>=4.5.0
|
|
25
|
+
Requires-Dist: pandas>=1.3.0
|
|
26
|
+
Requires-Dist: scipy>=1.7.0
|
|
27
|
+
Requires-Dist: joblib>=1.3.0
|
|
28
|
+
Requires-Dist: tqdm>=4.62.0
|
|
29
|
+
Requires-Dist: pydantic>=2.7
|
|
30
|
+
Requires-Dist: pyyaml-include<2.0,>=1.3
|
|
31
|
+
Provides-Extra: lerobot
|
|
32
|
+
Requires-Dist: lerobot>=0.4.4; extra == "lerobot"
|
|
33
|
+
Provides-Extra: gui
|
|
34
|
+
Requires-Dist: flask>=3.0; extra == "gui"
|
|
35
|
+
Provides-Extra: test
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# TSO Sensorium
|
|
40
|
+
|
|
41
|
+
A library for sensor data recording, post-processing, and episodic dataset creation for robot learning.
|
|
42
|
+
|
|
43
|
+
## Package layout
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
tso_sensorium/
|
|
47
|
+
├── recording/ # sensor capture: core + ros1/ (Noetic) and ros2/ (Jazzy) adapters
|
|
48
|
+
├── processing/ # stereo rectification, deinterlacing, timestamp alignment
|
|
49
|
+
├── episodes/ # schema, episode assembly, parallel dataset building
|
|
50
|
+
├── export/ # dataset writers: CSV folders, LeRobot (v3.0 format)
|
|
51
|
+
└── scripts/ # command-line entry points
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Processing, episode assembly, and export have no ROS dependency and run anywhere. Recording adapters import the ROS client libraries and are only usable inside the corresponding ROS environment.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### With pixi (recommended)
|
|
59
|
+
|
|
60
|
+
The preferred workflow is to enter the environment once and run commands
|
|
61
|
+
normally inside it. Pixi creates the environment on first use and installs this
|
|
62
|
+
package in editable mode automatically. Install [Pixi](https://pixi.sh/) first
|
|
63
|
+
if it is not already available.
|
|
64
|
+
|
|
65
|
+
For ROS 1 development, start a clean terminal and enter the ROS 1 environment:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pixi shell -e ros1
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Confirm that Python is using the RoboStack ROS installation from this
|
|
72
|
+
repository's Pixi environment:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python -c "import rospy; print(rospy.__file__)"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The path must be under `.pixi/envs/ros1/`, not `/opt/ros/noetic/`. Once inside
|
|
79
|
+
the shell, run ROS commands, application commands, and tests directly.
|
|
80
|
+
|
|
81
|
+
The `tso_testbed` configurations also require the custom messages generated by
|
|
82
|
+
the testbed catkin workspace. Add only that workspace's generated Python
|
|
83
|
+
packages to the Pixi environment:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export TESTBED_WORKSPACE=/path/to/robot_testbed
|
|
87
|
+
export PYTHONPATH="$TESTBED_WORKSPACE/devel/lib/python3/dist-packages"
|
|
88
|
+
python -c "import rospy, testbed_msgs; print(rospy.__file__); print(testbed_msgs.__file__)"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`rospy` must still resolve under `.pixi/envs/ros1/`, while `testbed_msgs` must
|
|
92
|
+
resolve under the testbed workspace. Do not source the workspace's `setup.bash`
|
|
93
|
+
inside the Pixi shell: it also adds `/opt/ros/noetic` and replaces RoboStack's
|
|
94
|
+
ROS Python packages with the system installation.
|
|
95
|
+
|
|
96
|
+
The following commands can then use the real testbed configuration:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python -m tso_sensorium.scripts.record \
|
|
100
|
+
--config_path configs/recording/tso_testbed.yaml \
|
|
101
|
+
--output_folder "$HOME/tso_sensorium_recordings"
|
|
102
|
+
pytest tests/recording/test_ros1_adapter.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use a separate terminal for each foreground process and run `pixi shell -e
|
|
106
|
+
ros1` in each one. Leave an environment with `exit` or Ctrl-D. The other
|
|
107
|
+
environments work the same way:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pixi shell # core development and non-ROS tests
|
|
111
|
+
pixi shell -e ros2 # ROS 2 recording and tests
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Do not automatically source `/opt/ros/noetic/setup.bash` or a catkin workspace
|
|
115
|
+
from `.bashrc`. Those scripts prepend the complete system ROS installation to
|
|
116
|
+
`PYTHONPATH`, which can make Pixi's Python import the wrong ROS packages. If the
|
|
117
|
+
current terminal has already sourced system ROS, open a clean terminal before
|
|
118
|
+
entering the Pixi shell, then add only the generated message path as shown
|
|
119
|
+
above.
|
|
120
|
+
|
|
121
|
+
For scripts and CI, where an interactive shell is inconvenient, use the
|
|
122
|
+
equivalent one-shot form:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pixi run test
|
|
126
|
+
pixi run -e ros1 pytest tests/recording/test_ros1_adapter.py
|
|
127
|
+
pixi run -e ros2 pytest tests/recording/test_ros2_adapter.py
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The environments are declared in `pyproject.toml` and locked in `pixi.lock`.
|
|
131
|
+
On machines with a small home quota, set `PIXI_CACHE_DIR` to storage with more
|
|
132
|
+
space before creating an environment. LeRobot export remains a pip extra to
|
|
133
|
+
avoid duplicating a multi-gigabyte torch installation per checkout.
|
|
134
|
+
|
|
135
|
+
### Alternative installations
|
|
136
|
+
|
|
137
|
+
#### Pip
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install -e . # core: processing, episodes, export
|
|
141
|
+
pip install -e ".[gui]" # + browser dashboard (Flask)
|
|
142
|
+
pip install -e ".[lerobot]" # + LeRobot dataset export (Python >= 3.10)
|
|
143
|
+
pip install -e ".[test]" # + pytest
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Python requirements: 3.9+ (the pydantic-backed config layer); LeRobot
|
|
147
|
+
export additionally needs 3.10+. Extras combine as usual, e.g.
|
|
148
|
+
`pip install -e ".[gui,test]"`. After install the scripts are available
|
|
149
|
+
as console commands (`tso-record`, `tso-record-ui`, `tso-annotate`,
|
|
150
|
+
`tso-generate-dataset`, `tso-label-phases`, and the `*-ros2` variants),
|
|
151
|
+
equivalent to the `python -m tso_sensorium.scripts.*` forms used below.
|
|
152
|
+
|
|
153
|
+
#### Mamba for ROS recording
|
|
154
|
+
|
|
155
|
+
For recording without Pixi, ROS client libraries must come from a system ROS
|
|
156
|
+
installation or a dedicated [RoboStack](https://robostack.github.io/)
|
|
157
|
+
environment because `rospy` and `rclpy` are not available from PyPI. The
|
|
158
|
+
environment files under `environments/` provide the latter:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
mamba env create -f environments/ros1-noetic.yml # or ros2-jazzy.yml
|
|
162
|
+
mamba activate tso-sensorium-ros1
|
|
163
|
+
pip install -e . --no-deps # deps already come from conda
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Note: the Noetic robot PC runs Python 3.8 and the LeRobot exporter needs
|
|
167
|
+
Python 3.10+, so recording and LeRobot export are not meant to share one
|
|
168
|
+
environment. Record on the robot, export on the processing machine.
|
|
169
|
+
|
|
170
|
+
## User guide
|
|
171
|
+
|
|
172
|
+
### Configuration
|
|
173
|
+
|
|
174
|
+
Every script is driven by a [pydantic](https://docs.pydantic.dev)-validated
|
|
175
|
+
YAML file under `configs/`, selected with `--config_path`. The same rules
|
|
176
|
+
apply everywhere:
|
|
177
|
+
|
|
178
|
+
- **Unknown keys fail loudly.** A typo'd key aborts with a validation error
|
|
179
|
+
naming the key, instead of being silently ignored.
|
|
180
|
+
- **Polymorphic entries pick their variant with a `type:` key** — recorder
|
|
181
|
+
kinds (`topic`, `video`), frame transforms (`deinterlace`, `resize`,
|
|
182
|
+
`rectify`), table transforms (`parse_vector3`, `sum_columns`, ...),
|
|
183
|
+
writers (`csv`, `lerobot`), phase labelers and triggers. The fields next
|
|
184
|
+
to `type` are the variant's own options.
|
|
185
|
+
- **Configs compose with `!include`**, resolved relative to the including
|
|
186
|
+
file. This keeps shared pieces in one place, e.g.
|
|
187
|
+
`configs/recording/mock_ui.yaml` includes
|
|
188
|
+
`configs/dataset/mock.yaml` as its `generation:` section, and the same
|
|
189
|
+
file is included by `configs/annotation/mock.yaml`.
|
|
190
|
+
- **Calibration files packaged with the library** can be referenced as
|
|
191
|
+
`package://<asset name>`.
|
|
192
|
+
|
|
193
|
+
**CLI overrides**: any field can be overridden with `--dot.path value`
|
|
194
|
+
pairs on top of the YAML. Values are parsed as YAML scalars, so `true`,
|
|
195
|
+
`3.5`, and `[a, b]` become their typed equivalents. List elements are
|
|
196
|
+
addressed by index, and setting a `type` key resets that section to the
|
|
197
|
+
new variant (follow-up flags then fill its fields):
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
python -m tso_sensorium.scripts.generate_dataset \
|
|
201
|
+
--config_path configs/dataset/bowel_retraction.yaml \
|
|
202
|
+
--recordings_root /data/recordings \
|
|
203
|
+
--save_frames true \
|
|
204
|
+
--videos.0.frame_column framePath \
|
|
205
|
+
--writer.type lerobot --writer.output_root /data/lerobot
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
All modules require Python 3.9+ (pydantic-backed config layer).
|
|
209
|
+
|
|
210
|
+
### Recording
|
|
211
|
+
|
|
212
|
+
When the endoscope is mounted on a moving robot, publish its calibrated live
|
|
213
|
+
camera orientation from the robot-state topic in a separate ROS 1 Pixi shell:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
python -m tso_sensorium.scripts.publish_robot_camera_transform \
|
|
217
|
+
--subscribe /ur5e_rcm_twist_controller/RobotState \
|
|
218
|
+
--topic /robot_camera_transform \
|
|
219
|
+
--calibration_path configs/calibration/tso_endoscope_mount.yaml
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The calibration file contains the fixed EE-to-camera axis mapping and optical
|
|
223
|
+
tilt. Replace it for a different holder or endoscope geometry. The equivalent
|
|
224
|
+
installed command is `tso-publish-camera-transform`.
|
|
225
|
+
|
|
226
|
+
Record ROS topics and video streams in one shot (inside a ROS 1
|
|
227
|
+
environment):
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
export TESTBED_WORKSPACE=/path/to/robot_testbed
|
|
231
|
+
export PYTHONPATH="$TESTBED_WORKSPACE/devel/lib/python3/dist-packages"
|
|
232
|
+
python -m tso_sensorium.scripts.record \
|
|
233
|
+
--config_path configs/recording/tso_testbed.yaml \
|
|
234
|
+
--output_folder "$HOME/tso_sensorium_recordings"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
For interactive sessions, `record_ui` runs the browser UI (requires the `gui`
|
|
238
|
+
extra):
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
export TESTBED_WORKSPACE=/path/to/robot_testbed
|
|
242
|
+
export PYTHONPATH="$TESTBED_WORKSPACE/devel/lib/python3/dist-packages"
|
|
243
|
+
python -m tso_sensorium.scripts.record_ui \
|
|
244
|
+
--config_path configs/recording/tso_testbed_ui.yaml \
|
|
245
|
+
--session.output_folder "$HOME/tso_sensorium_recordings"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
For the force-sensing setup, the bundled UI config records UR5e state,
|
|
249
|
+
the left, right, and combined endoscope streams, the robot-camera transform,
|
|
250
|
+
and the Bota wrench and IMU topics. It saves under
|
|
251
|
+
`~/tso_sensorium_recordings` by default:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
python -m tso_sensorium.scripts.record_ui \
|
|
255
|
+
--config_path configs/recording/force_session_ui.yaml
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Video readiness uses an explicitly configured lightweight per-frame status
|
|
259
|
+
topic, not raw image subscriptions. Each video entry in the recording YAML
|
|
260
|
+
must specify `liveness_topic` when used with the UI; topic names are never
|
|
261
|
+
inferred. The bundled setups use `sensor_msgs/CameraInfo` messages.
|
|
262
|
+
Recording still subscribes to the configured image topic when an episode
|
|
263
|
+
starts and releases that subscription when it stops. The configured live
|
|
264
|
+
preview is a separate, intentional image subscription.
|
|
265
|
+
|
|
266
|
+
Set the status topic and its message type alongside the recorded image topic:
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
- type: video
|
|
270
|
+
file_name: camera
|
|
271
|
+
topic_name: /camera/image_raw
|
|
272
|
+
liveness_topic: /camera/camera_info
|
|
273
|
+
liveness_message_type: sensor_msgs.msg.CameraInfo
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Other per-frame status messages, such as `std_msgs.msg.Header`, can be selected
|
|
277
|
+
with `liveness_message_type`. Command-line recording does not use these fields.
|
|
278
|
+
The status topic must publish with each acquired frame; latched calibration
|
|
279
|
+
alone does not establish ongoing camera freshness. A missing or stale status
|
|
280
|
+
topic keeps the source unready rather than silently falling back to expensive
|
|
281
|
+
raw-image subscriptions. If a camera can only provide raw images for status,
|
|
282
|
+
explicitly select that image topic and `sensor_msgs.msg.Image`, accepting its
|
|
283
|
+
bandwidth cost. This affects readiness only, not the recording format.
|
|
284
|
+
|
|
285
|
+
Open `http://<host>:8080` from any machine on the network. The dashboard
|
|
286
|
+
has two sections: **Record** (live camera feed, per-sensor liveness,
|
|
287
|
+
start/stop of demonstrations, per-episode topic selection) and **Library**
|
|
288
|
+
(episode browsing with in-browser replay, phase annotation, and dataset
|
|
289
|
+
generation — see below). The server is unauthenticated; expose it on
|
|
290
|
+
trusted networks only.
|
|
291
|
+
|
|
292
|
+
To try the full record → browse → annotate → generate loop without any
|
|
293
|
+
hardware, first enter `pixi shell -e ros1`, then run the bundled synthetic
|
|
294
|
+
sensors:
|
|
295
|
+
|
|
296
|
+
First check whether the current `ROS_MASTER_URI` already has a running master:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
rostopic list
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
If that succeeds, reuse the existing master. If it fails, start `roscore` in
|
|
303
|
+
that terminal. Do not start a second master:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
roscore
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Open a second terminal, enter `pixi shell -e ros1`, and start the sensors:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
python -m tso_sensorium.scripts.mock_sensors
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Open a third terminal, enter `pixi shell -e ros1`, and start the dashboard:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
python -m tso_sensorium.scripts.record_ui \
|
|
319
|
+
--config_path configs/recording/mock_ui.yaml
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
ROS may warn when `~/.ros/log` exceeds 1 GB. Inspect it with `rosclean check`;
|
|
323
|
+
run `rosclean purge` only if deleting old ROS logs is acceptable.
|
|
324
|
+
|
|
325
|
+
The same dashboard runs on ROS 2 (`record_ui_ros2`,
|
|
326
|
+
`mock_sensors_ros2`) with identical configs — recorder entries reference
|
|
327
|
+
message types by dotted path (`std_msgs.msg.Bool`), which resolve to the
|
|
328
|
+
ROS 2 classes inside a ROS 2 environment:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
python -m tso_sensorium.scripts.mock_sensors_ros2 &
|
|
332
|
+
python -m tso_sensorium.scripts.record_ui_ros2 \
|
|
333
|
+
--config_path configs/recording/mock_ui.yaml
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Annotation and dataset studio (no ROS required)
|
|
337
|
+
|
|
338
|
+
The Library section also runs as a standalone app on any machine — a
|
|
339
|
+
processing workstation without ROS, pointed at a folder of recordings:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
python -m tso_sensorium.scripts.annotate \
|
|
343
|
+
--config_path configs/annotation/mock.yaml
|
|
344
|
+
# or directly:
|
|
345
|
+
python -m tso_sensorium.scripts.annotate \
|
|
346
|
+
--recordings_root /data/recordings --port 8090
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
From the dashboard you can:
|
|
350
|
+
|
|
351
|
+
- **Switch the recordings folder** being browsed (the path field at the
|
|
352
|
+
top; the episode list, annotations, and legend reload for that folder).
|
|
353
|
+
- **Edit the dataset metadata and phase legend**: dataset name, task, and
|
|
354
|
+
the mapping of integer phase labels to a phase name plus its language
|
|
355
|
+
instruction variants. One instruction per phase is deterministic;
|
|
356
|
+
several lines make a stochastic mapping — one variant is sampled per
|
|
357
|
+
episode, seeded by the episode name so regeneration is reproducible.
|
|
358
|
+
The legend is saved to `dataset_metadata.json` at the recordings root.
|
|
359
|
+
- **Annotate episodes on a timeline** (pencil icon next to an episode):
|
|
360
|
+
colored phase segments under the video, click to seek and select, drag
|
|
361
|
+
segment edges, split at the playhead, and assign phases from the legend.
|
|
362
|
+
Edits are saved to `annotations.json` inside the episode folder with
|
|
363
|
+
`source: manual`, so re-running an automatic labeler never overwrites
|
|
364
|
+
them.
|
|
365
|
+
- **Generate datasets** with per-run options (format, output root, frame
|
|
366
|
+
extraction, sync tolerance); discarded episodes are listed with the
|
|
367
|
+
reason.
|
|
368
|
+
|
|
369
|
+
### Phase legends
|
|
370
|
+
|
|
371
|
+
Legends map phase labels to names and instruction variants. Select one in
|
|
372
|
+
your dataset-generation config:
|
|
373
|
+
|
|
374
|
+
```yaml
|
|
375
|
+
annotations:
|
|
376
|
+
legend: package://instructions/endoscope_guidance.yaml
|
|
377
|
+
legend_source: config
|
|
378
|
+
language_source: phase_legend
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`legend` also accepts a YAML filename or an inline metadata mapping.
|
|
382
|
+
|
|
383
|
+
- `legend_source`: `config` uses the supplied legend; `auto` (default) prefers
|
|
384
|
+
the phase legend in the recordings root's `dataset_metadata.json`, falling
|
|
385
|
+
back to the configured legend when none is saved.
|
|
386
|
+
- `language_source`: `phase_legend` samples instructions from the legend;
|
|
387
|
+
`annotation` (default) uses segment-specific text when present, otherwise
|
|
388
|
+
falling back to the legend.
|
|
389
|
+
|
|
390
|
+
See the [annotation configuration](tso_sensorium/episodes/generation_config.py)
|
|
391
|
+
for all fields and defaults.
|
|
392
|
+
|
|
393
|
+
### Automatic phase labeling
|
|
394
|
+
|
|
395
|
+
Labelers segment episodes from recorded signals and write automatic
|
|
396
|
+
segments into each episode's `annotations.json`:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
python -m tso_sensorium.scripts.label_phases \
|
|
400
|
+
--config_path configs/labeling/bowel_retraction.yaml \
|
|
401
|
+
--recordings_root /data/recordings
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`configs/labeling/` shows both labeler kinds: `column_threshold` (two
|
|
405
|
+
phases split by a signal threshold) and `sequential_trigger` (an ordered
|
|
406
|
+
phase sequence where each trigger hands over to the next phase — gripper
|
|
407
|
+
state changes, motion starting or settling). The typical workflow is
|
|
408
|
+
auto-label → correct visually in the dashboard → generate.
|
|
409
|
+
|
|
410
|
+
### Dataset generation
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
python -m tso_sensorium.scripts.generate_dataset \
|
|
414
|
+
--config_path configs/dataset/bowel_retraction.yaml \
|
|
415
|
+
--recordings_root /data/recordings
|
|
416
|
+
|
|
417
|
+
# Same recordings, exported as a LeRobot dataset instead:
|
|
418
|
+
python -m tso_sensorium.scripts.generate_dataset \
|
|
419
|
+
--config_path configs/dataset/bowel_retraction.yaml \
|
|
420
|
+
--recordings_root /data/recordings \
|
|
421
|
+
--writer.type lerobot --writer.output_root /data/lerobot
|
|
422
|
+
|
|
423
|
+
# Recompute transformed actions without re-encoding observations or videos:
|
|
424
|
+
python -m tso_sensorium.scripts.generate_dataset \
|
|
425
|
+
--config_path configs/dataset/bowel_retraction.yaml \
|
|
426
|
+
--recordings_root /data/recordings \
|
|
427
|
+
--writer.type lerobot_action_update \
|
|
428
|
+
--writer.dataset_root /data/lerobot
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
When the config has an `annotations:` section, each episode's phase
|
|
432
|
+
segments are joined onto the aligned table as an integer phase column and
|
|
433
|
+
a language instruction column. Every writer stores generation statistics
|
|
434
|
+
and any configured `schema.coordinate_frame_features` in
|
|
435
|
+
`dataset_metadata.json`. Coordinate-frame entries list their exact component
|
|
436
|
+
columns, named frame, and whether that frame is fixed, moving, or unknown
|
|
437
|
+
across timesteps. LeRobot exports can also carry named
|
|
438
|
+
`schema.auxiliary_features`, such as an integer phase label, without folding
|
|
439
|
+
them into the policy action vector. Action-only updates require an existing
|
|
440
|
+
LeRobot v3 dataset whose episode order, lengths, schema, observations,
|
|
441
|
+
auxiliary values, and task strings exactly match the regenerated episodes.
|
|
442
|
+
They atomically replace only actions, derived action statistics, and
|
|
443
|
+
`dataset_metadata.json`; a validation failure or cancellation leaves the
|
|
444
|
+
existing dataset unchanged.
|
|
445
|
+
|
|
446
|
+
### Library usage
|
|
447
|
+
|
|
448
|
+
Generate an episode table programmatically:
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
from tso_sensorium.episodes import EpisodeGenerator
|
|
452
|
+
|
|
453
|
+
episode = (
|
|
454
|
+
EpisodeGenerator()
|
|
455
|
+
.add_video(
|
|
456
|
+
video_path="episode/left.mp4",
|
|
457
|
+
timestamps_path="episode/left_timestamps.csv",
|
|
458
|
+
sync_col_name="timestamp",
|
|
459
|
+
frames_output_path="episode/frames/left",
|
|
460
|
+
frame_col_name="left_frame",
|
|
461
|
+
save_frames=True,
|
|
462
|
+
)
|
|
463
|
+
.add_state(
|
|
464
|
+
state_data_path="episode/robot_state.csv",
|
|
465
|
+
sync_col_name="timestamp",
|
|
466
|
+
dataset_cols=["x", "y", "z", "roll"],
|
|
467
|
+
)
|
|
468
|
+
.generate_dataset()
|
|
469
|
+
.save_dataset("episode/episode.csv")
|
|
470
|
+
)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Rectify and deinterlace stereo images:
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
from tso_sensorium.processing import Rectifier, deinterlace_cv_image
|
|
477
|
+
|
|
478
|
+
rectifier = Rectifier(calibration_file_path="calibration.yml")
|
|
479
|
+
left, right = deinterlace_cv_image(image=interlaced_image)
|
|
480
|
+
left, right = rectifier.rectify(left=left, right=right)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Development
|
|
484
|
+
|
|
485
|
+
Enter the default Pixi shell for framework-independent development:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
pixi shell
|
|
489
|
+
pytest
|
|
490
|
+
pytest -m ""
|
|
491
|
+
ruff format tso_sensorium/ tests/
|
|
492
|
+
ruff check tso_sensorium/ tests/
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
ROS adapter tests require the matching environment and skip in the default
|
|
496
|
+
environment:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
exit
|
|
500
|
+
pixi shell -e ros1
|
|
501
|
+
pytest tests/recording/test_ros1_adapter.py
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
For non-interactive use, the equivalents are `pixi run test`, `pixi run
|
|
505
|
+
test-all`, `pixi run lint`, and `pixi run -e ros1 pytest
|
|
506
|
+
tests/recording/test_ros1_adapter.py`.
|
|
507
|
+
|
|
508
|
+
CI checks pull requests and `main`, including ROS 1/2 tests and package builds.
|
|
509
|
+
Published GitHub releases upload to PyPI after those checks pass.
|