oopsie-data-tools 0.2.0__py3-none-any.whl
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.
- oopsie_data_tools/__init__.py +7 -0
- oopsie_data_tools/annotation_tool/__init__.py +5 -0
- oopsie_data_tools/annotation_tool/annotation_schema.py +266 -0
- oopsie_data_tools/annotation_tool/annotator_server.py +850 -0
- oopsie_data_tools/annotation_tool/episode_recorder.py +648 -0
- oopsie_data_tools/annotation_tool/rollout_annotator.py +333 -0
- oopsie_data_tools/annotation_tool/ui/annotator.html +2240 -0
- oopsie_data_tools/cli.py +894 -0
- oopsie_data_tools/init_wizard.py +329 -0
- oopsie_data_tools/skill/SKILL.md +98 -0
- oopsie_data_tools/skill/reference/conversion.md +257 -0
- oopsie_data_tools/skill/reference/format.md +112 -0
- oopsie_data_tools/skill/reference/robot-profile.md +104 -0
- oopsie_data_tools/skill/reference/setup.md +216 -0
- oopsie_data_tools/skill/reference/troubleshooting.md +97 -0
- oopsie_data_tools/test/__init__.py +1 -0
- oopsie_data_tools/test/conftest.py +174 -0
- oopsie_data_tools/test/fixtures/__init__.py +0 -0
- oopsie_data_tools/test/fixtures/make_invalid.py +633 -0
- oopsie_data_tools/test/fixtures/make_valid.py +406 -0
- oopsie_data_tools/test/test_annotation_completeness.py +154 -0
- oopsie_data_tools/test/test_annotation_schema.py +190 -0
- oopsie_data_tools/test/test_annotator_server.py +240 -0
- oopsie_data_tools/test/test_annotator_server_guards.py +146 -0
- oopsie_data_tools/test/test_bulk_inference_end_to_end.py +112 -0
- oopsie_data_tools/test/test_cli_config.py +111 -0
- oopsie_data_tools/test/test_cli_tools.py +438 -0
- oopsie_data_tools/test/test_contributor_config.py +38 -0
- oopsie_data_tools/test/test_conversion_utils_annotations.py +75 -0
- oopsie_data_tools/test/test_credentials_location.py +106 -0
- oopsie_data_tools/test/test_diversity.py +33 -0
- oopsie_data_tools/test/test_episode_recorder.py +335 -0
- oopsie_data_tools/test/test_episode_video_paths.py +173 -0
- oopsie_data_tools/test/test_hf_upload.py +234 -0
- oopsie_data_tools/test/test_init_wizard.py +193 -0
- oopsie_data_tools/test/test_install_skill.py +120 -0
- oopsie_data_tools/test/test_migrate_taxonomy_v2.py +255 -0
- oopsie_data_tools/test/test_new_profile.py +84 -0
- oopsie_data_tools/test/test_paths.py +137 -0
- oopsie_data_tools/test/test_python38_compat.py +79 -0
- oopsie_data_tools/test/test_record_step_purity.py +127 -0
- oopsie_data_tools/test/test_robot_setup.py +244 -0
- oopsie_data_tools/test/test_rollout_annotator.py +134 -0
- oopsie_data_tools/test/test_rotation_utils.py +126 -0
- oopsie_data_tools/test/test_validate.py +494 -0
- oopsie_data_tools/utils/__init__.py +1 -0
- oopsie_data_tools/utils/claude_skill.py +96 -0
- oopsie_data_tools/utils/contributor_config.py +91 -0
- oopsie_data_tools/utils/conversion_utils.py +220 -0
- oopsie_data_tools/utils/h5.py +60 -0
- oopsie_data_tools/utils/h5_inspect.py +167 -0
- oopsie_data_tools/utils/hf_limits.py +22 -0
- oopsie_data_tools/utils/hf_upload.py +235 -0
- oopsie_data_tools/utils/log.py +35 -0
- oopsie_data_tools/utils/migrate_taxonomy_v2.py +215 -0
- oopsie_data_tools/utils/paths.py +162 -0
- oopsie_data_tools/utils/restructure.py +402 -0
- oopsie_data_tools/utils/robot_profile/__init__.py +1 -0
- oopsie_data_tools/utils/robot_profile/robot_profile.py +240 -0
- oopsie_data_tools/utils/robot_profile/rotation_utils.py +119 -0
- oopsie_data_tools/utils/robot_profile/template.py +108 -0
- oopsie_data_tools/utils/validation/__init__.py +5 -0
- oopsie_data_tools/utils/validation/annotation_completeness.py +67 -0
- oopsie_data_tools/utils/validation/diversity.py +93 -0
- oopsie_data_tools/utils/validation/episode_data.py +54 -0
- oopsie_data_tools/utils/validation/episode_loader.py +201 -0
- oopsie_data_tools/utils/validation/episode_validator.py +315 -0
- oopsie_data_tools/utils/validation/errors.py +17 -0
- oopsie_data_tools/utils/validation/validation_utils.py +88 -0
- oopsie_data_tools-0.2.0.dist-info/METADATA +131 -0
- oopsie_data_tools-0.2.0.dist-info/RECORD +74 -0
- oopsie_data_tools-0.2.0.dist-info/WHEEL +4 -0
- oopsie_data_tools-0.2.0.dist-info/entry_points.txt +2 -0
- oopsie_data_tools-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# The `oopsiedata_format_v1` data format
|
|
2
|
+
|
|
3
|
+
One episode per HDF5 file, with its MP4 videos alongside. Run `oopsie-data inspect <file.h5>` to
|
|
4
|
+
see what a file actually contains before theorizing about it — it works even on files `validate`
|
|
5
|
+
rejects.
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
Root attributes — `schema`, `episode_id`, `language_instruction`, `lab_id`, `operator_name` and
|
|
10
|
+
`robot_profile` (the whole profile, JSON-serialized) are **required**. `timestamp` (float epoch
|
|
11
|
+
seconds) is written by the recorder but not required by the loader.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
observations/
|
|
15
|
+
robot_states/<key> one float64 dataset per profile.robot_state_keys, shape (T, ...)
|
|
16
|
+
video_paths/<camera> one string dataset per profile.camera_names, holding a path
|
|
17
|
+
relative to the .h5 file
|
|
18
|
+
actions/<key> one dataset per VALID_ACTION_KEYS
|
|
19
|
+
episode_annotations/<annotator_name>/ optional; annotation fields are HDF5 attrs on the
|
|
20
|
+
subgroup, not datasets
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Note that state datasets live under `observations/robot_states/`, not directly under
|
|
24
|
+
`observations/`.
|
|
25
|
+
|
|
26
|
+
## Actions
|
|
27
|
+
|
|
28
|
+
The recorder writes a dataset for **all nine** valid action keys. Keys outside the profile's
|
|
29
|
+
`action_space` are written as `h5py.Empty` placeholders; **every key in `action_space` must be a
|
|
30
|
+
real tensor** — an `h5py.Empty` there is rejected with `actions/<key> is in profile.action_space
|
|
31
|
+
but stored as h5py.Empty`. Since a valid action space always has at least one arm and one gripper
|
|
32
|
+
key, at least two `actions/` datasets are always real.
|
|
33
|
+
|
|
34
|
+
The reverse also holds: a non-empty action dataset the profile does not declare is rejected.
|
|
35
|
+
|
|
36
|
+
Actions are **unnormalized and absolute**. Bi-arm setups concatenate left and right.
|
|
37
|
+
|
|
38
|
+
## What the validator checks
|
|
39
|
+
|
|
40
|
+
Metadata:
|
|
41
|
+
|
|
42
|
+
- `language_instruction`, `episode_id`, `lab_id`, `operator_name` all non-empty; `lab_id` must
|
|
43
|
+
not still be `your_lab_id`.
|
|
44
|
+
- `control_freq > 0`.
|
|
45
|
+
- **Episode duration, not step count**: `trajectory_length / control_freq` must be between 1 and
|
|
46
|
+
600 seconds. A 5-step episode at 10 Hz is rejected.
|
|
47
|
+
|
|
48
|
+
Profile consistency — the profile documents the episode, so both directions are enforced:
|
|
49
|
+
|
|
50
|
+
- Every `robot_state_keys`, `action_space` and `camera_names` entry must be present.
|
|
51
|
+
- Nothing may be present that the profile does not declare, for either
|
|
52
|
+
`observations/robot_states` or `actions`.
|
|
53
|
+
- `len(robot_state_joint_names)` must equal the last axis of `observations/joint_position`, when
|
|
54
|
+
`joint_position` is recorded at all.
|
|
55
|
+
- `len(action_joint_names)` must equal the last axis of `actions/joint_position` /
|
|
56
|
+
`joint_velocity`, when set.
|
|
57
|
+
- `cartesian_position` (state or action) must be exactly 7 DOF, or 14 when `is_biarm` — it is
|
|
58
|
+
`[x, y, z, qx, qy, qz, qw]` per arm. Joint counts are deliberately **not** constrained by
|
|
59
|
+
`is_biarm`: two arms need not share a DOF count. Gripper DOF is not checked at all, since
|
|
60
|
+
nothing in the profile declares it.
|
|
61
|
+
|
|
62
|
+
Trajectories:
|
|
63
|
+
|
|
64
|
+
- Every observation and action array must share the same leading dimension `T`.
|
|
65
|
+
|
|
66
|
+
Videos:
|
|
67
|
+
|
|
68
|
+
- Each side between 180 and 1280 px.
|
|
69
|
+
- Frame count within `max(5, 0.1 * T)` of `T`.
|
|
70
|
+
- Video duration within 0.5 s of `T / control_freq`.
|
|
71
|
+
- Frame counts across cameras within 1 of each other.
|
|
72
|
+
|
|
73
|
+
Annotations — **always checked by `validate` and `upload`.** The `strict_annotation_check`
|
|
74
|
+
*parameter* defaults to `False`, but `run_validation` — what both CLI commands call — passes
|
|
75
|
+
`True` unconditionally, so an episode with no `episode_annotations` group fails with
|
|
76
|
+
`Annotations dict is empty, must be provided for upload`. Record an episode first and annotate
|
|
77
|
+
it second; it is only between those two steps that an unannotated episode is legal.
|
|
78
|
+
|
|
79
|
+
- Every annotator subgroup needs a numeric, non-NaN `success` in `[0.0, 1.0]`.
|
|
80
|
+
- A present `taxonomy` attr must parse as a JSON object.
|
|
81
|
+
- If that object carries an `outcome`, it must be one of `success`, `success_suboptimal`,
|
|
82
|
+
`success_side_effect`, `failure`, and must agree in sign with `success` (`failure` iff
|
|
83
|
+
`success < 0.5`).
|
|
84
|
+
- Nothing else is required. `episode_description`, `side_effect_category` and `severity` are
|
|
85
|
+
all optional in every branch, so a partial annotation — including a failure with no taxonomy
|
|
86
|
+
at all — is valid. Taxonomy v1 files, which carry no `outcome`, skip the outcome check and
|
|
87
|
+
remain valid unchanged.
|
|
88
|
+
|
|
89
|
+
## Recording-time checks
|
|
90
|
+
|
|
91
|
+
`record_step` requires:
|
|
92
|
+
|
|
93
|
+
- `observation` to be a dict with both `robot_state` and `image_observation`;
|
|
94
|
+
- `robot_state` to contain every `robot_state_keys` entry, and `image_observation` a key named
|
|
95
|
+
exactly `<cam>` for every `camera_names` entry — the alternative spellings `image_<cam>` and
|
|
96
|
+
`<cam>_image` are recognized when the frame is read, but the presence check runs first and
|
|
97
|
+
rejects the step, so they are not a usable substitute for the plain name;
|
|
98
|
+
- `action` keys to equal `action_space` exactly, with no `None` values.
|
|
99
|
+
|
|
100
|
+
`cartesian_position` is converted to `(x, y, z, qx, qy, qz, qw)` via
|
|
101
|
+
`orientation_representation`, then shape-checked to `(7,)` or `(14,)` with a unit quaternion in
|
|
102
|
+
`[3:7]` (tolerance 1e-2). Everything else is recorded as given.
|
|
103
|
+
|
|
104
|
+
`finish_rollout` validates *before* writing videos or HDF5, so a rejected episode leaves nothing
|
|
105
|
+
on disk. It runs the same checks as `validate` **except** the annotation ones — this is the one
|
|
106
|
+
path that uses the lenient default — which is why `finish_rollout(instruction=...)` without
|
|
107
|
+
`success` is allowed, and why the resulting episode does not pass `validate` until annotated. It raises `EpisodeValidationError`, which subclasses `AssertionError` — so an existing
|
|
108
|
+
`except AssertionError` still catches it. One exception: a camera with zero buffered frames
|
|
109
|
+
raises a plain `ValueError` from `VideoInfo.from_frames`, which neither catches.
|
|
110
|
+
|
|
111
|
+
Episode file names are second-resolution timestamps with `_2`, `_3` suffixes on collision, so do
|
|
112
|
+
not parse them as pure timestamps.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Robot profile
|
|
2
|
+
|
|
3
|
+
A robot profile captures hardware and policy metadata. It is serialized to JSON and stored as an
|
|
4
|
+
HDF5 root attribute on every episode, so it is the episode's documentation — the validator
|
|
5
|
+
checks the recorded data against it in both directions.
|
|
6
|
+
|
|
7
|
+
Start from `oopsie-data new-profile --name <name>`, which writes a commented skeleton into
|
|
8
|
+
`<profiles-dir>/`. Every required field is blank, so the profile will not load until a human
|
|
9
|
+
fills it in. Ask the user the questions below; where the choice is constrained to a set, list the
|
|
10
|
+
options and explain them if asked.
|
|
11
|
+
|
|
12
|
+
## Required fields
|
|
13
|
+
|
|
14
|
+
All nine must be present or `load_robot_profile` raises `Robot profile missing keys: [...]`.
|
|
15
|
+
|
|
16
|
+
| Question | YAML key | Example / rule |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| What is the policy name? | `policy_name` | `pi0.5`, `act_plus_plus` |
|
|
19
|
+
| What is the robot name? | `robot_name` | `franka_droid`, `aloha` |
|
|
20
|
+
| What is the gripper name? | `gripper_name` | `robotiq_2f_85`, `aloha_gripper` |
|
|
21
|
+
| Is this a bimanual setup? | `is_biarm` | `true` / `false` |
|
|
22
|
+
| Does the robot use a mobile base? | `uses_mobile_base` | `true` / `false` |
|
|
23
|
+
| Control frequency (Hz)? | `control_freq` | `10`, `50` — must be > 0 |
|
|
24
|
+
| Camera names? | `camera_names` | `[left, right, wrist]` |
|
|
25
|
+
| Which robot state keys are recorded? | `robot_state_keys` | see below |
|
|
26
|
+
| What does the policy output? | `action_space` | see below |
|
|
27
|
+
|
|
28
|
+
### `robot_state_keys`
|
|
29
|
+
|
|
30
|
+
`gripper_position` is **always mandatory**. Beyond that, the state must observe whatever space
|
|
31
|
+
the action controls — checked at profile load:
|
|
32
|
+
|
|
33
|
+
| If `action_space` contains | `robot_state_keys` must contain |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `joint_position` or `joint_velocity` | `joint_position` |
|
|
36
|
+
| `cartesian_position` or `cartesian_velocity` | `cartesian_position` |
|
|
37
|
+
|
|
38
|
+
Velocity control requires *position* state: you observe where the arm is, you command how fast
|
|
39
|
+
it moves. The requirement is a union over the action space, so an `action_space` mixing joint
|
|
40
|
+
and Cartesian actions needs both keys. `base_position` is an optional addition — no base action
|
|
41
|
+
currently requires it.
|
|
42
|
+
|
|
43
|
+
`base_position` aside, nothing else may be recorded: an observation key the profile does not
|
|
44
|
+
declare fails validation, because it has no joint names, units or expected DOF and nothing
|
|
45
|
+
downstream can interpret it.
|
|
46
|
+
|
|
47
|
+
### `robot_state_joint_names`
|
|
48
|
+
|
|
49
|
+
**Required when — and only when — `joint_position` is in `robot_state_keys`**; a profile whose
|
|
50
|
+
robot is controlled purely in Cartesian space omits it. When present it must have exactly as
|
|
51
|
+
many entries as the last axis of the recorded `joint_position` array. The emptiness is caught at
|
|
52
|
+
profile load; the length mismatch at validation.
|
|
53
|
+
|
|
54
|
+
### `action_space`
|
|
55
|
+
|
|
56
|
+
Not a free choice. The rule (`is_valid_action_space`):
|
|
57
|
+
|
|
58
|
+
- **at least one** arm action from `joint_position`, `joint_velocity`, `cartesian_position`,
|
|
59
|
+
`cartesian_velocity`
|
|
60
|
+
- **at least one** gripper action from `gripper_position`, `gripper_velocity`, `gripper_binary`
|
|
61
|
+
- **at most one** base action from `base_velocity`, `base_position`
|
|
62
|
+
- no other keys
|
|
63
|
+
|
|
64
|
+
Declaring two arm actions or two gripper actions is legal — but every declared key must then be
|
|
65
|
+
recorded as a real array in every episode. A profile with `uses_mobile_base: true` must include a
|
|
66
|
+
base action. (The converse is not checked: a base action with `uses_mobile_base: false` passes.)
|
|
67
|
+
|
|
68
|
+
The dict passed to `record_step` must have keys equal to `action_space` **exactly** — not a
|
|
69
|
+
subset, not a superset.
|
|
70
|
+
|
|
71
|
+
## Conditionally required
|
|
72
|
+
|
|
73
|
+
| Question | YAML key | Rule |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Joint names for the robot state, in order? | `robot_state_joint_names` | **Required** whenever `joint_position` is in `robot_state_keys` — see above. Not one of the nine unconditional keys, so a purely Cartesian profile omits it entirely. |
|
|
76
|
+
| Joint names for arm actions? | `action_joint_names` | **Required** whenever `joint_position` or `joint_velocity` is in `action_space`. Same order as the action vector; its length is checked against the recorded DOF. |
|
|
77
|
+
| Orientation representation for cartesian actions? | `orientation_representation` | Needed whenever `cartesian_position` is in `action_space` and the policy does not already emit scalar-last quaternions. |
|
|
78
|
+
| Orientation representation for cartesian state? | `robot_state_orientation_representation` | Same, for `cartesian_position` in `robot_state_keys`. |
|
|
79
|
+
|
|
80
|
+
### Orientation representation values
|
|
81
|
+
|
|
82
|
+
`quat` (scalar-last, shape `(4,)`), `matrix` (`(3, 3)`), `rot6d` (first two columns of the
|
|
83
|
+
rotation matrix, flattened, `(6,)` — what openpi uses), `rotvec` (axis-angle, `(3,)`), or
|
|
84
|
+
`euler_<order>` where order is one of `xyz`, `zyx`, `xyx`, `XYZ`, `ZYX`, `XYX`. **Case is
|
|
85
|
+
meaningful**: lowercase orders are extrinsic rotations, uppercase intrinsic.
|
|
86
|
+
|
|
87
|
+
These strings are **not validated when the YAML is parsed**. A bad value is stored verbatim and
|
|
88
|
+
surfaces as a plain `ValueError` from `EpisodeRecorder.__init__` — at construction, not
|
|
89
|
+
mid-rollout, but not as a profile error either.
|
|
90
|
+
|
|
91
|
+
Conversion applies to `cartesian_position` only. `cartesian_velocity` is recorded exactly as
|
|
92
|
+
given, with no conversion and no shape check.
|
|
93
|
+
|
|
94
|
+
## Optional
|
|
95
|
+
|
|
96
|
+
Stored for reproducibility, never validated:
|
|
97
|
+
|
|
98
|
+
- `controller` — e.g. `OSC`, `joint_position`, `joint_velocity`
|
|
99
|
+
- `gains` — controller gains; see the skeleton for the expected nesting
|
|
100
|
+
- `intrinsic_calibration_matrix` / `extrinsic_calibration_matrix`, keyed by camera name. Both
|
|
101
|
+
the underscored and the spaced spelling (`intrinsic calibration matrix`) are read; the
|
|
102
|
+
underscored one is canonical.
|
|
103
|
+
|
|
104
|
+
Please ask explicitly if the user wants to provide the optional inputs!
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
Onboarding a user into `oopsie-data-tools`, in order. Ask the questions in each section before
|
|
4
|
+
writing anything.
|
|
5
|
+
|
|
6
|
+
## 1. Prerequisites
|
|
7
|
+
|
|
8
|
+
- Python 3.8 or newer. 3.8 is the supported floor and is tested in CI alongside 3.10 and 3.12,
|
|
9
|
+
but 3.8 itself has been end-of-life since October 2024 — prefer newer if the user has a choice.
|
|
10
|
+
- `uv` (preferred) or `pip`.
|
|
11
|
+
- A **lab ID** and **HuggingFace token** from the registration form,
|
|
12
|
+
<https://forms.gle/9arwZHAvRjvbozoT7>. Nothing works without these; if the user does not have
|
|
13
|
+
them, send them there first.
|
|
14
|
+
|
|
15
|
+
## 2. Installation
|
|
16
|
+
|
|
17
|
+
To use the toolkit:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv add oopsie-data-tools # or: pip install oopsie-data-tools
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To work from a checkout (contributing to the toolkit, or running the bundled examples):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/oopsie-data/oopsie-data-tools
|
|
27
|
+
cd oopsie-data-tools
|
|
28
|
+
uv sync # or: pip install -e .
|
|
29
|
+
uv sync --extra droid # only for examples/inference_examples/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`droid` is the only extra; everything the CLI does works off a bare install. In a `uv sync`
|
|
33
|
+
checkout, every command below needs a `uv run` prefix.
|
|
34
|
+
|
|
35
|
+
Confirm `oopsie-data --version` works before continuing.
|
|
36
|
+
|
|
37
|
+
## 3. Where configs live
|
|
38
|
+
|
|
39
|
+
The two kinds of config are looked up through **separate chains**, because they belong to
|
|
40
|
+
different things. In each chain, the first location that exists wins.
|
|
41
|
+
|
|
42
|
+
**Credentials** (`contributor_config.yaml`) belong to the user and are shared by every project:
|
|
43
|
+
|
|
44
|
+
1. `$OOPSIE_CONFIG_DIR` — explicit override
|
|
45
|
+
2. `~/.config/oopsie-data` (or `$XDG_CONFIG_HOME/oopsie-data`)
|
|
46
|
+
3. the repository's `configs/` directory — only when working from a clone
|
|
47
|
+
|
|
48
|
+
**Robot profiles** belong to the robot code that loads them, and are *never* looked up in the
|
|
49
|
+
user config directory:
|
|
50
|
+
|
|
51
|
+
1. `$OOPSIE_ROBOT_PROFILES_DIR` — explicit override
|
|
52
|
+
2. `./robot_profiles` or `./configs/robot_profiles`, relative to the working directory
|
|
53
|
+
3. the repository's `configs/robot_profiles` — only when working from a clone
|
|
54
|
+
|
|
55
|
+
So: let `oopsie-data init` write the contributor config (it defaults to `~/.config/oopsie-data`,
|
|
56
|
+
never the checkout, because the file holds a token and a token inside a git working tree can be
|
|
57
|
+
committed). Put the robot profile next to the user's robot code — a `robot_profiles/` directory
|
|
58
|
+
beside their eval script is the normal choice — and load it by explicit path.
|
|
59
|
+
|
|
60
|
+
To keep either somewhere custom, persist it in the shell rc file:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
echo 'export OOPSIE_CONFIG_DIR=/path/to/oopsie-config' >> ~/.bashrc # or ~/.zshrc
|
|
64
|
+
echo 'export OOPSIE_ROBOT_PROFILES_DIR=/path/to/profiles' >> ~/.bashrc
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--config-dir <dir>` overrides the credential location for one run. It is a flag on
|
|
68
|
+
`oopsie-data` itself, so it goes **before** the subcommand — `oopsie-data --config-dir <dir>
|
|
69
|
+
upload --path ...`. Placing it after the subcommand is an argparse error.
|
|
70
|
+
|
|
71
|
+
When unsure what is in effect, run `oopsie-data show-config`: it prints both chains, the
|
|
72
|
+
location that wins, and the lab id and token in use (`--show-token` prints the token unmasked).
|
|
73
|
+
`$HF_TOKEN` in the environment overrides the stored token.
|
|
74
|
+
|
|
75
|
+
Below, `<config-dir>` means the resolved credential location and `<profiles-dir>` the resolved
|
|
76
|
+
profile location.
|
|
77
|
+
|
|
78
|
+
## 4. Contributor config — `oopsie-data init`
|
|
79
|
+
|
|
80
|
+
Ask the user for:
|
|
81
|
+
|
|
82
|
+
1. **Their lab ID** — the exact string from registration. Capitalization matters; a wrong value
|
|
83
|
+
blocks access to the lab's HuggingFace repo.
|
|
84
|
+
2. **Their HuggingFace token.**
|
|
85
|
+
|
|
86
|
+
Then have them run:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
oopsie-data init
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
It asks which config directory to use, then for the lab id and token, rejects the `your_lab_id`
|
|
93
|
+
placeholder, and writes `<config-dir>/contributor_config.yaml` with mode 0600. `--lab-id`,
|
|
94
|
+
`--hf-token`, `--no-verify-token` and `--force` skip the corresponding prompts, so it also runs
|
|
95
|
+
unattended.
|
|
96
|
+
|
|
97
|
+
Do **not** write this file by hand. The token check is **advisory**: a token that fails to
|
|
98
|
+
verify is reported as a warning, saved anyway, and `init` still exits 0. Only the lab id is
|
|
99
|
+
actually rejected. You find out a token is wrong at `oopsie-data upload`.
|
|
100
|
+
|
|
101
|
+
`init` does not create robot profiles — see section 5.
|
|
102
|
+
|
|
103
|
+
## 5. Robot profile
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
oopsie-data new-profile --name <name> # writes <profiles-dir>/<name>.yaml
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then fill it in by hand, using `reference/robot-profile.md` for the questions to ask and the
|
|
110
|
+
legal values. Do not start from one of the bundled example profiles: they describe someone
|
|
111
|
+
else's robot, and an unnoticed leftover field is recorded into every episode's HDF5 attrs and
|
|
112
|
+
uploaded.
|
|
113
|
+
|
|
114
|
+
Check the result loads:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python -c "from oopsie_data_tools.utils.robot_profile.robot_profile import load_robot_profile; load_robot_profile('<path>')"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 6. Confirm the setup
|
|
121
|
+
|
|
122
|
+
`oopsie-data show-config` is what verifies a contributor config; loading the profile as above is
|
|
123
|
+
what verifies a profile. Real confirmation comes from `oopsie-data validate --path ./samples`
|
|
124
|
+
once episodes exist.
|
|
125
|
+
|
|
126
|
+
Running `pytest oopsie_data_tools/test/` checks the toolkit, not the user's setup — the suite
|
|
127
|
+
deliberately isolates `HOME`, `XDG_CONFIG_HOME`, both `OOPSIE_*` variables and the working
|
|
128
|
+
directory, so it cannot see the user's config at all.
|
|
129
|
+
|
|
130
|
+
## 7. Choose an annotation workflow
|
|
131
|
+
|
|
132
|
+
Ask which the user needs:
|
|
133
|
+
|
|
134
|
+
**A. In-the-loop** — annotate each episode right after it is collected. The annotation server
|
|
135
|
+
runs during robot operation; launch it from the robot script (`WebRolloutAnnotator`), or run
|
|
136
|
+
`oopsie-data annotate --with-rollouts`.
|
|
137
|
+
|
|
138
|
+
**B. Bulk collection** — collect everything first, annotate later:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
oopsie-data annotate --samples-dir ./samples --annotator-name <YOUR_NAME> --port 5001
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`--samples-dir` defaults to `./samples` and `--port` to 5001. Omitting `--annotator-name`
|
|
145
|
+
prompts for it, and errors out when stdin is not a terminal. `--no-browser` suppresses opening
|
|
146
|
+
a browser. The equivalent long form is `python -m
|
|
147
|
+
oopsie_data_tools.annotation_tool.annotator_server --samples-dir ./samples --annotator-name
|
|
148
|
+
<YOUR_NAME> --port 5001`.
|
|
149
|
+
|
|
150
|
+
## 8. Wire `EpisodeRecorder` into the robot script
|
|
151
|
+
|
|
152
|
+
Ask the user:
|
|
153
|
+
|
|
154
|
+
- Where is their robot control loop? (file path)
|
|
155
|
+
- What variable holds the observation dict? (needs `robot_state` and `image_observation` keys)
|
|
156
|
+
- What variable holds the action dict? (keys must equal `action_space` in the profile exactly)
|
|
157
|
+
- Where should episode HDF5 files and videos be saved? (`data_root_dir`)
|
|
158
|
+
- Who is running the evaluation? (`operator_name`, stamped into every episode)
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from oopsie_data_tools.annotation_tool.episode_recorder import EpisodeRecorder
|
|
162
|
+
from oopsie_data_tools.utils.robot_profile.robot_profile import load_robot_profile
|
|
163
|
+
|
|
164
|
+
profile = load_robot_profile("<profiles-dir>/<your_profile>.yaml")
|
|
165
|
+
recorder = EpisodeRecorder(
|
|
166
|
+
robot_profile=profile,
|
|
167
|
+
data_root_dir="./samples",
|
|
168
|
+
operator_name="<operator>",
|
|
169
|
+
# resume_session_name="20260101_120000", # optional: append to an existing session
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for _ in range(num_episodes):
|
|
173
|
+
recorder.reset_episode_recorder() # clears the buffers between episodes
|
|
174
|
+
|
|
175
|
+
# Inside the control loop:
|
|
176
|
+
recorder.record_step(observation=obs, action=action)
|
|
177
|
+
|
|
178
|
+
# After the rollout ends. `instruction` is required; `success` is optional
|
|
179
|
+
# (omit it to leave the episode for the web annotator — but note that an episode
|
|
180
|
+
# with no annotation at all fails `oopsie-data validate`, so it must be annotated
|
|
181
|
+
# before it can be uploaded).
|
|
182
|
+
recorder.finish_rollout(instruction="pick up the red block", success=success)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`record_step` does not mutate the dicts it is passed. `finish_rollout` validates *before*
|
|
186
|
+
writing anything, so a rejected episode leaves no MP4s and no HDF5 on disk.
|
|
187
|
+
|
|
188
|
+
The keys in the profile and the keys passed to `record_step` must agree exactly — see
|
|
189
|
+
`reference/format.md`.
|
|
190
|
+
|
|
191
|
+
## 9. Upload
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
oopsie-data upload --path ./samples
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This validates, then pushes to `OopsieData-Submissions/<lab_id>`. To check without publishing,
|
|
198
|
+
use `oopsie-data validate --path ./samples`, or `upload --path ./samples --skip-upload` for the
|
|
199
|
+
full pre-upload sequence. `--episode-id` restricts either command to one episode; `--log-path`
|
|
200
|
+
writes a report.
|
|
201
|
+
|
|
202
|
+
If upload refuses because a directory holds more than 10,000 files (a HuggingFace limit), add
|
|
203
|
+
`--with-restructure`:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
oopsie-data upload --path ./samples --with-restructure
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
That writes a restructured copy to `./samples_restructured` and uploads it. Every directory over
|
|
210
|
+
the limit — at any depth — is split into numbered subfolders of 500 episodes; the rest of the
|
|
211
|
+
tree is copied through unchanged, with video paths inside the copied HDF5 files rewritten. It
|
|
212
|
+
copies rather than moves, so you need room for a second copy, and the original is untouched
|
|
213
|
+
until the user deletes it. Run `oopsie-data restructure --source ./samples --output <dir>`
|
|
214
|
+
separately to put the copy elsewhere.
|
|
215
|
+
|
|
216
|
+
Afterwards, `oopsie-data submissions` confirms what landed in the lab's repo.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Errors you will actually see
|
|
4
|
+
|
|
5
|
+
**`lab_id` unset, blank (`lab_id:`), or still `your_lab_id`.** A `RuntimeError` pointing at the
|
|
6
|
+
registration form, raised from `EpisodeRecorder.__init__` and from `oopsie-data upload`.
|
|
7
|
+
Capitalization must match the value you were given exactly.
|
|
8
|
+
|
|
9
|
+
**Config edited in the wrong place.** Editing the clone's `configs/` while `$OOPSIE_CONFIG_DIR`
|
|
10
|
+
or `~/.config/oopsie-data` also exists — the earlier entry in the chain wins. The error message
|
|
11
|
+
names the file that was actually read; `oopsie-data show-config` shows the whole chain.
|
|
12
|
+
|
|
13
|
+
**Action dict keys not matching `action_space`.** Raised at `record_step`. The two must be equal
|
|
14
|
+
as sets, not merely overlapping.
|
|
15
|
+
|
|
16
|
+
**"missing robot state keys required by its action_space".** Raised at `load_robot_profile`. The
|
|
17
|
+
state must observe the space the action controls: add `joint_position` to `robot_state_keys` for a
|
|
18
|
+
`joint_position`/`joint_velocity` action space, or `cartesian_position` for a
|
|
19
|
+
`cartesian_position`/`cartesian_velocity` one. `gripper_position` is required either way.
|
|
20
|
+
|
|
21
|
+
**"robot_state_joint_names is required when joint_position is included in robot_state_keys".**
|
|
22
|
+
Also at `load_robot_profile`. Name every joint, in the order the `joint_position` array uses. The
|
|
23
|
+
key is needed *only* in that case — drop it from a purely Cartesian profile rather than leaving it
|
|
24
|
+
blank.
|
|
25
|
+
|
|
26
|
+
**`robot_state_joint_names` length ≠ `joint_position` DOF.** `EpisodeValidationError` inside
|
|
27
|
+
`finish_rollout`, before any file is written.
|
|
28
|
+
|
|
29
|
+
**`cartesian_position` recorded but `orientation_representation` unset.** The value is stored
|
|
30
|
+
unconverted and then rejected unless it already is `[x, y, z, qx, qy, qz, qw]` with a unit
|
|
31
|
+
quaternion. A representation that is set but does not match what the policy emits is reported by
|
|
32
|
+
width, e.g. `QUAT orientation expects 4 value(s), got 3`.
|
|
33
|
+
|
|
34
|
+
**Episode duration out of range.** `[1, 600]` seconds, computed as `trajectory_length /
|
|
35
|
+
control_freq` — not a step count. Short test rollouts trip this constantly.
|
|
36
|
+
|
|
37
|
+
**"Annotations dict is empty, must be provided for upload".** The episode has no
|
|
38
|
+
`episode_annotations` group. `validate` and `upload` both check annotations unconditionally, so a
|
|
39
|
+
freshly recorded episode fails until someone has annotated it — run `oopsie-data annotate` first.
|
|
40
|
+
This is expected between recording and annotation, not a malformed file.
|
|
41
|
+
|
|
42
|
+
**"taxonomy is not valid JSON" / "taxonomy must be a JSON object".** The `taxonomy` attr on an
|
|
43
|
+
annotator subgroup is stored as a JSON object string. Something wrote it by hand and got the
|
|
44
|
+
quoting wrong — write annotations through `write_episode_annotations` or the annotation tool
|
|
45
|
+
rather than setting the attr directly.
|
|
46
|
+
|
|
47
|
+
**"unrecognized outcome" / "outcome disagrees with success".** The `outcome` slug inside
|
|
48
|
+
`taxonomy` must be one of `success`, `success_suboptimal`, `success_side_effect`, `failure`,
|
|
49
|
+
and must match the `success` float (`failure` iff `success < 0.5`). Both are only reachable by
|
|
50
|
+
writing the attrs by hand; the writers derive one from the other. Taxonomy v1 files have no
|
|
51
|
+
`outcome` at all and are exempt.
|
|
52
|
+
|
|
53
|
+
**Undeclared keys.** `observations/robot_states contains N key(s) the robot profile does not
|
|
54
|
+
declare`. Add them to the profile or stop recording them; an extra diagnostic channel is not
|
|
55
|
+
allowed through.
|
|
56
|
+
|
|
57
|
+
**"Too many files in a directory" on upload.** HuggingFace caps a directory at 10,000 files.
|
|
58
|
+
`oopsie-data restructure --source ./samples` writes a split copy alongside the original
|
|
59
|
+
(non-destructive, rewrites video paths inside the copied HDF5 files), or `oopsie-data upload
|
|
60
|
+
--with-restructure` does it inline. Both need room for a second copy.
|
|
61
|
+
|
|
62
|
+
**Low-diversity warnings on upload.** A submission of near-identical tasks or annotations warns
|
|
63
|
+
by default and fails with `--strict-diversity`. It is a signal about dataset value, not a bug to
|
|
64
|
+
silence. The check runs even under `--skip-validate`.
|
|
65
|
+
|
|
66
|
+
**`--config-dir` rejected.** It is a flag on `oopsie-data` itself and must come *before* the
|
|
67
|
+
subcommand.
|
|
68
|
+
|
|
69
|
+
**`oopsie-data inspect --path ...` fails.** `inspect` takes a positional path.
|
|
70
|
+
|
|
71
|
+
**`--robot-profile` missing** when running an `examples/inference_examples/` script. It is
|
|
72
|
+
required and deliberately has no default. Those examples also need `uv sync --extra droid`.
|
|
73
|
+
|
|
74
|
+
Exit codes: 1 on a handled failure, 2 for a bare invocation or argparse error, 130 on Ctrl-C.
|
|
75
|
+
|
|
76
|
+
## Mistakes that pass validation silently
|
|
77
|
+
|
|
78
|
+
These cannot be caught by the toolkit. Ask the user to confirm each.
|
|
79
|
+
|
|
80
|
+
**Actions in delta rather than absolute coordinates.** Nothing detects this, and deltas cannot be
|
|
81
|
+
used downstream because the base offset is not recorded.
|
|
82
|
+
|
|
83
|
+
**Wrong quaternion component order.** Poses must be scalar-last, `(x, y, z, w)`. A scalar-first
|
|
84
|
+
pose only produces a `logger.warning` from a heuristic, never an error.
|
|
85
|
+
|
|
86
|
+
**An action chunk passed instead of a per-step action.** Only `cartesian_position` is
|
|
87
|
+
shape-checked, to `(7,)` or `(14,)`. For joint action spaces a `(T, chunk, dof)` array satisfies
|
|
88
|
+
both the DOF and trajectory-length checks and is recorded silently.
|
|
89
|
+
|
|
90
|
+
**Annotation *quality*.** The presence and shape of annotations *is* checked (see above), but
|
|
91
|
+
nothing judges what they say — whether a failure description actually describes what happened,
|
|
92
|
+
or whether the category fits, is unverifiable by the toolkit.
|
|
93
|
+
|
|
94
|
+
**`cartesian_velocity` of any shape.** It is recorded exactly as given, with no conversion and
|
|
95
|
+
no shape check.
|
|
96
|
+
|
|
97
|
+
**A base action declared with `uses_mobile_base: false`.** Only the other direction is checked.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test suite for oopsie_data_tools."""
|