mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-21 17:01:53 +00:00
annotate: ruff lint + format pass
Quality-gate fixes after the main merge:
* UP037: drop redundant quotes from PlanConfig forward-ref annotations
(action_records / task_aug_axes) — safe under 'from __future__ import
annotations'.
* ruff format applied to config.py, executor.py, general_vqa.py,
plan_subtasks_memory.py, validator.py, lerobot_annotate.py.
No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,7 +116,7 @@ class PlanConfig:
|
|||||||
# that record back to canonical subtask text — reducing the VLM's
|
# that record back to canonical subtask text — reducing the VLM's
|
||||||
# "creative" surface to just the perception step. See
|
# "creative" surface to just the perception step. See
|
||||||
# ``ActionRecordsConfig`` for details. Off by default (back-compat).
|
# ``ActionRecordsConfig`` for details. Off by default (back-compat).
|
||||||
action_records: "ActionRecordsConfig" = field(default_factory=lambda: ActionRecordsConfig())
|
action_records: ActionRecordsConfig = field(default_factory=lambda: ActionRecordsConfig())
|
||||||
|
|
||||||
# Structured 5-axis augmentation taxonomy for the t=0 task variants
|
# Structured 5-axis augmentation taxonomy for the t=0 task variants
|
||||||
# (replaces the free-form ``n_task_rephrasings`` flow when enabled).
|
# (replaces the free-form ``n_task_rephrasings`` flow when enabled).
|
||||||
@@ -124,7 +124,7 @@ class PlanConfig:
|
|||||||
# free-form rephrasings, the VLM produces variants along named
|
# free-form rephrasings, the VLM produces variants along named
|
||||||
# axes (synonym / omit_arm / omit_orientation / omit_grasp_method /
|
# axes (synonym / omit_arm / omit_orientation / omit_grasp_method /
|
||||||
# combined). Off by default (back-compat).
|
# combined). Off by default (back-compat).
|
||||||
task_aug_axes: "TaskAugAxesConfig" = field(default_factory=lambda: TaskAugAxesConfig())
|
task_aug_axes: TaskAugAxesConfig = field(default_factory=lambda: TaskAugAxesConfig())
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -136,12 +136,12 @@ class ActionRecordsConfig:
|
|||||||
subtask to extract a typed record::
|
subtask to extract a typed record::
|
||||||
|
|
||||||
{
|
{
|
||||||
"verb": "pick" | "place" | "press" | ..., # closed vocabulary
|
"verb": "pick" | "place" | "press" | ..., # closed vocabulary
|
||||||
"object": "<canonical_object_name>",
|
"object": "<canonical_object_name>",
|
||||||
"arm": "left" | "right" | "both" | null,
|
"arm": "left" | "right" | "both" | null,
|
||||||
"grasp_type": "pinch" | "wrap" | "hook" | ... | null,
|
"grasp_type": "pinch" | "wrap" | "hook" | ... | null,
|
||||||
"destination": "<canonical_destination>" | null,
|
"destination": "<canonical_destination>" | null,
|
||||||
"mistake": "<short text>" | null,
|
"mistake": "<short text>" | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
The record is emitted as a separate row with ``style="action_record"``
|
The record is emitted as a separate row with ``style="action_record"``
|
||||||
@@ -176,16 +176,34 @@ class ActionRecordsConfig:
|
|||||||
# exactly one. Override per-dataset (e.g. ``["pick", "place", "open",
|
# exactly one. Override per-dataset (e.g. ``["pick", "place", "open",
|
||||||
# "close"]`` for door-only manipulation) for tighter constraint.
|
# "close"]`` for door-only manipulation) for tighter constraint.
|
||||||
verb_vocabulary: tuple[str, ...] = (
|
verb_vocabulary: tuple[str, ...] = (
|
||||||
"pick", "place", "push", "pull", "open", "close", "turn",
|
"pick",
|
||||||
"press", "lift", "insert", "pour", "move", "reach", "grasp",
|
"place",
|
||||||
"release", "wipe", "dump",
|
"push",
|
||||||
|
"pull",
|
||||||
|
"open",
|
||||||
|
"close",
|
||||||
|
"turn",
|
||||||
|
"press",
|
||||||
|
"lift",
|
||||||
|
"insert",
|
||||||
|
"pour",
|
||||||
|
"move",
|
||||||
|
"reach",
|
||||||
|
"grasp",
|
||||||
|
"release",
|
||||||
|
"wipe",
|
||||||
|
"dump",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Closed grasp-type vocabulary. ``null`` is always allowed (no
|
# Closed grasp-type vocabulary. ``null`` is always allowed (no
|
||||||
# contact / unclear). Adjust per-hardware (e.g. drop ``hook`` /
|
# contact / unclear). Adjust per-hardware (e.g. drop ``hook`` /
|
||||||
# ``key`` for parallel-jaw grippers).
|
# ``key`` for parallel-jaw grippers).
|
||||||
grasp_vocabulary: tuple[str, ...] = (
|
grasp_vocabulary: tuple[str, ...] = (
|
||||||
"pinch", "wrap", "hook", "key", "lateral",
|
"pinch",
|
||||||
|
"wrap",
|
||||||
|
"hook",
|
||||||
|
"key",
|
||||||
|
"lateral",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -238,9 +238,7 @@ class Executor:
|
|||||||
prompt path is reused.
|
prompt path is reused.
|
||||||
"""
|
"""
|
||||||
if not self.plan.enabled or not self.interjections.enabled:
|
if not self.plan.enabled or not self.interjections.enabled:
|
||||||
return PhaseResult(
|
return PhaseResult(name="plan_update", episodes_processed=0, episodes_skipped=len(records))
|
||||||
name="plan_update", episodes_processed=0, episodes_skipped=len(records)
|
|
||||||
)
|
|
||||||
processed = 0
|
processed = 0
|
||||||
for record in records:
|
for record in records:
|
||||||
staging = EpisodeStaging(staging_dir, record.episode_index)
|
staging = EpisodeStaging(staging_dir, record.episode_index)
|
||||||
|
|||||||
@@ -206,9 +206,7 @@ class GeneralVqaModule:
|
|||||||
episode_task=record.episode_task,
|
episode_task=record.episode_task,
|
||||||
question_type=question_type,
|
question_type=question_type,
|
||||||
)
|
)
|
||||||
images = self.frame_provider.frames_at(
|
images = self.frame_provider.frames_at(record, [frame_timestamp], camera_key=camera_key)
|
||||||
record, [frame_timestamp], camera_key=camera_key
|
|
||||||
)
|
|
||||||
content = [*to_image_blocks(images), {"type": "text", "text": prompt}]
|
content = [*to_image_blocks(images), {"type": "text", "text": prompt}]
|
||||||
return [{"role": "user", "content": content}]
|
return [{"role": "user", "content": content}]
|
||||||
|
|
||||||
|
|||||||
@@ -172,9 +172,7 @@ class PlanSubtasksMemoryModule:
|
|||||||
# "what's still left" at inference time.
|
# "what's still left" at inference time.
|
||||||
for span in subtask_spans:
|
for span in subtask_spans:
|
||||||
boundary_t = snap_to_frame(span["start"], record.frame_timestamps)
|
boundary_t = snap_to_frame(span["start"], record.frame_timestamps)
|
||||||
plan_text = self._generate_plan(
|
plan_text = self._generate_plan(record, subtask_spans, refresh_t=boundary_t, task=effective_task)
|
||||||
record, subtask_spans, refresh_t=boundary_t, task=effective_task
|
|
||||||
)
|
|
||||||
if plan_text is not None:
|
if plan_text is not None:
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
@@ -336,7 +334,9 @@ class PlanSubtasksMemoryModule:
|
|||||||
if not frames:
|
if not frames:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"action_record: no frames at span %.2f-%.2f for ep %s; skipping",
|
"action_record: no frames at span %.2f-%.2f for ep %s; skipping",
|
||||||
start_t, end_t, record.episode_index,
|
start_t,
|
||||||
|
end_t,
|
||||||
|
record.episode_index,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -811,12 +811,15 @@ class PlanSubtasksMemoryModule:
|
|||||||
import json # noqa: PLC0415
|
import json # noqa: PLC0415
|
||||||
|
|
||||||
subtasks_json = json.dumps(
|
subtasks_json = json.dumps(
|
||||||
{"subtasks": [{"text": s["text"], "start": round(s["start"], 3), "end": round(s["end"], 3)} for s in spans]},
|
{
|
||||||
|
"subtasks": [
|
||||||
|
{"text": s["text"], "start": round(s["start"], 3), "end": round(s["end"], 3)}
|
||||||
|
for s in spans
|
||||||
|
]
|
||||||
|
},
|
||||||
indent=2,
|
indent=2,
|
||||||
)
|
)
|
||||||
prompt = load_prompt("module_1_subtask_verify").format(
|
prompt = load_prompt("module_1_subtask_verify").format(episode_task=task, subtasks_json=subtasks_json)
|
||||||
episode_task=task, subtasks_json=subtasks_json
|
|
||||||
)
|
|
||||||
kept_raw = self._vlm_field(self._video_message(record, prompt, window=window), "subtasks")
|
kept_raw = self._vlm_field(self._video_message(record, prompt, window=window), "subtasks")
|
||||||
# Windowed verify: the video is sampled from the absolute window
|
# Windowed verify: the video is sampled from the absolute window
|
||||||
# ``[w0, w1]`` but the model perceives it as a clip starting at 0,
|
# ``[w0, w1]`` but the model perceives it as a clip starting at 0,
|
||||||
@@ -824,9 +827,7 @@ class PlanSubtasksMemoryModule:
|
|||||||
# Clamp to that relative range and skip the absolute frame-snap
|
# Clamp to that relative range and skip the absolute frame-snap
|
||||||
# dedupe (done once later on the merged absolute-time set).
|
# dedupe (done once later on the merged absolute-time set).
|
||||||
clamp = (0.0, float(window[1] - window[0])) if window is not None else None
|
clamp = (0.0, float(window[1] - window[0])) if window is not None else None
|
||||||
kept = self._clean_spans(
|
kept = self._clean_spans(kept_raw, record, bounds=clamp, dedupe=window is None)
|
||||||
kept_raw, record, bounds=clamp, dedupe=window is None
|
|
||||||
)
|
|
||||||
if not kept:
|
if not kept:
|
||||||
logger.info(
|
logger.info(
|
||||||
"episode %d: verify pass returned nothing — keeping the %d "
|
"episode %d: verify pass returned nothing — keeping the %d "
|
||||||
@@ -927,17 +928,13 @@ class PlanSubtasksMemoryModule:
|
|||||||
if not subtask_spans:
|
if not subtask_spans:
|
||||||
return None
|
return None
|
||||||
remaining = [
|
remaining = [
|
||||||
s
|
s for s in subtask_spans if refresh_t is None or float(s.get("start", 0.0)) >= float(refresh_t)
|
||||||
for s in subtask_spans
|
|
||||||
if refresh_t is None or float(s.get("start", 0.0)) >= float(refresh_t)
|
|
||||||
]
|
]
|
||||||
if not remaining:
|
if not remaining:
|
||||||
# Past the last subtask boundary on a late refresh — nothing
|
# Past the last subtask boundary on a late refresh — nothing
|
||||||
# left to plan; emit None so the caller skips the row.
|
# left to plan; emit None so the caller skips the row.
|
||||||
return None
|
return None
|
||||||
return "\n".join(
|
return "\n".join(f"{i}. {span.get('text', '').strip()}" for i, span in enumerate(remaining, start=1))
|
||||||
f"{i}. {span.get('text', '').strip()}" for i, span in enumerate(remaining, start=1)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _generate_memory(
|
def _generate_memory(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -137,9 +137,7 @@ class StagingValidator:
|
|||||||
persistent: list[dict[str, Any]] = []
|
persistent: list[dict[str, Any]] = []
|
||||||
for row in all_rows:
|
for row in all_rows:
|
||||||
self._check_column_routing(row, report, record.episode_index)
|
self._check_column_routing(row, report, record.episode_index)
|
||||||
self._check_camera_field(
|
self._check_camera_field(row, report, record.episode_index, self.dataset_camera_keys)
|
||||||
row, report, record.episode_index, self.dataset_camera_keys
|
|
||||||
)
|
|
||||||
if column_for_style(row.get("style")) == LANGUAGE_PERSISTENT:
|
if column_for_style(row.get("style")) == LANGUAGE_PERSISTENT:
|
||||||
persistent.append(row)
|
persistent.append(row)
|
||||||
else:
|
else:
|
||||||
@@ -166,15 +164,9 @@ class StagingValidator:
|
|||||||
try:
|
try:
|
||||||
validate_camera_field(style, camera)
|
validate_camera_field(style, camera)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
report.add_error(
|
report.add_error(f"ep={episode_index} module={row.get('_module')}: {exc}")
|
||||||
f"ep={episode_index} module={row.get('_module')}: {exc}"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
if (
|
if is_view_dependent_style(style) and dataset_camera_keys and camera not in dataset_camera_keys:
|
||||||
is_view_dependent_style(style)
|
|
||||||
and dataset_camera_keys
|
|
||||||
and camera not in dataset_camera_keys
|
|
||||||
):
|
|
||||||
report.add_error(
|
report.add_error(
|
||||||
f"ep={episode_index} module={row.get('_module')}: camera {camera!r} on style "
|
f"ep={episode_index} module={row.get('_module')}: camera {camera!r} on style "
|
||||||
f"{style!r} is not one of the dataset's video keys {sorted(dataset_camera_keys)!r}"
|
f"{style!r} is not one of the dataset's video keys {sorted(dataset_camera_keys)!r}"
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ def annotate(cfg: AnnotationPipelineConfig) -> None:
|
|||||||
logger.info("annotate: root=%s", root)
|
logger.info("annotate: root=%s", root)
|
||||||
|
|
||||||
vlm = make_vlm_client(cfg.vlm)
|
vlm = make_vlm_client(cfg.vlm)
|
||||||
frame_provider = make_frame_provider(
|
frame_provider = make_frame_provider(root, camera_key=cfg.vlm.camera_key, video_backend=cfg.video_backend)
|
||||||
root, camera_key=cfg.vlm.camera_key, video_backend=cfg.video_backend
|
|
||||||
)
|
|
||||||
# Surface the resolved cameras up front so a silent vqa-module no-op
|
# Surface the resolved cameras up front so a silent vqa-module no-op
|
||||||
# is obvious in job output rather than discovered post-hoc by counting
|
# is obvious in job output rather than discovered post-hoc by counting
|
||||||
# parquet rows.
|
# parquet rows.
|
||||||
@@ -168,7 +166,10 @@ def _push_to_hub(root: Path, cfg: AnnotationPipelineConfig) -> None:
|
|||||||
if isinstance(ds_version, str) and ds_version.startswith("v"):
|
if isinstance(ds_version, str) and ds_version.startswith("v"):
|
||||||
version_tag = ds_version
|
version_tag = ds_version
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
print(f"[lerobot-annotate] could not read codebase_version from info.json ({exc}); falling back to {version_tag}", flush=True)
|
print(
|
||||||
|
f"[lerobot-annotate] could not read codebase_version from info.json ({exc}); falling back to {version_tag}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
revision = getattr(commit_info, "oid", None)
|
revision = getattr(commit_info, "oid", None)
|
||||||
tag_kwargs = {
|
tag_kwargs = {
|
||||||
"repo_id": repo_id,
|
"repo_id": repo_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user