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.
Files changed (74) hide show
  1. oopsie_data_tools/__init__.py +7 -0
  2. oopsie_data_tools/annotation_tool/__init__.py +5 -0
  3. oopsie_data_tools/annotation_tool/annotation_schema.py +266 -0
  4. oopsie_data_tools/annotation_tool/annotator_server.py +850 -0
  5. oopsie_data_tools/annotation_tool/episode_recorder.py +648 -0
  6. oopsie_data_tools/annotation_tool/rollout_annotator.py +333 -0
  7. oopsie_data_tools/annotation_tool/ui/annotator.html +2240 -0
  8. oopsie_data_tools/cli.py +894 -0
  9. oopsie_data_tools/init_wizard.py +329 -0
  10. oopsie_data_tools/skill/SKILL.md +98 -0
  11. oopsie_data_tools/skill/reference/conversion.md +257 -0
  12. oopsie_data_tools/skill/reference/format.md +112 -0
  13. oopsie_data_tools/skill/reference/robot-profile.md +104 -0
  14. oopsie_data_tools/skill/reference/setup.md +216 -0
  15. oopsie_data_tools/skill/reference/troubleshooting.md +97 -0
  16. oopsie_data_tools/test/__init__.py +1 -0
  17. oopsie_data_tools/test/conftest.py +174 -0
  18. oopsie_data_tools/test/fixtures/__init__.py +0 -0
  19. oopsie_data_tools/test/fixtures/make_invalid.py +633 -0
  20. oopsie_data_tools/test/fixtures/make_valid.py +406 -0
  21. oopsie_data_tools/test/test_annotation_completeness.py +154 -0
  22. oopsie_data_tools/test/test_annotation_schema.py +190 -0
  23. oopsie_data_tools/test/test_annotator_server.py +240 -0
  24. oopsie_data_tools/test/test_annotator_server_guards.py +146 -0
  25. oopsie_data_tools/test/test_bulk_inference_end_to_end.py +112 -0
  26. oopsie_data_tools/test/test_cli_config.py +111 -0
  27. oopsie_data_tools/test/test_cli_tools.py +438 -0
  28. oopsie_data_tools/test/test_contributor_config.py +38 -0
  29. oopsie_data_tools/test/test_conversion_utils_annotations.py +75 -0
  30. oopsie_data_tools/test/test_credentials_location.py +106 -0
  31. oopsie_data_tools/test/test_diversity.py +33 -0
  32. oopsie_data_tools/test/test_episode_recorder.py +335 -0
  33. oopsie_data_tools/test/test_episode_video_paths.py +173 -0
  34. oopsie_data_tools/test/test_hf_upload.py +234 -0
  35. oopsie_data_tools/test/test_init_wizard.py +193 -0
  36. oopsie_data_tools/test/test_install_skill.py +120 -0
  37. oopsie_data_tools/test/test_migrate_taxonomy_v2.py +255 -0
  38. oopsie_data_tools/test/test_new_profile.py +84 -0
  39. oopsie_data_tools/test/test_paths.py +137 -0
  40. oopsie_data_tools/test/test_python38_compat.py +79 -0
  41. oopsie_data_tools/test/test_record_step_purity.py +127 -0
  42. oopsie_data_tools/test/test_robot_setup.py +244 -0
  43. oopsie_data_tools/test/test_rollout_annotator.py +134 -0
  44. oopsie_data_tools/test/test_rotation_utils.py +126 -0
  45. oopsie_data_tools/test/test_validate.py +494 -0
  46. oopsie_data_tools/utils/__init__.py +1 -0
  47. oopsie_data_tools/utils/claude_skill.py +96 -0
  48. oopsie_data_tools/utils/contributor_config.py +91 -0
  49. oopsie_data_tools/utils/conversion_utils.py +220 -0
  50. oopsie_data_tools/utils/h5.py +60 -0
  51. oopsie_data_tools/utils/h5_inspect.py +167 -0
  52. oopsie_data_tools/utils/hf_limits.py +22 -0
  53. oopsie_data_tools/utils/hf_upload.py +235 -0
  54. oopsie_data_tools/utils/log.py +35 -0
  55. oopsie_data_tools/utils/migrate_taxonomy_v2.py +215 -0
  56. oopsie_data_tools/utils/paths.py +162 -0
  57. oopsie_data_tools/utils/restructure.py +402 -0
  58. oopsie_data_tools/utils/robot_profile/__init__.py +1 -0
  59. oopsie_data_tools/utils/robot_profile/robot_profile.py +240 -0
  60. oopsie_data_tools/utils/robot_profile/rotation_utils.py +119 -0
  61. oopsie_data_tools/utils/robot_profile/template.py +108 -0
  62. oopsie_data_tools/utils/validation/__init__.py +5 -0
  63. oopsie_data_tools/utils/validation/annotation_completeness.py +67 -0
  64. oopsie_data_tools/utils/validation/diversity.py +93 -0
  65. oopsie_data_tools/utils/validation/episode_data.py +54 -0
  66. oopsie_data_tools/utils/validation/episode_loader.py +201 -0
  67. oopsie_data_tools/utils/validation/episode_validator.py +315 -0
  68. oopsie_data_tools/utils/validation/errors.py +17 -0
  69. oopsie_data_tools/utils/validation/validation_utils.py +88 -0
  70. oopsie_data_tools-0.2.0.dist-info/METADATA +131 -0
  71. oopsie_data_tools-0.2.0.dist-info/RECORD +74 -0
  72. oopsie_data_tools-0.2.0.dist-info/WHEEL +4 -0
  73. oopsie_data_tools-0.2.0.dist-info/entry_points.txt +2 -0
  74. 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."""