From b5970dd36066bce5a398a77d8b2d4c154c89b14f Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Mon, 1 Sep 2025 13:59:47 +0900 Subject: [PATCH 1/3] feat: add support of loading lidarseg schema as optional Signed-off-by: ktro2828 --- t4_devkit/schema/name.py | 3 +++ t4_devkit/schema/tables/__init__.py | 1 + t4_devkit/schema/tables/lidarseg.py | 24 ++++++++++++++++++++++++ t4_devkit/tier4.py | 2 ++ tests/schema/test_schema_name.py | 3 +++ 5 files changed, 33 insertions(+) create mode 100644 t4_devkit/schema/tables/lidarseg.py diff --git a/t4_devkit/schema/name.py b/t4_devkit/schema/name.py index e310a8a..fd4c971 100644 --- a/t4_devkit/schema/name.py +++ b/t4_devkit/schema/name.py @@ -23,6 +23,7 @@ class SchemaName(str, Enum): SCENE: A scene is a specific long sequence of consecutive frames extracted from a log. SENSOR: A specific sensor type. VISIBILITY: The visibility of instance is the fraction of annotation visible in all images. + LIDARSEG (optional): The annotation of 3D point cloud segmentation. OBJECT_ANN (optional): The annotation of a foreground object in an image. SURFACE_ANN (optional): The annotation of a background object in an image. KEYPOINT (optional): The annotation of pose keypoints of an object in an image. @@ -42,6 +43,7 @@ class SchemaName(str, Enum): VISIBILITY = "visibility" SENSOR = "sensor" SCENE = "scene" + LIDARSEG = "lidarseg" # optional OBJECT_ANN = "object_ann" # optional SURFACE_ANN = "surface_ann" # optional KEYPOINT = "keypoint" # optional @@ -63,6 +65,7 @@ def is_optional(self) -> bool: Return True if this schema is optional. """ return self in ( + SchemaName.LIDARSEG, SchemaName.OBJECT_ANN, SchemaName.SURFACE_ANN, SchemaName.KEYPOINT, diff --git a/t4_devkit/schema/tables/__init__.py b/t4_devkit/schema/tables/__init__.py index ac2ff02..6ef7a95 100644 --- a/t4_devkit/schema/tables/__init__.py +++ b/t4_devkit/schema/tables/__init__.py @@ -6,6 +6,7 @@ from .ego_pose import * # noqa from .instance import * # noqa from .keypoint import * # noqa +from .lidarseg import * # noqa from .log import * # noqa from .map import * # noqa from .object_ann import * # noqa diff --git a/t4_devkit/schema/tables/lidarseg.py b/t4_devkit/schema/tables/lidarseg.py new file mode 100644 index 0000000..1f34e36 --- /dev/null +++ b/t4_devkit/schema/tables/lidarseg.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from attrs import define, field, validators + +from ..name import SchemaName +from .base import SchemaBase +from .registry import SCHEMAS + +__all__ = ["LidarSeg"] + + +@define(slots=False) +@SCHEMAS.register(SchemaName.LIDARSEG) +class LidarSeg(SchemaBase): + """A dataclass to represent lidar point cloud segmentation data. + + Attributes: + token (str): Unique record identifier. + filename (str): The filename of the lidar point cloud segmentation data. + sample_data_token (str): The token of the sample data. + """ + + filename: str = field(validator=validators.instance_of(str)) + sample_data_token: str = field(validator=validators.instance_of(str)) diff --git a/t4_devkit/tier4.py b/t4_devkit/tier4.py index 7b5a93c..bd16a6f 100644 --- a/t4_devkit/tier4.py +++ b/t4_devkit/tier4.py @@ -27,6 +27,7 @@ EgoPose, Instance, Keypoint, + LidarSeg, Log, Map, ObjectAnn, @@ -183,6 +184,7 @@ def __init__( self.ego_pose: list[EgoPose] = load_table(self.annotation_dir, SchemaName.EGO_POSE) self.instance: list[Instance] = load_table(self.annotation_dir, SchemaName.INSTANCE) self.keypoint: list[Keypoint] = load_table(self.annotation_dir, SchemaName.KEYPOINT) + self.lidarseg: list[LidarSeg] = load_table(self.annotation_dir, SchemaName.LIDARSEG) self.log: list[Log] = load_table(self.annotation_dir, SchemaName.LOG) self.map: list[Map] = load_table(self.annotation_dir, SchemaName.MAP) self.object_ann: list[ObjectAnn] = load_table(self.annotation_dir, SchemaName.OBJECT_ANN) diff --git a/tests/schema/test_schema_name.py b/tests/schema/test_schema_name.py index 357cd94..56acb1b 100644 --- a/tests/schema/test_schema_name.py +++ b/tests/schema/test_schema_name.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from t4_devkit.schema import SchemaName @@ -19,6 +21,7 @@ def test_schema_name() -> None: "visibility": False, "sensor": False, "scene": False, + "lidarseg": True, "object_ann": True, "surface_ann": True, "keypoint": True, From 80aa27e7240224b70591613c580f2c14ef699059 Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Mon, 1 Sep 2025 14:20:49 +0900 Subject: [PATCH 2/3] test: add unit tests for lidarseg Signed-off-by: ktro2828 --- tests/schema/conftest.py | 19 ++++++++++++++++ tests/schema/tables/test_lidarseg_table.py | 26 ++++++++++++++++++++++ tests/schema/test_schema_builder.py | 7 ++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/schema/tables/test_lidarseg_table.py diff --git a/tests/schema/conftest.py b/tests/schema/conftest.py index e9f49b6..7511c47 100644 --- a/tests/schema/conftest.py +++ b/tests/schema/conftest.py @@ -308,6 +308,25 @@ def visibility_json(visibility_dict) -> Generator[str, Any, None]: yield f.name +# === LidarSeg === +@pytest.fixture(scope="session") +def lidarseg_dict() -> dict: + """Return a dummy lidarseg record as dictionary.""" + return { + "token": "4f79b0e9ae192558c76bd31a84bfe125", + "filename": "lidarseg/0.bin", + "sample_data_token": "df2bee5733d8607e49bf792fac3014a3", + } + + +@pytest.fixture(scope="session") +def lidarseg_json(lidarseg_dict) -> Generator[str, Any, None]: + """Return a file path of dummy lidarseg record.""" + with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: + save_json([lidarseg_dict], f.name) + yield f.name + + # === ObjectAnn === @pytest.fixture(scope="session") def object_ann_dict() -> dict: diff --git a/tests/schema/tables/test_lidarseg_table.py b/tests/schema/tables/test_lidarseg_table.py new file mode 100644 index 0000000..f3f27f2 --- /dev/null +++ b/tests/schema/tables/test_lidarseg_table.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from t4_devkit.common.serialize import serialize_dataclass, serialize_dataclasses +from t4_devkit.schema import LidarSeg + + +def test_lidarseg_json(lidarseg_json) -> None: + """Test loading lidarseg from a json file.""" + schemas = LidarSeg.from_json(lidarseg_json) + serialized = serialize_dataclasses(schemas) + assert isinstance(serialized, list) + + +def test_lidarseg(lidarseg_dict) -> None: + """Test loading lidarseg from a dictionary.""" + schema = LidarSeg.from_dict(lidarseg_dict) + serialized = serialize_dataclass(schema) + assert serialized == lidarseg_dict + + +def test_new_lidarseg(lidarseg_dict) -> None: + """Test generating lidarseg with a new token.""" + without_token = {k: v for k, v in lidarseg_dict.items() if k != "token"} + ret = LidarSeg.new(without_token) + # check the new token is not the same with the token in input data + assert ret.token != lidarseg_dict["token"] diff --git a/tests/schema/test_schema_builder.py b/tests/schema/test_schema_builder.py index 78859a3..6969fa5 100644 --- a/tests/schema/test_schema_builder.py +++ b/tests/schema/test_schema_builder.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from t4_devkit.schema import SchemaName, build_schema @@ -26,6 +28,11 @@ def test_build_instance(instance_json) -> None: _ = build_schema(SchemaName.INSTANCE, instance_json) +def test_build_lidarseg(lidarseg_json) -> None: + """Test building lidarseg.""" + _ = build_schema(SchemaName.LIDARSEG, lidarseg_json) + + def test_build_log(log_json) -> None: """Test building log.""" _ = build_schema(SchemaName.LOG, log_json) From ceb9f8f1303ed34dfac2747655e21c8a7ee85dec Mon Sep 17 00:00:00 2001 From: ktro2828 Date: Mon, 1 Sep 2025 15:13:17 +0900 Subject: [PATCH 3/3] docs: update document Signed-off-by: ktro2828 --- docs/schema/table.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/schema/table.md b/docs/schema/table.md index d01e4a4..0b7a272 100644 --- a/docs/schema/table.md +++ b/docs/schema/table.md @@ -285,6 +285,20 @@ visibility { The following tables are optional, and skipped loading by `Tier4` class if not exists. +### LidarSeg + +- Filename: `lidarseg.json` + +Mapping between LiDAR segmentation annotations and `SampleData` corresponding to the LiDAR point cloud associated with a keyframe. + +```json +lidarseg { + "token": -- Unique record identifier. + "filename": -- Filename of the LiDAR segmentation annotation labels that is an array of unit8. + "sample_data_token": -- Foreign key to the `SampleData` table associated with the sample data. +} +``` + ### ObjectAnn - Filename: `object_ann.json`