mirror of
https://github.com/huggingface/lerobot.git
synced 2026-08-01 05:59:44 +00:00
feat(dataset tools): support task replacement mappings in modify_tasks (#4244)
* Support task replacement mappings in modify_tasks Part of #2326. Signed-off-by: 陈伟 <woshei0a0a0a@qq.com> * feat(task modification precedence): Improving task modification precedence so that all modes can be used in a single run. Adapting tests accordinginly. * chore(fromat): formatting code * docs(docstrings): updating docstrings * docs(update): updating docs with the task modification features --------- Signed-off-by: 陈伟 <woshei0a0a0a@qq.com> Co-authored-by: vvezre <93599357+vvezre@users.noreply.github.com>
This commit is contained in:
@@ -1125,9 +1125,61 @@ def test_modify_tasks_default_with_overrides(sample_dataset):
|
||||
assert ep_data["tasks"][0] == default_task
|
||||
|
||||
|
||||
def test_modify_tasks_replacements(sample_dataset):
|
||||
"""Test replacing task strings based on their current values."""
|
||||
modified_dataset = modify_tasks(
|
||||
sample_dataset,
|
||||
task_replacements={
|
||||
"task_0": "Pick the cube",
|
||||
"task_1": "Place the cube",
|
||||
},
|
||||
)
|
||||
|
||||
assert len(modified_dataset.meta.tasks) == 2
|
||||
assert "Pick the cube" in modified_dataset.meta.tasks.index
|
||||
assert "Place the cube" in modified_dataset.meta.tasks.index
|
||||
|
||||
for ep_idx in range(5):
|
||||
expected_task = "Pick the cube" if ep_idx % 2 == 0 else "Place the cube"
|
||||
assert modified_dataset.meta.episodes[ep_idx]["tasks"][0] == expected_task
|
||||
|
||||
|
||||
def test_modify_tasks_replacements_with_episode_overrides(sample_dataset):
|
||||
"""Test that explicit episode overrides take precedence over replacements."""
|
||||
modified_dataset = modify_tasks(
|
||||
sample_dataset,
|
||||
task_replacements={
|
||||
"task_0": "Pick the cube",
|
||||
"task_1": "Place the cube",
|
||||
},
|
||||
episode_tasks={1: "Inspect the cube"},
|
||||
)
|
||||
|
||||
assert modified_dataset.meta.episodes[0]["tasks"][0] == "Pick the cube"
|
||||
assert modified_dataset.meta.episodes[1]["tasks"][0] == "Inspect the cube"
|
||||
assert modified_dataset.meta.episodes[3]["tasks"][0] == "Place the cube"
|
||||
assert len(modified_dataset.meta.tasks) == 3
|
||||
|
||||
|
||||
def test_modify_tasks_default_task_and_replacements(sample_dataset):
|
||||
"""Test that new_task acts as the default for episodes not matched by task_replacements."""
|
||||
modified_dataset = modify_tasks(
|
||||
sample_dataset,
|
||||
new_task="Default task",
|
||||
task_replacements={"task_0": "Pick the cube"},
|
||||
)
|
||||
|
||||
for ep_idx in range(5):
|
||||
expected_task = "Pick the cube" if ep_idx % 2 == 0 else "Default task"
|
||||
assert modified_dataset.meta.episodes[ep_idx]["tasks"][0] == expected_task
|
||||
assert len(modified_dataset.meta.tasks) == 2
|
||||
|
||||
|
||||
def test_modify_tasks_no_task_specified(sample_dataset):
|
||||
"""Test error when no task is specified."""
|
||||
with pytest.raises(ValueError, match="Must specify at least one of new_task or episode_tasks"):
|
||||
with pytest.raises(
|
||||
ValueError, match="Must specify at least one of new_task, episode_tasks, or task_replacements"
|
||||
):
|
||||
modify_tasks(sample_dataset)
|
||||
|
||||
|
||||
@@ -1137,6 +1189,12 @@ def test_modify_tasks_invalid_episode_indices(sample_dataset):
|
||||
modify_tasks(sample_dataset, episode_tasks={10: "Task", 20: "Task"})
|
||||
|
||||
|
||||
def test_modify_tasks_invalid_task_replacements(sample_dataset):
|
||||
"""Test error when task replacements refer to unknown task strings."""
|
||||
with pytest.raises(ValueError, match="Task replacements reference unknown tasks"):
|
||||
modify_tasks(sample_dataset, task_replacements={"missing_task": "New task"})
|
||||
|
||||
|
||||
def test_modify_tasks_updates_info_json(sample_dataset):
|
||||
"""Test that total_tasks is updated in info.json."""
|
||||
episode_tasks = {0: "Task A", 1: "Task B", 2: "Task C", 3: "Task A", 4: "Task B"}
|
||||
|
||||
@@ -105,6 +105,23 @@ class TestOperationTypeParsing:
|
||||
resolved_name = OperationConfig.get_choice_name(type(cfg.operation))
|
||||
assert resolved_name == type_name
|
||||
|
||||
def test_modify_tasks_replacements_args_parse(self):
|
||||
cfg = parse_cfg(
|
||||
[
|
||||
"--repo_id",
|
||||
"test/repo",
|
||||
"--operation.type",
|
||||
"modify_tasks",
|
||||
"--operation.task_replacements",
|
||||
'{"task_0": "pick cube", "task_1": "place cube"}',
|
||||
]
|
||||
)
|
||||
assert isinstance(cfg.operation, ModifyTasksConfig)
|
||||
assert cfg.operation.task_replacements == {
|
||||
"task_0": "pick cube",
|
||||
"task_1": "place cube",
|
||||
}
|
||||
|
||||
|
||||
class TestDepthEncoderParsing:
|
||||
"""Test that the depth encoder is exposed and parsed for video operations."""
|
||||
|
||||
Reference in New Issue
Block a user