twanghcmut's picture
download
raw
8.4 kB
"""Tests for fpgm.datagen.episode_spec -- no GPU, no network, no real DROID data.
:class:`~fpgm.datagen.episode_spec.EpisodeMetadata` is a plain dataclass here (never
built via ``from_flows_h5``), so these tests exercise
:class:`~fpgm.datagen.episode_spec.EpisodeSpecResolver` purely against in-memory
metadata plus a :class:`~fpgm.config_datagen.DatagenProfile` pointed at a stub mesh
file this module creates itself.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from fpgm.config_datagen import DatagenProfile, RoleConfig, RolesConfig, SceneAssetsConfig
from fpgm.datagen.episode_spec import (
EpisodeMetadata,
EpisodeSpec,
EpisodeSpecResolver,
ObjectRole,
ObjectSpec,
ShapeRepresentation,
TaskNotParseableError,
)
from fpgm.types import ConfigError
_SCENE_ID = "8756300955"
_REAL_TASK = "put brick in drawer shelf and close drawer"
_UNPARSEABLE_TASK = "Do anything you like that takes multiple steps to complete."
def _stub_mesh(tmp_path: Path) -> Path:
path = tmp_path / "manipulated.glb"
path.write_bytes(b"stub-not-a-real-glb") # resolver only checks existence, never loads it
return path
def _profile(tmp_path: Path, *, with_parent_bridging: bool = False) -> DatagenProfile:
profile = DatagenProfile()
mesh_path = str(_stub_mesh(tmp_path))
profile.scene_assets = SceneAssetsConfig(meshes={_SCENE_ID: {"manipulated": mesh_path}})
if with_parent_bridging:
profile.roles = RolesConfig(
manipulated=RoleConfig(
representation="mesh", plausible_scale_range=[0.03, 0.5], allow_scale=True,
parent_role="fixture",
),
fixture=RoleConfig(
representation="observed_surface", plausible_scale_range=[0.5, 2.0],
allow_scale=False,
),
)
return profile
def _metadata(task: str) -> EpisodeMetadata:
return EpisodeMetadata(
uuid="test-uuid", scene_id=_SCENE_ID, task=task,
camera_serials={"ext1": "22008760", "ext2": "22008761"},
)
class TestResolverDerivesRoles:
def test_real_instruction_derives_brick_and_drawer_with_correct_roles(
self, tmp_path: Path
) -> None:
profile = _profile(tmp_path)
spec = EpisodeSpecResolver(profile).resolve(_metadata(_REAL_TASK), camera_role="ext1")
assert set(spec.labels) == {"brick", "drawer"}
brick = spec.object("brick")
assert brick.role is ObjectRole.MANIPULATED
assert brick.representation is ShapeRepresentation.MESH
assert brick.mesh_path is not None and brick.mesh_path.exists()
assert brick.plausible_scale_range == (0.03, 0.5)
assert brick.allow_scale is True
drawer = spec.object("drawer")
assert drawer.role is ObjectRole.FIXTURE
assert drawer.representation is ShapeRepresentation.OBSERVED_SURFACE
assert drawer.mesh_path is None
assert drawer.plausible_scale_range == (0.5, 2.0)
assert drawer.allow_scale is False
def test_camera_role_and_serial_resolve_from_metadata(self, tmp_path: Path) -> None:
profile = _profile(tmp_path)
spec = EpisodeSpecResolver(profile).resolve(_metadata(_REAL_TASK), camera_role="ext2")
assert spec.camera_role == "ext2"
assert spec.camera_serial == "22008761"
def test_parent_bridging_names_the_fixture_as_the_manipulated_objects_parent(
self, tmp_path: Path
) -> None:
profile = _profile(tmp_path, with_parent_bridging=True)
spec = EpisodeSpecResolver(profile).resolve(_metadata(_REAL_TASK), camera_role="ext1")
assert spec.parents == {"brick": "drawer"}
assert spec.object("brick").parent_label == "drawer"
assert spec.object("drawer").parent_label is None
class TestTaskNotParseableError:
def test_freeform_instruction_raises_instead_of_fabricating_a_spec(
self, tmp_path: Path
) -> None:
profile = _profile(tmp_path)
with pytest.raises(TaskNotParseableError):
EpisodeSpecResolver(profile).resolve(_metadata(_UNPARSEABLE_TASK), camera_role="ext1")
def test_meta_token_only_instruction_also_raises(self, tmp_path: Path) -> None:
"""A short phrase that is nonetheless pure meta-vocabulary must also refuse."""
profile = _profile(tmp_path)
metadata = _metadata("complete the suggested task")
with pytest.raises(TaskNotParseableError):
EpisodeSpecResolver(profile).resolve(metadata, camera_role="ext1")
class TestWithStaticSpans:
def _resolved_spec(self, tmp_path: Path) -> EpisodeSpec:
profile = _profile(tmp_path)
return EpisodeSpecResolver(profile).resolve(_metadata(_REAL_TASK), camera_role="ext1")
def test_attaches_measured_spans_by_label(self, tmp_path: Path) -> None:
spec = self._resolved_spec(tmp_path)
updated = spec.with_static_spans({"brick": (0, 30), "drawer": None})
assert updated.object("brick").static_span == (0, 30)
assert updated.object("drawer").static_span is None
# original spec is untouched -- EpisodeSpec is frozen/immutable.
assert spec.object("brick").static_span is None
def test_rejects_unknown_labels(self, tmp_path: Path) -> None:
spec = self._resolved_spec(tmp_path)
with pytest.raises(ConfigError):
spec.with_static_spans({"not_a_real_object": (0, 5)})
def test_partial_update_leaves_other_labels_alone(self, tmp_path: Path) -> None:
spec = self._resolved_spec(tmp_path)
updated = spec.with_static_spans({"brick": (0, 12)})
assert updated.object("brick").static_span == (0, 12)
assert updated.object("drawer").static_span is None
class TestObjectSpecValidation:
def test_mesh_representation_requires_a_mesh_path(self) -> None:
with pytest.raises(ConfigError):
ObjectSpec(
label="brick", role=ObjectRole.MANIPULATED, prompt_candidates=("brick",),
representation=ShapeRepresentation.MESH, mesh_path=None,
plausible_scale_range=None, allow_scale=True,
)
def test_observed_surface_representation_does_not_require_a_mesh_path(self) -> None:
spec = ObjectSpec(
label="drawer", role=ObjectRole.FIXTURE, prompt_candidates=("drawer",),
representation=ShapeRepresentation.OBSERVED_SURFACE, mesh_path=None,
plausible_scale_range=None, allow_scale=False,
)
assert spec.mesh_path is None
def test_empty_prompt_candidates_rejected(self) -> None:
with pytest.raises(ConfigError):
ObjectSpec(
label="brick", role=ObjectRole.MANIPULATED, prompt_candidates=(),
representation=ShapeRepresentation.OBSERVED_SURFACE, mesh_path=None,
plausible_scale_range=None, allow_scale=True,
)
def test_empty_label_rejected(self) -> None:
with pytest.raises(ConfigError):
ObjectSpec(
label="", role=ObjectRole.MANIPULATED, prompt_candidates=("brick",),
representation=ShapeRepresentation.OBSERVED_SURFACE, mesh_path=None,
plausible_scale_range=None, allow_scale=True,
)
class TestEpisodeSpecDuplicateLabels:
def test_duplicate_labels_rejected(self) -> None:
obj = ObjectSpec(
label="brick", role=ObjectRole.MANIPULATED, prompt_candidates=("brick",),
representation=ShapeRepresentation.OBSERVED_SURFACE, mesh_path=None,
plausible_scale_range=None, allow_scale=True,
)
with pytest.raises(ConfigError):
EpisodeSpec(
uuid="u", camera_role="ext1", camera_serial="1", scene_id="s", task="t",
objects=(obj, obj),
)
def test_unknown_parent_label_rejected(self) -> None:
obj = ObjectSpec(
label="brick", role=ObjectRole.MANIPULATED, prompt_candidates=("brick",),
representation=ShapeRepresentation.OBSERVED_SURFACE, mesh_path=None,
plausible_scale_range=None, allow_scale=True, parent_label="does_not_exist",
)
with pytest.raises(ConfigError):
EpisodeSpec(
uuid="u", camera_role="ext1", camera_serial="1", scene_id="s", task="t",
objects=(obj,),
)

Xet Storage Details

Size:
8.4 kB
·
Xet hash:
034bba5ee2064071bdf47dd8a4f999c2445aaf33bd73b323d1830537741fb5f7

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.