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.
Files changed (79) hide show
  1. tso_sensorium-0.1.0/LICENSE +22 -0
  2. tso_sensorium-0.1.0/PKG-INFO +509 -0
  3. tso_sensorium-0.1.0/README.md +471 -0
  4. tso_sensorium-0.1.0/pyproject.toml +168 -0
  5. tso_sensorium-0.1.0/setup.cfg +4 -0
  6. tso_sensorium-0.1.0/tests/test_resources.py +19 -0
  7. tso_sensorium-0.1.0/tso_sensorium/__init__.py +8 -0
  8. tso_sensorium-0.1.0/tso_sensorium/assets/instructions/bowel_retraction_phantom.yaml +76 -0
  9. tso_sensorium-0.1.0/tso_sensorium/assets/instructions/bowel_retraction_porcine.yaml +76 -0
  10. tso_sensorium-0.1.0/tso_sensorium/assets/instructions/endoscope_guidance.yaml +27 -0
  11. tso_sensorium-0.1.0/tso_sensorium/assets/recording_dashboard.html +1556 -0
  12. tso_sensorium-0.1.0/tso_sensorium/assets/storz_endoscope_calibration.yml +68 -0
  13. tso_sensorium-0.1.0/tso_sensorium/configuration.py +150 -0
  14. tso_sensorium-0.1.0/tso_sensorium/episodes/__init__.py +31 -0
  15. tso_sensorium-0.1.0/tso_sensorium/episodes/annotations.py +158 -0
  16. tso_sensorium-0.1.0/tso_sensorium/episodes/builder.py +244 -0
  17. tso_sensorium-0.1.0/tso_sensorium/episodes/dataset_builder.py +416 -0
  18. tso_sensorium-0.1.0/tso_sensorium/episodes/dataset_transforms.py +191 -0
  19. tso_sensorium-0.1.0/tso_sensorium/episodes/generation.py +184 -0
  20. tso_sensorium-0.1.0/tso_sensorium/episodes/generation_config.py +344 -0
  21. tso_sensorium-0.1.0/tso_sensorium/episodes/legend.py +157 -0
  22. tso_sensorium-0.1.0/tso_sensorium/episodes/phase_labelers.py +275 -0
  23. tso_sensorium-0.1.0/tso_sensorium/episodes/schema.py +240 -0
  24. tso_sensorium-0.1.0/tso_sensorium/episodes/table_transforms.py +362 -0
  25. tso_sensorium-0.1.0/tso_sensorium/export/__init__.py +11 -0
  26. tso_sensorium-0.1.0/tso_sensorium/export/base.py +47 -0
  27. tso_sensorium-0.1.0/tso_sensorium/export/csv_writer.py +59 -0
  28. tso_sensorium-0.1.0/tso_sensorium/export/lerobot_action_update_writer.py +966 -0
  29. tso_sensorium-0.1.0/tso_sensorium/export/lerobot_writer.py +218 -0
  30. tso_sensorium-0.1.0/tso_sensorium/processing/__init__.py +30 -0
  31. tso_sensorium-0.1.0/tso_sensorium/processing/alignment.py +216 -0
  32. tso_sensorium-0.1.0/tso_sensorium/processing/camera_transform.py +146 -0
  33. tso_sensorium-0.1.0/tso_sensorium/processing/frame_transforms.py +131 -0
  34. tso_sensorium-0.1.0/tso_sensorium/processing/image.py +45 -0
  35. tso_sensorium-0.1.0/tso_sensorium/processing/rectification.py +99 -0
  36. tso_sensorium-0.1.0/tso_sensorium/recording/__init__.py +13 -0
  37. tso_sensorium-0.1.0/tso_sensorium/recording/config.py +145 -0
  38. tso_sensorium-0.1.0/tso_sensorium/recording/core.py +255 -0
  39. tso_sensorium-0.1.0/tso_sensorium/recording/dashboard.py +370 -0
  40. tso_sensorium-0.1.0/tso_sensorium/recording/denoising_preview.py +324 -0
  41. tso_sensorium-0.1.0/tso_sensorium/recording/episode_files.py +77 -0
  42. tso_sensorium-0.1.0/tso_sensorium/recording/episode_names.py +29 -0
  43. tso_sensorium-0.1.0/tso_sensorium/recording/library_service.py +754 -0
  44. tso_sensorium-0.1.0/tso_sensorium/recording/liveness.py +60 -0
  45. tso_sensorium-0.1.0/tso_sensorium/recording/message_fields.py +55 -0
  46. tso_sensorium-0.1.0/tso_sensorium/recording/network.py +43 -0
  47. tso_sensorium-0.1.0/tso_sensorium/recording/playback.py +66 -0
  48. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/__init__.py +33 -0
  49. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/camera_transform_publisher.py +141 -0
  50. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/liveness.py +116 -0
  51. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/random_publisher.py +259 -0
  52. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/record.py +210 -0
  53. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/ros_config.py +37 -0
  54. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/session.py +127 -0
  55. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/video_subscriber.py +165 -0
  56. tso_sensorium-0.1.0/tso_sensorium/recording/ros1/web_service.py +54 -0
  57. tso_sensorium-0.1.0/tso_sensorium/recording/ros2/__init__.py +9 -0
  58. tso_sensorium-0.1.0/tso_sensorium/recording/ros2/liveness.py +118 -0
  59. tso_sensorium-0.1.0/tso_sensorium/recording/ros2/record.py +226 -0
  60. tso_sensorium-0.1.0/tso_sensorium/recording/ros2/session.py +136 -0
  61. tso_sensorium-0.1.0/tso_sensorium/recording/ros2/web_service.py +61 -0
  62. tso_sensorium-0.1.0/tso_sensorium/resources.py +34 -0
  63. tso_sensorium-0.1.0/tso_sensorium/scripts/__init__.py +8 -0
  64. tso_sensorium-0.1.0/tso_sensorium/scripts/annotate.py +55 -0
  65. tso_sensorium-0.1.0/tso_sensorium/scripts/generate_dataset.py +36 -0
  66. tso_sensorium-0.1.0/tso_sensorium/scripts/label_phases.py +33 -0
  67. tso_sensorium-0.1.0/tso_sensorium/scripts/mock_scene.py +60 -0
  68. tso_sensorium-0.1.0/tso_sensorium/scripts/mock_sensors.py +72 -0
  69. tso_sensorium-0.1.0/tso_sensorium/scripts/mock_sensors_ros2.py +78 -0
  70. tso_sensorium-0.1.0/tso_sensorium/scripts/publish_robot_camera_transform.py +120 -0
  71. tso_sensorium-0.1.0/tso_sensorium/scripts/record.py +67 -0
  72. tso_sensorium-0.1.0/tso_sensorium/scripts/record_ui.py +60 -0
  73. tso_sensorium-0.1.0/tso_sensorium/scripts/record_ui_ros2.py +69 -0
  74. tso_sensorium-0.1.0/tso_sensorium.egg-info/PKG-INFO +509 -0
  75. tso_sensorium-0.1.0/tso_sensorium.egg-info/SOURCES.txt +77 -0
  76. tso_sensorium-0.1.0/tso_sensorium.egg-info/dependency_links.txt +1 -0
  77. tso_sensorium-0.1.0/tso_sensorium.egg-info/entry_points.txt +10 -0
  78. tso_sensorium-0.1.0/tso_sensorium.egg-info/requires.txt +17 -0
  79. 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.