mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-15 16:49:55 +00:00
fix(smolvla2): instantiate CameraConfig subclasses from JSON dicts
``--robot.cameras`` parses the JSON into ``dict[str, dict]``, but ``RobotConfig`` expects ``dict[str, CameraConfig]`` — each inner value must be the actual ``CameraConfig`` subclass instance for the chosen backend (e.g. ``OpenCVCameraConfig``). Passing raw dicts blew up in ``RobotConfig.__post_init__`` with ``AttributeError: 'dict' object has no attribute 'width'`` when it iterated cameras and tried to read attributes. Look up the right subclass per-camera by its ``"type"`` field via ``CameraConfig.get_choice_class(...)`` (mirroring the lazy-import dance we already do for ``RobotConfig``: eagerly walk ``lerobot.cameras``'s submodules so the registry is populated before lookup). Construct an instance with the rest of the dict's fields. On an unknown camera type, raise a clean ``ValueError`` listing the available choices. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -494,11 +494,52 @@ def _build_robot(
|
||||
kwargs["id"] = robot_id
|
||||
if robot_cameras_json:
|
||||
try:
|
||||
kwargs["cameras"] = json.loads(robot_cameras_json)
|
||||
cameras_raw = json.loads(robot_cameras_json)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
f"--robot.cameras must be a JSON object, got {robot_cameras_json!r}: {exc}"
|
||||
) from exc
|
||||
# ``RobotConfig`` expects ``cameras: dict[str, CameraConfig]`` —
|
||||
# each inner value must be an actual ``CameraConfig`` subclass
|
||||
# instance, not a raw dict. Look up the matching subclass via
|
||||
# ``CameraConfig.get_choice_class(<type>)`` (registered by
|
||||
# ``@CameraConfig.register_subclass`` decorators on each camera
|
||||
# backend's config) and instantiate it. Mirror the lazy-import
|
||||
# pattern from above so the registry is populated.
|
||||
import lerobot.cameras as _cameras_pkg # noqa: PLC0415
|
||||
from lerobot.cameras import CameraConfig # noqa: PLC0415
|
||||
|
||||
for _modinfo in pkgutil.iter_modules(_cameras_pkg.__path__):
|
||||
if _modinfo.name.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
importlib.import_module(f"lerobot.cameras.{_modinfo.name}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.debug("could not import lerobot.cameras.%s: %s", _modinfo.name, exc)
|
||||
|
||||
cameras: dict[str, Any] = {}
|
||||
for cam_name, cam_dict in cameras_raw.items():
|
||||
if not isinstance(cam_dict, dict):
|
||||
raise ValueError(
|
||||
f"camera {cam_name!r} value must be a dict, got {cam_dict!r}"
|
||||
)
|
||||
cam_dict = dict(cam_dict) # don't mutate caller's parsed JSON
|
||||
cam_type = cam_dict.pop("type", None)
|
||||
if cam_type is None:
|
||||
raise ValueError(
|
||||
f"camera {cam_name!r} is missing a 'type' field "
|
||||
f"(e.g. 'opencv', 'intelrealsense')"
|
||||
)
|
||||
try:
|
||||
cam_cls = CameraConfig.get_choice_class(cam_type)
|
||||
except KeyError as exc:
|
||||
available = sorted(CameraConfig._choice_registry.keys())
|
||||
raise ValueError(
|
||||
f"camera {cam_name!r}: unknown type {cam_type!r}. "
|
||||
f"Available choices: {available}"
|
||||
) from exc
|
||||
cameras[cam_name] = cam_cls(**cam_dict)
|
||||
kwargs["cameras"] = cameras
|
||||
if robot_max_relative_target:
|
||||
# Accept either a bare float (uniform cap) or a JSON object
|
||||
# (per-motor cap). Matches ``RobotConfig.max_relative_target``'s
|
||||
|
||||
Reference in New Issue
Block a user