Skip to content

HDF5Writer: support variable-length string columns#285

Open
qhua360 wants to merge 3 commits into
galilai-group:mainfrom
qhua360:hdf5writer-string-columns
Open

HDF5Writer: support variable-length string columns#285
qhua360 wants to merge 3 commits into
galilai-group:mainfrom
qhua360:hdf5writer-string-columns

Conversation

@qhua360

@qhua360 qhua360 commented Jul 6, 2026

Copy link
Copy Markdown

What

HDF5Writer infers each column's dtype from the first episode via np.asarray(vals[0]).dtype. For a text column that yields a numpy <U dtype, which h5py has no conversion path for — so any string column (e.g. a per-step lang_instr caption) raised on create_dataset. This was asymmetric with the reader: HDF5Dataset._load_slice already decodes string columns back to python str.

Change

Store text columns (python str/bytes, or numpy U/S/O dtype) as a per-step variable-length UTF-8 string via h5py.string_dtype():

  • _init_schema — string columns get shape=(0,), maxshape=(None,), dtype=h5py.string_dtype(), chunks=(1,); numeric columns unchanged.
  • write_episode — string columns are written as an object array (bytes decoded to str); numeric assignment unchanged.
  • _validate_episode_against_existing — skips the per-step-shape check for string columns (variable-length strings have no fixed feature shape).

This makes the reader/writer symmetric and lets convert() / write_episodes carry a caption column end to end.

Motivation

Building a captioned dataset (adding a lang_instr text column alongside pixels/action/state) previously required a hand-rolled h5py pass because HDF5Writer couldn't hold strings. With this change the writer handles it natively.

Test

Adds TestHDF5Writer::test_string_column_roundtrip: writes episodes with a lang_instr string column and asserts it stores as h5py.string_dtype() and decodes back to str via HDF5Dataset. Full test_format.py writer/convert suite passes (the 2 TestCollectFormat failures are pre-existing No module named 'pygame' from the uninstalled env extra, unrelated to this change).

🤖 Generated with Claude Code

qhua360 and others added 3 commits July 6, 2026 14:27
HDF5Writer inferred each column's dtype from the first episode via
np.asarray(vals[0]).dtype, which yields a numpy `<U` dtype for text
columns — h5py has no conversion path for that, so any string column
(e.g. a per-step `lang_instr` caption) raised on create_dataset. This
was asymmetric with the reader: HDF5Dataset._load_slice already decodes
string columns back to python str.

Store text columns (python str/bytes or numpy U/S/O dtype) as a per-step
variable-length UTF-8 string (h5py.string_dtype()):
- _init_schema: string columns get shape (0,)/maxshape (None,) with
  h5py.string_dtype(); numeric columns unchanged.
- write_episode: assign string columns as an object array (decoding bytes
  to str); numeric assignment unchanged.
- _validate_episode_against_existing: skip the per-step-shape check for
  string columns (variable-length strings have no fixed feature shape).

This lets convert()/write_episodes carry a caption column end to end. Add
a round-trip test writing a `lang_instr` string column and reading it
back via HDF5Dataset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The variable-length string list comprehension added in the previous
commit was formatted with a newer ruff; the CI-pinned ruff 0.12.8
(.pre-commit-config.yaml) wraps the ternary across lines, failing the
pre-commit / ruff-format check. Reformat to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lerobot 0.6.0 made its default install minimal, moving datasets/pyarrow/
torchcodec into an optional [dataset] extra (huggingface/lerobot#3362).
This repo requested plain `lerobot>=0.5.0`, so lerobot's former
`datasets>=4.0.0` floor disappeared. Combined with stable-pretraining's
unpinned `datasets` and the `env` extra's transitive constraints, uv
backtracks `datasets` to the ancient 1.1.1, which references the
`pyarrow.PyExtensionType` removed in pyarrow 24 -> the CI test failures
in tests/data/test_normalization.py. (uv.lock is gitignored, so CI
re-resolves each run and drifted onto 0.6.0 the moment it published.)

Opt into `lerobot[dataset]>=0.6.0` per lerobot's migration guidance,
restoring datasets 4.8.5 / pyarrow 24 across py3.10-3.12. The dataset
adapter API surface (import path, LeRobotDataset constructor kwargs,
.features/.meta.camera_keys/.meta.fps/.meta.info/.hf_dataset) is intact
under 0.6.0 -- all 6 adapter tests pass against the real lerobot/pusht
Hub dataset, so no adapter code change is needed. Also correct the
adapter's ImportError hint to name the [lerobot] extra that actually
provides the package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant