From 5b7494c52bf2f0c2b753b42294ac0be439129f03 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 08:51:39 +0100 Subject: [PATCH 001/190] refactor: remove stats module --- raillabel/__init__.py | 2 +- raillabel/stats/__init__.py | 5 -- raillabel/stats/generate_timespan.py | 36 ---------- .../stats/test_generate_timespan.py | 71 ------------------- 4 files changed, 1 insertion(+), 113 deletions(-) delete mode 100644 raillabel/stats/__init__.py delete mode 100644 raillabel/stats/generate_timespan.py delete mode 100644 tests/test_raillabel/stats/test_generate_timespan.py diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 40dfb0a..7b613eb 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -3,7 +3,7 @@ """Devkit for working with recorded and annotated train ride data from DB.""" from importlib import metadata -from . import _util, format, stats +from . import _util, format from .exceptions import * from .filter.filter import filter from .format import Scene diff --git a/raillabel/stats/__init__.py b/raillabel/stats/__init__.py deleted file mode 100644 index d6fb143..0000000 --- a/raillabel/stats/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Statsmodule provides statistics.""" - -from .generate_timespan import generate_timespan diff --git a/raillabel/stats/generate_timespan.py b/raillabel/stats/generate_timespan.py deleted file mode 100644 index 44853d7..0000000 --- a/raillabel/stats/generate_timespan.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from decimal import Decimal - -from ..format import Scene - - -def generate_timespan(scene: Scene) -> t.Tuple[t.Optional[Decimal], t.Optional[Decimal]]: - """Return start and end timestamp of the scene. - - Parameters - ---------- - scene: raillabel.format.Scene - Scene the timespan should be based off. - - Returns - ------- - decimal.Decimal or None - Start timestamp of the scene. Is None if the scene has no frames. - decimal.Decimal or None - End timestamp of the scene. Is None if the scene has no frames. - """ - start_timestamp = None - end_timestamp = None - - for frame in scene.frames.values(): - - if start_timestamp == None or frame.timestamp < start_timestamp: - start_timestamp = frame.timestamp - - if end_timestamp == None or frame.timestamp > end_timestamp: - end_timestamp = frame.timestamp - - return (start_timestamp, end_timestamp) diff --git a/tests/test_raillabel/stats/test_generate_timespan.py b/tests/test_raillabel/stats/test_generate_timespan.py deleted file mode 100644 index a9e0236..0000000 --- a/tests/test_raillabel/stats/test_generate_timespan.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - -import raillabel - -# == Fixtures ============================ - -@pytest.fixture -def metadata() -> raillabel.format.Metadata: - return raillabel.format.Metadata(schema_version="1.0.0") - -# == Tests ============================ - -def test_simple_timespan(metadata): - scene = raillabel.Scene( - metadata=metadata, - frames={ - 0: raillabel.format.Frame( - uid=0, - timestamp=100 - ), - 1: raillabel.format.Frame( - uid=1, - timestamp=105 - ), - 2: raillabel.format.Frame( - uid=0, - timestamp=110 - ), - } - ) - - assert raillabel.stats.generate_timespan(scene) == (100, 110) - -def test_unordered_timspan(metadata): - scene = raillabel.Scene( - metadata=metadata, - frames={ - 0: raillabel.format.Frame( - uid=0, - timestamp=110 - ), - 1: raillabel.format.Frame( - uid=1, - timestamp=100 - ), - } - ) - - assert raillabel.stats.generate_timespan(scene) == (100, 110) - -def test_empty_timespan(metadata): - scene = raillabel.Scene( - metadata=metadata, - frames={} - ) - - assert raillabel.stats.generate_timespan(scene) == (None, None) - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 2b6f51838f08a3b297c6a287ca929c1625effed2 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 09:14:18 +0100 Subject: [PATCH 002/190] refactor: remove understand ai modules --- raillabel/format/understand_ai/__init__.py | 17 - raillabel/format/understand_ai/_annotation.py | 73 -- .../format/understand_ai/_translation.py | 93 -- .../format/understand_ai/bounding_box_2d.py | 79 -- .../format/understand_ai/bounding_box_3d.py | 84 -- .../format/understand_ai/coordinate_system.py | 151 ---- raillabel/format/understand_ai/frame.py | 198 ----- raillabel/format/understand_ai/metadata.py | 91 -- raillabel/format/understand_ai/point_3d.py | 44 - raillabel/format/understand_ai/polygon_2d.py | 83 -- raillabel/format/understand_ai/polyline_2d.py | 83 -- raillabel/format/understand_ai/quaternion.py | 48 - raillabel/format/understand_ai/scene.py | 134 --- .../format/understand_ai/segmentation_3d.py | 66 -- .../format/understand_ai/sensor_reference.py | 63 -- raillabel/format/understand_ai/size_3d.py | 44 - .../loader_classes/loader_understand_ai.py | 84 -- .../load_/loader_classes/translation.json | 197 ----- .../loader_classes/translation.json.license | 2 - .../understand_ai_real_life.json | 1 - .../understand_ai_real_life.json.license | 2 - .../understand_ai_t4_short.json | 823 ------------------ .../understand_ai_t4_short.json.license | 2 - .../format/understand_ai/conftest.py | 49 -- .../understand_ai/test_uai_attributes.py | 51 -- .../understand_ai/test_uai_bounding_box_2d.py | 131 --- .../understand_ai/test_uai_bounding_box_3d.py | 132 --- .../test_uai_coordinate_system.py | 341 -------- .../format/understand_ai/test_uai_frame.py | 214 ----- .../format/understand_ai/test_uai_metadata.py | 101 --- .../format/understand_ai/test_uai_point_3d.py | 54 -- .../understand_ai/test_uai_polygon_2d.py | 146 ---- .../understand_ai/test_uai_polyline_2d.py | 146 ---- .../understand_ai/test_uai_quaternion.py | 58 -- .../format/understand_ai/test_uai_scene.py | 184 ---- .../understand_ai/test_uai_segmentation_3d.py | 110 --- .../test_uai_sensor_reference.py | 107 --- .../format/understand_ai/test_uai_size_3d.py | 54 -- .../test_loader_understand_ai.py | 119 --- tests/test_raillabel/load_/test_load.py | 6 - .../schemas/test_understand_ai_t4_schema.py | 32 - 41 files changed, 4497 deletions(-) delete mode 100644 raillabel/format/understand_ai/__init__.py delete mode 100644 raillabel/format/understand_ai/_annotation.py delete mode 100644 raillabel/format/understand_ai/_translation.py delete mode 100644 raillabel/format/understand_ai/bounding_box_2d.py delete mode 100644 raillabel/format/understand_ai/bounding_box_3d.py delete mode 100644 raillabel/format/understand_ai/coordinate_system.py delete mode 100644 raillabel/format/understand_ai/frame.py delete mode 100644 raillabel/format/understand_ai/metadata.py delete mode 100644 raillabel/format/understand_ai/point_3d.py delete mode 100644 raillabel/format/understand_ai/polygon_2d.py delete mode 100644 raillabel/format/understand_ai/polyline_2d.py delete mode 100644 raillabel/format/understand_ai/quaternion.py delete mode 100644 raillabel/format/understand_ai/scene.py delete mode 100644 raillabel/format/understand_ai/segmentation_3d.py delete mode 100644 raillabel/format/understand_ai/sensor_reference.py delete mode 100644 raillabel/format/understand_ai/size_3d.py delete mode 100644 raillabel/load_/loader_classes/loader_understand_ai.py delete mode 100644 raillabel/load_/loader_classes/translation.json delete mode 100644 raillabel/load_/loader_classes/translation.json.license delete mode 100644 tests/__test_assets__/understand_ai_real_life.json delete mode 100644 tests/__test_assets__/understand_ai_real_life.json.license delete mode 100644 tests/__test_assets__/understand_ai_t4_short.json delete mode 100644 tests/__test_assets__/understand_ai_t4_short.json.license delete mode 100644 tests/test_raillabel/format/understand_ai/conftest.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_attributes.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_bounding_box_2d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_bounding_box_3d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_coordinate_system.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_frame.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_metadata.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_point_3d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_polygon_2d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_polyline_2d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_quaternion.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_scene.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_segmentation_3d.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_sensor_reference.py delete mode 100644 tests/test_raillabel/format/understand_ai/test_uai_size_3d.py delete mode 100644 tests/test_raillabel/load_/loader_classes/test_loader_understand_ai.py delete mode 100644 tests/test_raillabel/validate/schemas/test_understand_ai_t4_schema.py diff --git a/raillabel/format/understand_ai/__init__.py b/raillabel/format/understand_ai/__init__.py deleted file mode 100644 index 52adc36..0000000 --- a/raillabel/format/understand_ai/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Module containing all relevant understand.ai format classes.""" - -from .bounding_box_2d import BoundingBox2d -from .bounding_box_3d import BoundingBox3d -from .coordinate_system import CoordinateSystem -from .frame import Frame -from .metadata import Metadata -from .point_3d import Point3d -from .polygon_2d import Polygon2d -from .polyline_2d import Polyline2d -from .quaternion import Quaternion -from .scene import Scene -from .segmentation_3d import Segmentation3d -from .sensor_reference import SensorReference -from .size_3d import Size3d diff --git a/raillabel/format/understand_ai/_annotation.py b/raillabel/format/understand_ai/_annotation.py deleted file mode 100644 index 257043f..0000000 --- a/raillabel/format/understand_ai/_annotation.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from abc import ABC, abstractmethod, abstractproperty -from dataclasses import dataclass -from uuid import UUID - -from ..._util._attribute_type import AttributeType -from ._translation import translate_class_id, translate_sensor_id -from .sensor_reference import SensorReference - - -@dataclass -class _Annotation(ABC): - - id: UUID - object_id: UUID - class_name: str - attributes: dict - sensor: SensorReference - - @property - @abstractproperty - def OPENLABEL_ID(self) -> t.List[str]: - raise NotImplementedError - - @classmethod - @abstractmethod - def fromdict(cls, data_dict: t.Dict) -> t.Type["_Annotation"]: - raise NotImplementedError - - def to_raillabel(self) -> t.Tuple[dict, str, str, dict]: - """Convert to a raillabel compatible dict. - - Returns - ------- - annotation: dict - Dictionary valid for the raillabel schema. - object_id: str - Friendly identifier of the object this annotation belongs to. - class_name: str - Friendly identifier of the class the annotated object belongs to. - sensor_reference: dict - Dictionary of the sensor reference. - """ - - return ( - { - "name": str(self.id), - "val": self._val_to_raillabel(), - "coordinate_system": translate_sensor_id(self.sensor.type), - "attributes": self._attributes_to_raillabel(), - }, - str(self.object_id), - translate_class_id(self.class_name), - self.sensor.to_raillabel()[1], - ) - - def _attributes_to_raillabel(self) -> dict: - - attributes = {} - - for attr_name, attr_value in self.attributes.items(): - - attr_type = AttributeType.from_value(type(attr_value)).value - - if attr_type not in attributes: - attributes[attr_type] = [] - - attributes[attr_type].append({"name": attr_name, "val": attr_value}) - - return attributes diff --git a/raillabel/format/understand_ai/_translation.py b/raillabel/format/understand_ai/_translation.py deleted file mode 100644 index 79b56fb..0000000 --- a/raillabel/format/understand_ai/_translation.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import json -from pathlib import Path - - -def translate_sensor_id(original_sensor_id: str) -> str: - """Translate deprecated sensor ids to the correct ones. - - Parameters - ---------- - original_sensor_id : str - Original id of the sensor. - - Returns - ------- - str - Translated id or original_sensor_id, if no translation could be found. - """ - return TRANSLATION["streams"].get(original_sensor_id, original_sensor_id) - - -def translate_class_id(original_class_id: str) -> str: - """Translate deprecated class ids to the correct ones. - - Parameters - ---------- - original_class_id : str - Original id of the class. - - Returns - ------- - str - Translated id or original_class_id, if no translation could be found. - """ - return TRANSLATION["classes"].get(original_class_id, original_class_id) - - -def fetch_sensor_type(sensor_id: str) -> str: - """Fetch sensor type from translation file. - - Parameters - ---------- - sensor_id : str - Id of the sensor. - - Returns - ------- - str - Sensor type or 'other' if sensor_id not found in translation.json. - """ - return TRANSLATION["stream_types"].get(sensor_id, "other") - - -def fetch_sensor_resolutions(sensor_id: str) -> dict: - """Fetch sensor resolution from translation file. - - Parameters - ---------- - sensor_id : str - Id of the sensor. - - Returns - ------- - dict - Dictionary containing the resolution information. Key 'x' contains the width in pixels, - key 'y' contains the height in pixels. If the sensor is a radar, 'resolution_px_per_m' is - also included. - """ - return TRANSLATION["stream_resolutions"].get( - sensor_id, {"x": None, "y": None, "resolution_px_per_m": None} - ) - - -def _load_translation(): - """Load the translation file when the module is imported. - - This prevents it from beeing loaded for every annotation. - """ - - global TRANSLATION - - translatiion_path = ( - Path(__file__).parent.parent.parent / "load_" / "loader_classes" / "translation.json" - ) - with translatiion_path.open() as translation_file: - TRANSLATION = json.load(translation_file) - - -TRANSLATION = {} - -_load_translation() diff --git a/raillabel/format/understand_ai/bounding_box_2d.py b/raillabel/format/understand_ai/bounding_box_2d.py deleted file mode 100644 index 2e86cfd..0000000 --- a/raillabel/format/understand_ai/bounding_box_2d.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from uuid import UUID - -from ._annotation import _Annotation -from .sensor_reference import SensorReference - - -@dataclass -class BoundingBox2d(_Annotation): - """A 2d bounding box. - - Parameters - ---------- - id: uuid.UUID - Unique identifier of the annotation. - object_id: uuid.UUID - Unique identifier of the object this annotation refers to. Used for tracking. - class_name: str - Name of the class this annotation belongs to. - attributes: dict[str, str or list] - Key value pairs of attributes with the keys beeing the friendly identifier of the - attribute and the value beeing the attribute value. - sensor: raillabel.format.understand_ai.SensorReference - Information about the sensor this annotation is labeled in. - x_min: float - Left corner of the bounding box in pixels. - y_min: float - Top corner of the bounding box in pixels. - x_max: float - Right corner of the bounding box in pixels. - y_max: float - Bottom corner of the bounding box in pixels. - """ - - x_min: float - y_min: float - x_max: float - y_max: float - - OPENLABEL_ID = "bbox" - - @classmethod - def fromdict(cls, data_dict: t.Dict) -> "BoundingBox2d": - """Generate a BoundingBox2d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - BoundingBox2d - Converted 2d bounding box. - """ - - return BoundingBox2d( - id=UUID(data_dict["id"]), - object_id=UUID(data_dict["objectId"]), - class_name=data_dict["className"], - x_min=data_dict["geometry"]["xMin"], - y_min=data_dict["geometry"]["yMin"], - x_max=data_dict["geometry"]["xMax"], - y_max=data_dict["geometry"]["yMax"], - attributes=data_dict["attributes"], - sensor=SensorReference.fromdict(data_dict["sensor"]), - ) - - def _val_to_raillabel(self) -> list: - return [ - (self.x_max + self.x_min) / 2, - (self.y_max + self.y_min) / 2, - abs(self.x_max - self.x_min), - abs(self.y_max - self.y_min), - ] diff --git a/raillabel/format/understand_ai/bounding_box_3d.py b/raillabel/format/understand_ai/bounding_box_3d.py deleted file mode 100644 index 9377695..0000000 --- a/raillabel/format/understand_ai/bounding_box_3d.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from uuid import UUID - -from ._annotation import _Annotation -from .point_3d import Point3d -from .quaternion import Quaternion -from .sensor_reference import SensorReference -from .size_3d import Size3d - - -@dataclass -class BoundingBox3d(_Annotation): - """A 3d bounding box. - - Parameters - ---------- - id: uuid.UUID - Unique identifier of the annotation. - object_id: uuid.UUID - Unique identifier of the object this annotation refers to. Used for tracking. - class_name: str - Name of the class this annotation belongs to. - attributes: dict[str, str or list] - Key value pairs of attributes with the keys beeing the friendly identifier of the - attribute and the value beeing the attribute value. - sensor: raillabel.format.understand_ai.SensorReference - Information about the sensor this annotation is labeled in. - center: raillabel.format.understand_ai.Point3d - Center position of the bounding box. - size: raillabel.format.understand_ai.Size3d - 3d size of the bounding box. - quaternion: raillabel.format.understand_ai.Quaternion - Rotation quaternion of the bounding box. - """ - - center: Point3d - size: Size3d - quaternion: Quaternion - - OPENLABEL_ID = "cuboid" - - @classmethod - def fromdict(cls, data_dict: t.Dict) -> "BoundingBox3d": - """Generate a BoundingBox3d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - BoundingBox3d - Converted 3d bounding box. - """ - - return BoundingBox3d( - id=UUID(data_dict["id"]), - object_id=UUID(data_dict["objectId"]), - class_name=data_dict["className"], - center=Point3d.fromdict(data_dict["geometry"]["center"]), - size=Size3d.fromdict(data_dict["geometry"]["size"]), - quaternion=Quaternion.fromdict(data_dict["geometry"]["quaternion"]), - attributes=data_dict["attributes"], - sensor=SensorReference.fromdict(data_dict["sensor"]), - ) - - def _val_to_raillabel(self) -> list: - return [ - float(self.center.x), - float(self.center.y), - float(self.center.z), - float(self.quaternion.x), - float(self.quaternion.y), - float(self.quaternion.z), - float(self.quaternion.w), - float(self.size.width), - float(self.size.length), - float(self.size.height), - ] diff --git a/raillabel/format/understand_ai/coordinate_system.py b/raillabel/format/understand_ai/coordinate_system.py deleted file mode 100644 index afe28c4..0000000 --- a/raillabel/format/understand_ai/coordinate_system.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass - -from ._translation import fetch_sensor_resolutions, fetch_sensor_type, translate_sensor_id - - -@dataclass -class CoordinateSystem: - """Global information for a sensor regarding calibration. - - Parameters - ---------- - uid: str - Friendly name of the sensor as well as its identifier. Must be unique - topic: str - Rostopic of the sensor. - frame_id: str - Name of the directory containing the files from this sensor. - position: list of float - 3D translation with regards to the origin. - rotation_quaternion: list of float - Rotation quaternion with regards to the origin. - rotation_matrix: list of float - Rotation matrix with regards to the origin. - angle_axis_rotation: list of float - Angle axis rotation with regards to the origin. - homogeneous_transform: list of float, optional - Homogeneous transformation matrix with regards to the origin. Default is None. - measured_position: list of float, optional - camera_matrix: list of float, optional - Camera matrix of the sensor. Only applies to sensors of type camera. Default is None. - dist_coeffs: list of float, optional - Distortion coefficients of the sensor. Only applies to sensors of type camera. Default is - None. - """ - - uid: str - topic: str - frame_id: str - position: t.List[float] - rotation_quaternion: t.List[float] - rotation_matrix: t.List[float] - angle_axis_rotation: t.List[float] - homogeneous_transform: t.Optional[t.List[float]] = None - measured_position: t.Optional[t.List[float]] = None - camera_matrix: t.Optional[t.List[float]] = None - dist_coeffs: t.Optional[t.List[float]] = None - - @property - def translated_uid(self) -> str: - """Return uid translated to raillabel.""" - return translate_sensor_id(self.uid) - - @classmethod - def fromdict(cls, data_dict: dict) -> "CoordinateSystem": - """Generate a CoordinateSystem from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data. - - Returns - ------- - coordinate_system: CoordinateSystem - Converted coordinate_system. - """ - - return CoordinateSystem( - uid=data_dict["coordinate_system_id"], - topic=data_dict["topic"], - frame_id=data_dict["frame_id"], - position=data_dict["position"], - rotation_quaternion=data_dict["rotation_quaternion"], - rotation_matrix=data_dict["rotation_matrix"], - angle_axis_rotation=data_dict["angle_axis_rotation"], - homogeneous_transform=data_dict.get("homogeneous_transform"), - measured_position=data_dict.get("measured_position"), - camera_matrix=data_dict.get("camera_matrix"), - dist_coeffs=data_dict.get("dist_coeffs"), - ) - - def to_raillabel(self) -> t.Tuple[dict, dict]: - """Convert to a raillabel compatible dict. - - Returns - ------- - coordinate_system_dict: dict - Dictionary of the raillabel coordinate system. - stream_dict: dict - Dictionary of the raillabel stream. - """ - - stream_dict = { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": { - "translation": self.position, - "quaternion": self.rotation_quaternion, - }, - } - - coordinate_system_dict = { - "type": fetch_sensor_type(self.translated_uid), - "uri": self.topic, - "stream_properties": self._stream_properties_to_raillabel( - fetch_sensor_type(self.translated_uid) - ), - } - - if coordinate_system_dict["stream_properties"] is None: - del coordinate_system_dict["stream_properties"] - - return stream_dict, coordinate_system_dict - - def _stream_properties_to_raillabel(self, type: str) -> t.Optional[dict]: - - if type == "camera": - return { - "intrinsics_pinhole": { - "camera_matrix": self._convert_camera_matrix(self.camera_matrix[:]), - "distortion_coeffs": self.dist_coeffs, - "width_px": fetch_sensor_resolutions(self.translated_uid)["x"], - "height_px": fetch_sensor_resolutions(self.translated_uid)["y"], - } - } - - elif type == "radar": - return { - "intrinsics_radar": { - "resolution_px_per_m": fetch_sensor_resolutions(self.translated_uid)[ - "resolution_px_per_m" - ], - "width_px": fetch_sensor_resolutions(self.translated_uid)["x"], - "height_px": fetch_sensor_resolutions(self.translated_uid)["y"], - } - } - - else: - return None - - def _convert_camera_matrix(self, camera_matrix: list) -> list: - - camera_matrix.insert(9, 0) - camera_matrix.insert(6, 0) - camera_matrix.insert(3, 0) - - return camera_matrix diff --git a/raillabel/format/understand_ai/frame.py b/raillabel/format/understand_ai/frame.py deleted file mode 100644 index 7c8ed35..0000000 --- a/raillabel/format/understand_ai/frame.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -import uuid -from dataclasses import dataclass -from decimal import Decimal - -from ..._util._warning import _warning -from ._annotation import _Annotation -from ._translation import translate_class_id, translate_sensor_id -from .bounding_box_2d import BoundingBox2d -from .bounding_box_3d import BoundingBox3d -from .polygon_2d import Polygon2d -from .polyline_2d import Polyline2d -from .segmentation_3d import Segmentation3d - - -@dataclass -class Frame: - """A container of dynamic, timewise, information. - - Parameters - ---------- - id: int - Numerical identifier of the frame. Must be unique within the scene. - timestamp: decimal.Decimal - Timestamp containing the Unix epoch time of the frame with up to nanosecond precision. - annotations: dict - Dictionary containing all annotations. The keys are the uids of the annotations and the - values are objects of type BoundingBox2d, BoundingBox3d, Polygon2d, Polyline2d or - Segementation3d. - """ - - id: int - timestamp: Decimal - bounding_box_2ds: t.Dict[str, BoundingBox2d] - bounding_box_3ds: t.Dict[str, BoundingBox3d] - polygon_2ds: t.Dict[str, Polygon2d] - polyline_2ds: t.Dict[str, Polyline2d] - segmentation_3ds: t.Dict[str, Segmentation3d] - - _annotation_uids: t.Set[str] = None - - @property - def annotations(self) -> dict: - """Return all annotations of this frame in one dict.""" - return { - **self.bounding_box_2ds, - **self.polyline_2ds, - **self.polygon_2ds, - **self.bounding_box_3ds, - **self.segmentation_3ds, - } - - @property - def translated_objects(self) -> dict: - """Return all objects in this frame and translate them. - - Returns - ------- - dict - Dictionary containing all objects. Keys are the object IDs and values are the - translated class names. - """ - return { - str(a.object_id): translate_class_id(a.class_name) for a in self.annotations.values() - } - - @property - def translated_sensors(self) -> dict: - """Return all sensors in this frame and translates them. - - Returns - ------- - dict - Dictionary containing all sensors. Keys are the translated sensor IDs and values are - the SensorReference objects. - """ - sensors_list = [] - - for annotation in list(self.annotations.values()): - sensors_list.append(annotation.sensor) - sensors_list[-1].type = translate_sensor_id(sensors_list[-1].type) - - return {sensor.type: sensor for sensor in sensors_list} - - @classmethod - def fromdict(cls, data_dict: dict) -> "Frame": - """Generate a Frame from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Frame - Converted frame. - """ - - cls._annotation_uids = set() - - return Frame( - id=int(data_dict["frameId"]), - timestamp=Decimal(data_dict["timestamp"]), - bounding_box_2ds=cls._annotation_fromdict( - data_dict["annotations"]["2D_BOUNDING_BOX"], BoundingBox2d - ), - bounding_box_3ds=cls._annotation_fromdict( - data_dict["annotations"]["3D_BOUNDING_BOX"], BoundingBox3d - ), - polygon_2ds=cls._annotation_fromdict(data_dict["annotations"]["2D_POLYGON"], Polygon2d), - polyline_2ds=cls._annotation_fromdict( - data_dict["annotations"]["2D_POLYLINE"], Polyline2d - ), - segmentation_3ds=cls._annotation_fromdict( - data_dict["annotations"]["3D_SEGMENTATION"], Segmentation3d - ), - ) - - def to_raillabel(self) -> dict: - """Generate a Frame from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Frame - Converted frame. - """ - return { - "frame_properties": self._frame_properties_to_raillabel(), - "objects": self._objects_to_raillabel(), - } - - @classmethod - def _annotation_fromdict( - cls, data_dict: dict, annotation_class: t.Type[_Annotation] - ) -> t.Dict[str, t.Type[_Annotation]]: - - annotations = {} - for annotation_dict in data_dict: - annotation_dict["id"] = cls._check_duplicate_annotation_uid(annotation_dict["id"]) - annotations[annotation_dict["id"]] = annotation_class.fromdict(annotation_dict) - - return {ann["id"]: annotation_class.fromdict(ann) for ann in data_dict} - - @classmethod - def _check_duplicate_annotation_uid(cls, uid: str) -> str: - - if uid in cls._annotation_uids: - _warning( - f"Annotation uid {uid} is contained more than once. A new uid will be assigned." - ) - return str(uuid.uuid4()) - - cls._annotation_uids.add(uid) - return uid - - def _frame_properties_to_raillabel(self) -> dict: - - streams_dict = {} - for stream_id, stream in self.translated_sensors.items(): - streams_dict[stream_id] = { - "stream_properties": {"sync": {"timestamp": str(stream.timestamp)}}, - "uri": stream.uri.split("/")[-1], - } - - return { - "timestamp": str(self.timestamp), - "streams": { - sensor.type: sensor.to_raillabel()[1] for sensor in self.translated_sensors.values() - }, - } - - def _objects_to_raillabel(self) -> dict: - object_data = {} - - for annotation in self.annotations.values(): - - object_id = str(annotation.object_id) - - if object_id not in object_data: - object_data[object_id] = {"object_data": {}} - - if annotation.OPENLABEL_ID not in object_data[object_id]["object_data"]: - object_data[object_id]["object_data"][annotation.OPENLABEL_ID] = [] - - object_data[object_id]["object_data"][annotation.OPENLABEL_ID].append( - annotation.to_raillabel()[0] - ) - - return object_data diff --git a/raillabel/format/understand_ai/metadata.py b/raillabel/format/understand_ai/metadata.py deleted file mode 100644 index 2a2a5a4..0000000 --- a/raillabel/format/understand_ai/metadata.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import json -from dataclasses import dataclass -from pathlib import Path - - -@dataclass -class Metadata: - """Container for metadata information about the scene itself. - - Parameters - ---------- - clip_id: str - Identifier of the scene for internal purposes. - external_clip_id: str - Identifier of the scene for external purposes. - project_id: str - Identifier of the annotation project. - export_time: str - Timestamp of the export in the format 'YYYY-MM-DD hh:mm UTC'. - exporter_version: str - Version of the Understand.AI-exporter. - coordinate_system_3d: str - coordinate_system_reference: str - folder_name: str - Directory with the exported reference data (e.g. images, point clouds). - """ - - clip_id: str - external_clip_id: str - project_id: str - export_time: str - exporter_version: str - coordinate_system_3d: str - coordinate_system_reference: str - folder_name: str - - @classmethod - def fromdict(cls, data_dict: dict) -> "Metadata": - """Generate a Metadata from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - metadata: Metadata - Converted metadata. - """ - - return Metadata( - clip_id=data_dict["clip_id"], - external_clip_id=data_dict["external_clip_id"], - project_id=data_dict["project_id"], - export_time=data_dict["export_time"], - exporter_version=data_dict["exporter_version"], - coordinate_system_3d=data_dict["coordinate_system_3d"], - coordinate_system_reference=data_dict["coordinate_system_reference"], - folder_name=data_dict["folder_name"], - ) - - def to_raillabel(self) -> dict: - """Convert to a raillabel compatible dict. - - Returns - ------- - metadata: dict - Converted metadata. - """ - - return { - "name": self.external_clip_id, - "schema_version": "1.0.0", - "subschema_version": self._get_subschema_version(), - "tagged_file": self.folder_name, - "annotator": "understandAI GmbH", - } - - def _get_subschema_version(self) -> str: - RAILLABEL_SCHEMA_PATH = ( - Path(__file__).parent.parent.parent / "validate" / "schemas" / "raillabel_schema.json" - ) - - with RAILLABEL_SCHEMA_PATH.open() as schema_file: - subschema_version = json.load(schema_file)["version"] - - return subschema_version diff --git a/raillabel/format/understand_ai/point_3d.py b/raillabel/format/understand_ai/point_3d.py deleted file mode 100644 index 41e90ec..0000000 --- a/raillabel/format/understand_ai/point_3d.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from dataclasses import dataclass - - -@dataclass -class Point3d: - """Dimensional information of an object in 3d. - - Parameters - ---------- - x: float - Position of the object in the x-dimension. - y: float - Position of the object in the y-dimension. - z: float - Position of the object in the z-dimension. - """ - - x: float - y: float - z: float - - @classmethod - def fromdict(cls, data_dict: dict) -> "Point3d": - """Generate a Point3d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Point3d - Converted 3d point. - """ - - return Point3d( - x=float(data_dict["x"]), - y=float(data_dict["y"]), - z=float(data_dict["z"]), - ) diff --git a/raillabel/format/understand_ai/polygon_2d.py b/raillabel/format/understand_ai/polygon_2d.py deleted file mode 100644 index 0d0ca42..0000000 --- a/raillabel/format/understand_ai/polygon_2d.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from uuid import UUID - -from ._annotation import _Annotation -from .sensor_reference import SensorReference - - -@dataclass -class Polygon2d(_Annotation): - """A 2d polygon. - - Parameters - ---------- - id: uuid.UUID - Unique identifier of the annotation. - object_id: uuid.UUID - Unique identifier of the object this annotation refers to. Used for tracking. - class_name: str - Name of the class this annotation belongs to. - attributes: dict[str, str or list] - Key value pairs of attributes with the keys beeing the friendly identifier of the - attribute and the value beeing the attribute value. - sensor: raillabel.format.understand_ai.SensorReference - Information about the sensor this annotation is labeled in. - points: list[tuple[float, float]] - 2d points belonging to the polygon. - """ - - points: t.List[t.Tuple[float, float]] - - OPENLABEL_ID = "poly2d" - - @classmethod - def fromdict(cls, data_dict: t.Dict) -> "Polygon2d": - """Generate a Polygon2d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Polygon2d - Converted 2d polygon. - """ - - return Polygon2d( - id=UUID(data_dict["id"]), - object_id=UUID(data_dict["objectId"]), - class_name=data_dict["className"], - attributes=data_dict["attributes"], - sensor=SensorReference.fromdict(data_dict["sensor"]), - points=[(p[0], p[1]) for p in data_dict["geometry"]["points"]], - ) - - def to_raillabel(self) -> t.Tuple[dict, str, str, dict]: - """Convert to a raillabel compatible dict. - - Returns - ------- - annotation: dict - Dictionary valid for the raillabel schema. - object_id: str - Friendly identifier of the object this sensor belongs to. - class_name: str - Friendly identifier of the class the annotated object belongs to. - sensor_reference: dict - Dictionary of the sensor reference. - """ - - polygon = super().to_raillabel() - polygon[0]["closed"] = True - polygon[0]["mode"] = "MODE_POLY2D_ABSOLUTE" - - return polygon - - def _val_to_raillabel(self) -> t.List[float]: - return [coordinate for point in self.points for coordinate in point] diff --git a/raillabel/format/understand_ai/polyline_2d.py b/raillabel/format/understand_ai/polyline_2d.py deleted file mode 100644 index 861fed3..0000000 --- a/raillabel/format/understand_ai/polyline_2d.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from uuid import UUID - -from ._annotation import _Annotation -from .sensor_reference import SensorReference - - -@dataclass -class Polyline2d(_Annotation): - """A 2d polyline. - - Parameters - ---------- - id: uuid.UUID - Unique identifier of the annotation. - object_id: uuid.UUID - Unique identifier of the object this annotation refers to. Used for tracking. - class_name: str - Name of the class this annotation belongs to. - attributes: dict[str, str or list] - Key value pairs of attributes with the keys beeing the friendly identifier of the - attribute and the value beeing the attribute value. - sensor: raillabel.format.understand_ai.SensorReference - Information about the sensor this annotation is labeled in. - points: list[tuple[float, float]] - 2d points belonging to the polyline. - """ - - points: t.List[t.Tuple[float, float]] - - OPENLABEL_ID = "poly2d" - - @classmethod - def fromdict(cls, data_dict: t.Dict) -> "Polyline2d": - """Generate a Polyline2d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Polyline2d - Converted 2d polyline. - """ - - return Polyline2d( - id=UUID(data_dict["id"]), - object_id=UUID(data_dict["objectId"]), - class_name=data_dict["className"], - attributes=data_dict["attributes"], - sensor=SensorReference.fromdict(data_dict["sensor"]), - points=[(p[0], p[1]) for p in data_dict["geometry"]["points"]], - ) - - def to_raillabel(self) -> t.Tuple[dict, str, str, dict]: - """Convert to a raillabel compatible dict. - - Returns - ------- - annotation: dict - Dictionary valid for the raillabel schema. - object_id: str - Friendly identifier of the object this sensor belongs to. - class_name: str - Friendly identifier of the class the annotated object belongs to. - sensor_reference: dict - Dictionary of the sensor reference. - """ - - polyline = super().to_raillabel() - polyline[0]["closed"] = False - polyline[0]["mode"] = "MODE_POLY2D_ABSOLUTE" - - return polyline - - def _val_to_raillabel(self) -> t.List[float]: - return [coordinate for point in self.points for coordinate in point] diff --git a/raillabel/format/understand_ai/quaternion.py b/raillabel/format/understand_ai/quaternion.py deleted file mode 100644 index eec6300..0000000 --- a/raillabel/format/understand_ai/quaternion.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from dataclasses import dataclass - - -@dataclass -class Quaternion: - """Dimensional information of an object in 3d. - - Parameters - ---------- - x: float - The x component of the quaternion. - y: float - The y component of the quaternion. - z: float - The z component of the quaternion. - w: float - The w component of the quaternion. - """ - - x: float - y: float - z: float - w: float - - @classmethod - def fromdict(cls, data_dict: dict) -> "Quaternion": - """Generate a Quaternion from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Quaternion - Converted quaternion. - """ - - return Quaternion( - x=float(data_dict["x"]), - y=float(data_dict["y"]), - z=float(data_dict["z"]), - w=float(data_dict["w"]), - ) diff --git a/raillabel/format/understand_ai/scene.py b/raillabel/format/understand_ai/scene.py deleted file mode 100644 index 3d589c2..0000000 --- a/raillabel/format/understand_ai/scene.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass - -from ..._util._warning import _warning -from .coordinate_system import CoordinateSystem -from .frame import Frame -from .metadata import Metadata - - -@dataclass -class Scene: - """The root Understand.Ai class, which contains all data. - - Parameters - ---------- - metadata: raillabel.format.understand_ai.Metadata - Container for metadata information about the scene itself. - coordinate_systems: dict[str, raillabel.format.understand_ai.CoordinateSystem] - Global information for sensors regarding calibration. - frames: dict[int, raillabel.format.understand_ai.Frame] - """ - - metadata: Metadata - coordinate_systems: t.Dict[str, CoordinateSystem] - frames: t.Dict[int, Frame] - - @classmethod - def fromdict(cls, data_dict: dict) -> "Scene": - """Generate a Scene from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Scene - Converted scene. - """ - - return Scene( - metadata=Metadata.fromdict(data_dict["metadata"]), - coordinate_systems=cls._coordinate_systems_fromdict(data_dict["coordinateSystems"]), - frames=cls._frames_fromdict(data_dict["frames"]), - ) - - def to_raillabel(self) -> dict: - """Convert to a raillabel compatible dict. - - Returns - ------- - dict: - Dictionary of the raillabel scene. - """ - return { - "openlabel": { - "metadata": self.metadata.to_raillabel(), - "streams": self._streams_to_raillabel(), - "coordinate_systems": self._coordinate_systems_to_raillabel(), - "objects": self._objects_to_raillabel(), - "frames": {str(frame.id): frame.to_raillabel() for frame in self.frames.values()}, - } - } - - @classmethod - def _coordinate_systems_fromdict(cls, data_dict: t.List[dict]) -> t.Dict[str, CoordinateSystem]: - coordinate_systems = {} - for cs in data_dict: - coordinate_systems[cs["coordinate_system_id"]] = CoordinateSystem.fromdict(cs) - - return coordinate_systems - - @classmethod - def _frames_fromdict(cls, data_dict: t.List[dict]) -> t.Dict[int, Frame]: - frames = {} - for frame in data_dict: - frame_id = int(frame["frameId"]) - - if frame_id in frames: - _warning( - f"Frame UID {frame_id} is contained more than once in the scene. " - + "The duplicate frame will be omitted." - ) - continue - - frames[frame_id] = Frame.fromdict(frame) - - return frames - - def _streams_to_raillabel(self) -> dict: - return {cs.translated_uid: cs.to_raillabel()[1] for cs in self.coordinate_systems.values()} - - def _coordinate_systems_to_raillabel(self) -> dict: - coordinate_systems = { - cs.translated_uid: cs.to_raillabel()[0] for cs in self.coordinate_systems.values() - } - - coordinate_systems["base"] = { - "type": "local", - "parent": "", - "children": list(coordinate_systems.keys()), - } - - return coordinate_systems - - def _objects_to_raillabel(self) -> dict: - - object_dicts = self._collect_all_translated_objects() - - object_name_counter = {} - objects = {} - for object_id, object_class in object_dicts.items(): - - if object_class not in object_name_counter: - object_name_counter[object_class] = 0 - - objects[object_id] = { - "name": f"{object_class}_{str(object_name_counter[object_class]).rjust(4, '0')}", - "type": object_class, - } - object_name_counter[object_class] += 1 - - return objects - - def _collect_all_translated_objects(self) -> dict: - object_dicts = {} - for frame in self.frames.values(): - object_dicts.update(frame.translated_objects) - - return object_dicts diff --git a/raillabel/format/understand_ai/segmentation_3d.py b/raillabel/format/understand_ai/segmentation_3d.py deleted file mode 100644 index 88edaf4..0000000 --- a/raillabel/format/understand_ai/segmentation_3d.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from uuid import UUID - -from ._annotation import _Annotation -from .sensor_reference import SensorReference - - -@dataclass -class Segmentation3d(_Annotation): - """The 3D segmentation of a lidar pointcloud. - - Parameters - ---------- - id: uuid.UUID - Unique identifier of the annotation. - object_id: uuid.UUID - Unique identifier of the object this annotation refers to. Used for tracking. - class_name: str - Name of the class this annotation belongs to. - attributes: dict[str, str or list] - Key value pairs of attributes with the keys beeing the friendly identifier of the - attribute and the value beeing the attribute value. - sensor: raillabel.format.understand_ai.SensorReference - Information about the sensor this annotation is labeled in. - associated_points: list[int] - List of point indices of the lidar pointcloud. - number_of_points: int - Total number of points in the associated_points. - """ - - associated_points: t.List[int] - number_of_points: int - - OPENLABEL_ID = "vec" - - @classmethod - def fromdict(cls, data_dict: t.Dict) -> "Segmentation3d": - """Generate a Segmentation3d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Segmentation3d - Converted 3d segmentation. - """ - - return Segmentation3d( - id=UUID(data_dict["id"]), - object_id=UUID(data_dict["objectId"]), - class_name=data_dict["className"], - attributes=data_dict["attributes"], - sensor=SensorReference.fromdict(data_dict["sensor"]), - associated_points=data_dict["geometry"]["associatedPoints"], - number_of_points=data_dict["geometry"]["numberOfPointsInBox"], - ) - - def _val_to_raillabel(self) -> list: - return self.associated_points diff --git a/raillabel/format/understand_ai/sensor_reference.py b/raillabel/format/understand_ai/sensor_reference.py deleted file mode 100644 index 5df859c..0000000 --- a/raillabel/format/understand_ai/sensor_reference.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from dataclasses import dataclass -from decimal import Decimal - - -@dataclass -class SensorReference: - """Information for a sensor in a frame. - - Parameters - ---------- - type: str - Friendly name of the sensor and its unique identifier. - uri: str - URI to the file containing the frame specific sensor output from the project directory. - timestamp: decimal.Decimal - Unix timestamp of the sensor recording. - """ - - type: str - uri: str - timestamp: Decimal - - @classmethod - def fromdict(cls, data_dict: dict) -> "SensorReference": - """Generate a SensorReference from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - SensorReference - Converted sensor reference. - """ - - return SensorReference( - type=data_dict["type"], uri=data_dict["uri"], timestamp=Decimal(data_dict["timestamp"]) - ) - - def to_raillabel(self) -> t.Tuple[str, dict]: - """Convert to a raillabel compatible dict. - - Returns - ------- - sensor_id: str - Friendly identifier of the sensor. - sensor_reference: dict - Dictionary valid for the raillabel schema. - """ - - return ( - self.type, - { - "stream_properties": {"sync": {"timestamp": str(self.timestamp)}}, - "uri": self.uri.split("/")[-1], - }, - ) diff --git a/raillabel/format/understand_ai/size_3d.py b/raillabel/format/understand_ai/size_3d.py deleted file mode 100644 index 6be56a4..0000000 --- a/raillabel/format/understand_ai/size_3d.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from dataclasses import dataclass - - -@dataclass -class Size3d: - """Dimensional information of an object in 3d. - - Parameters - ---------- - width: float - Size of the object in the x-dimension. - length: float - Size of the object in the y-dimension. - height: float - Size of the object in the z-dimension. - """ - - width: float - length: float - height: float - - @classmethod - def fromdict(cls, data_dict: dict) -> "Size3d": - """Generate a Size3d from a dictionary in the UAI format. - - Parameters - ---------- - data_dict: dict - Understand.AI T4 format dictionary containing the data_dict. - - Returns - ------- - Size3d - Converted 3d size. - """ - - return Size3d( - width=float(data_dict["width"]), - length=float(data_dict["length"]), - height=float(data_dict["height"]), - ) diff --git a/raillabel/load_/loader_classes/loader_understand_ai.py b/raillabel/load_/loader_classes/loader_understand_ai.py deleted file mode 100644 index d196360..0000000 --- a/raillabel/load_/loader_classes/loader_understand_ai.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from pathlib import Path - -from ..._util._warning import _WarningsLogger -from ...format import understand_ai as uai_format -from ._loader_abc import LoaderABC -from .loader_raillabel import LoaderRailLabel - - -class LoaderUnderstandAi(LoaderABC): - """Loader class for the Understand.Ai Trains4 annotation format. - - Attributes - ---------- - scene: raillabel.format.understand_ai.Scene - Loaded raillabel.format.understand_ai.Scene with the data. - warnings: t.List[str] - List of warning strings, that have been found during the execution of load(). - """ - - scene: uai_format.Scene - warnings: t.List[str] - - SCHEMA_PATH: Path = ( - Path(__file__).parent.parent.parent - / "validate" - / "schemas" - / "understand_ai_t4_schema.json" - ) - - def load(self, data: dict, validate: bool = False) -> uai_format.Scene: - """Load the data into a UAIScene and return it. - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - validate: bool - If True, the annotation data is validated via the respective schema. This is highly - recommended, as not validating the data may lead to Errors during loading or while - handling the scene. However, validating may increase the loading time. Default is False. - - Returns - ------- - scene: raillabel.format.understand_ai.UAIScene - The loaded scene with the data. - """ - - if validate: - self.validate(data) - - with _WarningsLogger() as logger: - data_converted_to_raillabel = uai_format.Scene.fromdict(data).to_raillabel() - - raillabel_loader = LoaderRailLabel() - raillabel_scene = raillabel_loader.load(data_converted_to_raillabel, validate=False) - - self.warnings = logger.warnings + raillabel_loader.warnings - - return raillabel_scene - - def supports(self, data: dict) -> bool: - """Determine if the loader is suitable for the data (lightweight). - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - - Returns - ------- - bool: - If True, the Loader class is suitable for the data. - """ - - return ( - "metadata" in data - and "project_id" in data["metadata"] - and "coordinateSystems" in data - and "frames" in data - ) diff --git a/raillabel/load_/loader_classes/translation.json b/raillabel/load_/loader_classes/translation.json deleted file mode 100644 index 1187d34..0000000 --- a/raillabel/load_/loader_classes/translation.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "__version__": "1.1.0", - "classes": { - "2D_catenary_pole": "catenary_pole", - "2D_person": "person", - "2D_bicycle": "bicycle", - "2D_signal_pole": "signal_pole", - "2D_track": "track", - "2D_train": "train", - "2D_train_front": "train_front", - "2D_transition": "transition", - "2D_wagons": "wagons", - "2D_switch": "switch", - "2D_buffer_stop": "buffer_stop", - "2D_crowd": "crowd", - "3D_catenary_pole": "catenary_pole", - "3D_person": "person", - "3D_signal_pole": "signal_pole", - "3D_track": "track", - "3D_train": "train", - "3D_transition": "transition", - "3D_wagons": "wagons", - "3D_switch": "switch", - "3D_buffer_stop": "buffer_stop", - "3D_crowd": "crowd", - "3D_bicycle": "bicycle", - "ir_track": "track", - "ir_transition": "transition", - "ir_wagons": "wagons", - "ir_switch": "switch", - "ir_buffer_stop": "buffer_stop", - "ir_crowd": "crowd", - "ir_bicycle": "bicycle", - "radar_catenary_pole": "catenary_pole", - "radar_person": "person", - "radar_signal_pole": "signal_pole", - "radar_train": "train", - "radar_transition": "transition", - "radar_wagons": "wagons", - "radar_switch": "switch", - "radar_buffer_stop": "buffer_stop", - "radar_crowd": "crowd", - "radar_bicycle": "bicycle", - "rgb_track": "track", - - "Pedestrian": "person", - "Track": "track", - "Trains": "train", - "Transition": "transition", - "CatenaryPoles": "catenary_pole" - }, - "coordinate_systems": { - "new_left_rect": "rgb_highres_left", - "new_middle_rect": "rgb_highres_middle", - "new_right_rect": "rgb_highres_right", - "rgb_left_rect": "rgb_left", - "rgb_middle_rect": "rgb_middle", - "rgb_right_rect": "rgb_right", - "ir_left": "ir_left", - "ir_middle": "ir_middle", - "ir_right": "ir_right", - "lidar_merged": "lidar", - "radar": "radar", - "imu": "gps_imu", - - "S1213751_image": "rgb_highres_left", - "S1213752_image": "rgb_highres_middle", - "S1213755_image": "rgb_highres_right", - "S1206062_image": "rgb_left", - "S1206063_image": "rgb_middle", - "S1206064_image": "rgb_right", - "A0001780_image": "ir_left", - "A0001781_image": "ir_middle", - "A0001782_image": "ir_right", - "points_raw": "lidar", - "S1213751_image_1": "rgb_highres_left", - "S1213752_image_1": "rgb_highres_middle", - "S1213755_image_1": "rgb_highres_right", - "S1206062_image_1": "rgb_left", - "S1206063_image_1": "rgb_middle", - "S1206064_image_1": "rgb_right", - "A0001780_image_1": "ir_left", - "A0001781_image_1": "ir_middle", - "A0001782_image_1": "ir_right", - "points_raw_1": "lidar", - - "base_link": "base", - "S1213751": "rgb_highres_left", - "S1213752": "rgb_highres_middle", - "S1213755": "rgb_highres_right", - "S1206062": "rgb_left", - "S1206063": "rgb_middle", - "S1206064": "rgb_right", - "A0001780": "ir_left", - "A0001781": "ir_middle", - "A0001782": "ir_right", - - "camera_infrared_rectified": "ir_middle", - "camera_long_range_rectified": "rgb_longrange_middle", - "camera_mid_range_rectified": "rgb_middle" - }, - "streams": { - "new_left_rect": "rgb_highres_left", - "new_middle_rect": "rgb_highres_middle", - "new_right_rect": "rgb_highres_right", - "rgb_left_rect": "rgb_left", - "rgb_middle_rect": "rgb_middle", - "rgb_right_rect": "rgb_right", - "ir_left": "ir_left", - "ir_middle": "ir_middle", - "ir_right": "ir_right", - "LIDAR": "lidar", - "lidar_merged": "lidar", - "imu": "gps_imu", - "radar": "radar", - - "S1213751_image": "rgb_highres_left", - "S1213752_image": "rgb_highres_middle", - "S1213755_image": "rgb_highres_right", - "S1206062_image": "rgb_left", - "S1206063_image": "rgb_middle", - "S1206064_image": "rgb_right", - "A0001780_image": "ir_left", - "A0001781_image": "ir_middle", - "A0001782_image": "ir_right", - "points_raw": "lidar", - "S1213751_image_1": "rgb_highres_left", - "S1213752_image_1": "rgb_highres_middle", - "S1213755_image_1": "rgb_highres_right", - "S1206062_image_1": "rgb_left", - "S1206063_image_1": "rgb_middle", - "S1206064_image_1": "rgb_right", - "A0001780_image_1": "ir_left", - "A0001781_image_1": "ir_middle", - "A0001782_image_1": "ir_right", - "points_raw_1": "lidar", - - "base_link": "base", - "S1213751": "rgb_highres_left", - "S1213752": "rgb_highres_middle", - "S1213755": "rgb_highres_right", - "S1206062": "rgb_left", - "S1206063": "rgb_middle", - "S1206064": "rgb_right", - "A0001780": "ir_left", - "A0001781": "ir_middle", - "A0001782": "ir_right", - - "camera_infrared_rectified": "ir_midrange", - "camera_long_range_rectified": "rgb_longrange", - "camera_mid_range_rectified": "rgb_midrange" - }, - "stream_types": { - "rgb_highres_left": "camera", - "rgb_highres_middle": "camera", - "rgb_highres_right": "camera", - "rgb_left": "camera", - "rgb_middle": "camera", - "rgb_right": "camera", - "ir_left": "camera", - "ir_middle": "camera", - "ir_right": "camera", - "radar": "radar", - "lidar": "lidar", - "gps_imu": "gps_imu", - - "ir_midrange": "camera", - "rgb_longrange": "camera", - "rgb_midrange": "camera" - }, - "stream_rostopics": { - "rgb_highres_left": "/S1213751/image", - "rgb_highres_middle": "/S1213752/image", - "rgb_highres_right": "/S1213755/image", - "rgb_left": "/S1206062/image", - "rgb_middle": "/S1206063/image", - "rgb_right": "/S1206064/image", - "ir_left": "/A0001780/image", - "ir_middle": "/A0001781/image", - "ir_right": "/A0001782/image", - "radar": "/talker1/Nvt/Cartesian", - "lidar": "/lidar_merged", - "gps_imu": "/novatel/oem7/inspva" - }, - "stream_resolutions": { - "rgb_highres_left": {"x": 4112,"y": 2504}, - "rgb_highres_middle": {"x": 4112,"y": 2504}, - "rgb_highres_right": {"x": 4112,"y": 2504}, - "rgb_left": {"x": 2464,"y": 1600}, - "rgb_middle": {"x": 2464,"y": 1600}, - "rgb_right": {"x": 2464,"y": 1600}, - "ir_left": {"x": 640,"y": 480}, - "ir_middle": {"x": 640,"y": 480}, - "ir_right": {"x": 640,"y": 480}, - "radar": {"resolution_px_per_m": 2.856, "x": 2856, "y": 1428} - } -} diff --git a/raillabel/load_/loader_classes/translation.json.license b/raillabel/load_/loader_classes/translation.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/raillabel/load_/loader_classes/translation.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/tests/__test_assets__/understand_ai_real_life.json b/tests/__test_assets__/understand_ai_real_life.json deleted file mode 100644 index d1dc17d..0000000 --- a/tests/__test_assets__/understand_ai_real_life.json +++ /dev/null @@ -1 +0,0 @@ -{"metadata": {"clip_id": "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", "external_clip_id": "2021-09-22-14-28-01_2021-09-22-14-44-03", "project_id": "trains_4", "export_time": "2023-04-20 01:38 UTC", "exporter_version": "1.0.0", "coordinate_system_3d": "FLU", "coordinate_system_reference": "SENSOR", "folder_name": "2021-09-22-14-28-01_2021-09-22-14-44-03"}, "coordinateSystems": [{"coordinate_system_id": "ir_left", "topic": "/A0001780/image", "frame_id": "A0001780", "position": [0.0907818, 0.398344, 3.5168], "rotation_quaternion": [0.996291, -2.43881e-05, 0.0254277, 0.0822105], "rotation_matrix": [0.9851897429851865, -0.1638122674201864, 0.05066272159526802, 0.1638097868835605, 0.986482876466111, 0.004229439715850778, -0.05067074143867226, 0.004132249000831645, 0.9987068641399489], "angle_axis_rotation": [-4.88367e-05, 0.0509184, 0.164625], "homogeneous_transform": [0.9851897429851865, -0.1638122674201864, 0.05066272159526802, 0.09078179057326469, 0.1638097868835605, 0.986482876466111, 0.004229439715850778, 0.3983440115030037, -0.05067074143867226, 0.004132249000831645, 0.9987068641399489, 3.516800000000002, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [3535.294117647, 0, 319.5, 0, 3535.294117647, 239.5, 0, 0, 1], "dist_coeffs": [0, 0, 0, 0, 0]}, {"coordinate_system_id": "ir_middle", "topic": "/A0001781/image", "frame_id": " A0001781", "position": [0.0933424, 0.225241, 3.5171], "rotation_quaternion": [0.999697, 0.00329686, 0.0243397, 0.00187897], "rotation_matrix": [0.998808097970439, -0.003596303890419965, 0.04867699689794021, 0.0039172823670546, 0.9999712003585413, -0.006500257868352383, -0.04865221811522157, 0.006683191739435895, 0.9987934203931477], "angle_axis_rotation": [0.00659439, 0.0486843, 0.00375831], "homogeneous_transform": [0.998808097970439, -0.003596303890419965, 0.04867699689794021, 0.09334241552161804, 0.0039172823670546, 0.9999712003585413, -0.006500257868352383, 0.2252408177142321, -0.04865221811522157, 0.006683191739435895, 0.9987934203931477, 3.517100000000001, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [3535.294117647, 0, 319.5, 0, 3535.294117647, 239.5, 0, 0, 1], "dist_coeffs": [0, 0, 0, 0, 0]}, {"coordinate_system_id": "ir_right", "topic": "/A0001782/image", "frame_id": "A0001782", "position": [0.0910011, 0.0528333, 3.5176], "rotation_quaternion": [0.99699, -0.000112887, 0.025241, -0.0733026], "rotation_matrix": [0.987979224228036, 0.1461583523732261, 0.05034668335960651, -0.1461697499026653, 0.9892534184834408, -0.003475375119154851, -0.05031358372408868, -0.003925563700978337, 0.9987257547707825], "angle_axis_rotation": [-0.000226001, 0.0505328, -0.146753], "homogeneous_transform": [0.987979224228036, 0.1461583523732261, 0.05034668335960651, 0.09100114488321256, -0.1461697499026653, 0.9892534184834408, -0.003475375119154851, 0.05283326726534611, -0.05031358372408868, -0.003925563700978337, 0.9987257547707825, 3.517600000000002, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [3535.294117647, 0, 319.5, 0, 3535.294117647, 239.5, 0, 0, 1], "dist_coeffs": [0, 0, 0, 0, 0]}, {"coordinate_system_id": "new_left_rect", "topic": "/S1213751/image", "frame_id": "S1213751", "position": [0.0609096, -0.136682, 3.51522], "rotation_quaternion": [0.980119, -0.0121052, 0.0606543, 0.188524], "rotation_matrix": [0.9215598505857764, -0.3710195436110039, 0.1143325852371231, 0.3680826063253607, 0.9286246600789553, 0.04659866536688102, -0.1234610736590737, -0.0008596231310027266, 0.9923490436026136], "angle_axis_rotation": [-0.0243722, 0.122119, 0.379566], "homogeneous_transform": [0.9215598505857764, -0.3710195436110039, 0.1143325852371231, 0.06090955407783759, 0.3680826063253607, 0.9286246600789553, 0.04659866536688102, -0.1366819504642395, -0.1234610736590737, -0.0008596231310027266, 0.9923490436026136, 3.515224928543994, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [7265.09513308939, 0, 2099.699693520321, 0, 7265.09513308939, 1217.709330768128, 0, 0, 1], "dist_coeffs": [-0.0878118, 0.0196239, 0, 0, 0.902557]}, {"coordinate_system_id": "new_middle_rect", "topic": "/S1213752/image", "frame_id": "S1213752", "position": [0.0801578, -0.332862, 3.50982], "rotation_quaternion": [0.998397, -0.00313306, 0.0562995, 0.00482918], "rotation_matrix": [0.9936140831557001, -0.009995656419306852, 0.1123883472936787, 0.009290097553920292, 0.9999337259704357, 0.006799834874476047, -0.1124488676781407, -0.005712312984130525, 0.9936410929698285], "angle_axis_rotation": [-0.00626947, 0.112659, 0.00966352], "homogeneous_transform": [0.9936140831557001, -0.009995656419306852, 0.1123883472936787, 0.08015775479489574, 0.009290097553920292, 0.9999337259704357, 0.006799834874476047, -0.3328620687352016, -0.1124488676781407, -0.005712312984130525, 0.9936410929698285, 3.509822884272755, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [7267.95450880415, 0, 2056.049238502414, 0, 7267.95450880415, 1232.862908875167, 0, 0, 1], "dist_coeffs": [-0.0764891, -0.188057, 0, 0, 1.78279]}, {"coordinate_system_id": "new_right_rect", "topic": "/S1213755/image", "frame_id": "S1213755", "position": [0.0510683, -0.525541, 3.51628], "rotation_quaternion": [0.980918, 0.0217394, 0.0639103, -0.182324], "rotation_matrix": [0.9253467445770058, 0.3604690081432737, 0.1174542313794686, -0.3549115208268265, 0.9325705804257798, -0.06595396052333095, -0.1333087194639806, 0.01934442277579149, 0.9908857545763512], "angle_axis_rotation": [0.0437575, 0.12864, -0.366986], "homogeneous_transform": [0.9253467445770058, 0.3604690081432737, 0.1174542313794686, 0.05106832805493725, -0.3549115208268265, 0.9325705804257798, -0.06595396052333095, -0.5255410107439861, -0.1333087194639806, 0.01934442277579149, 0.9908857545763512, 3.516282343577211, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [7265.854580654392, 0, 2093.506452810741, 0, 7265.854580654392, 1228.255759518024, 0, 0, 1], "dist_coeffs": [-0.0655375, -0.329857, 0, 0, 2.48002]}, {"coordinate_system_id": "rgb_left_rect", "topic": "/S1206062/image", "frame_id": "S1206062", "position": [0.0298925, 0.186612, 2.05637], "rotation_quaternion": [0.984704, 0.00228435, -0.0104673, 0.173904], "rotation_matrix": [0.9392957338063904, -0.3425356865117656, -0.01981988695799589, 0.3424400423651093, 0.9395044262429134, -0.008139438245733292, 0.02140891959199371, 0.0008582166902300636, 0.9997704344628401], "angle_axis_rotation": [0.00459215, -0.021042, 0.349592], "homogeneous_transform": [0.9392957338063904, -0.3425356865117656, -0.01981988695799589, 0.02989252679403986, 0.3424400423651093, 0.9395044262429134, -0.008139438245733292, 0.1866115905585829, 0.02140891959199371, 0.0008582166902300636, 0.9997704344628401, 2.05636627359921, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [4622.041473915607, 0, 1233.380196060109, 0, 4622.041473915607, 843.3909933480334, 0, 0, 1], "dist_coeffs": [-0.0868757, 0.53867, 0, 0, 1.69009]}, {"coordinate_system_id": "rgb_middle_rect", "topic": "/S1206063/image", "frame_id": "S1206063", "position": [0.0669458, -0.000911152, 2.05432], "rotation_quaternion": [0.99999, 0.00193374, -0.00293566, 0.00271082], "rotation_matrix": [0.9999680667369587, -0.005432933661727504, -0.005860779656024867, 0.005410226430795589, 0.9999778242364062, -0.003883360064516633, 0.005881747726375861, 0.003851527911158113, 0.9999752850828029], "angle_axis_rotation": [0.00386749, -0.00587134, 0.00542165], "homogeneous_transform": [0.9999680667369587, -0.005432933661727504, -0.005860779656024867, 0.06694577073389593, 0.005410226430795589, 0.9999778242364062, -0.003883360064516633, -0.0009111521949818156, 0.005881747726375861, 0.003851527911158113, 0.9999752850828029, 2.054322280217477, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [4609.471892628096, 0, 1257.158605934, 0, 4609.471892628096, 820.0498076210201, 0, 0, 1], "dist_coeffs": [-0.0914603, 0.605326, 0, 0, 0.417134]}, {"coordinate_system_id": "rgb_right_rect", "topic": "/S1206064/image", "frame_id": "S1206064", "position": [0.051346, -0.196979, 2.05803], "rotation_quaternion": [0.982823, 0.00740182, -0.0113748, -0.184053], "rotation_matrix": [0.9319898651765356, 0.3616154118829123, -0.02508356228637691, -0.3619521900584727, 0.9321390652699187, -0.01036219617635507, 0.01963423846493898, 0.01873651212135621, 0.9996316520566103], "angle_axis_rotation": [0.014889, -0.0228808, -0.370229], "homogeneous_transform": [0.9319898651765356, 0.3616154118829123, -0.02508356228637691, 0.05134595505315692, -0.3619521900584727, 0.9321390652699187, -0.01036219617635507, -0.1969787994760381, 0.01963423846493898, 0.01873651212135621, 0.9996316520566103, 2.05803483265557, 0, 0, 0, 1], "measured_position": [0, 0, 0], "camera_matrix": [4613.465257442901, 0, 1230.818284106724, 0, 4613.465257442901, 783.2495217495479, 0, 0, 1], "dist_coeffs": [-0.0888195, 0.52648, 0, 0, 1.27408]}, {"coordinate_system_id": "lidar_merged", "topic": "/lidar_merged", "frame_id": "lidar_merged", "position": [0, 0, 0], "rotation_quaternion": [1, 0, 0, 0], "rotation_matrix": [1, 0, 0, 0, 1, 0, 0, 0, 1], "angle_axis_rotation": [0, 0, 0], "homogeneous_transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]}, {"coordinate_system_id": "imu", "topic": "/novatel/oem7/inspva", "frame_id": "gps", "position": [-0.0128436, -0.34586, 1.40235], "rotation_quaternion": [0.708951, 0.00553888, -0.00646254, -0.705207], "rotation_matrix": [0.005283316568778951, 0.9998419494966226, -0.01697535250965811, -0.9999851303902971, 0.005305487067641945, 0.001261271293448576, 0.001351134462094406, 0.01696843639726932, 0.999855112804799], "angle_axis_rotation": [0.0122955, -0.0143459, -1.56546], "homogeneous_transform": [0.005283316568778951, 0.9998419494966226, -0.01697535250965811, -0.01284360283877562, -0.9999851303902971, 0.005305487067641945, 0.001261271293448576, -0.3458604234242062, 0.001351134462094406, 0.01696843639726932, 0.999855112804799, 1.402350000000001, 0, 0, 0, 1]}, {"coordinate_system_id": "radar", "rotation_around_z_in_degrees": 1.22869, "topic": "/talker1/Nvt/Polar", "frame_id": "navtech", "position": [-0.0029401, 0.329699, 1.5638], "rotation_quaternion": [0.999943, 0, 0, 0.0107221], "rotation_matrix": [0.9997700727912402, -0.02144298372425017, 0, 0.02144298372425017, 0.9997700727912402, 0, 0, 0, 1], "angle_axis_rotation": [0, 0, 0.0214446], "homogeneous_transform": [0.9997700727912402, -0.02144298372425017, 0, -0.002940099322541904, 0.02144298372425017, 0.9997700727912402, 0, 0.3296990382394244, 0, 0, 1, 1.5638, 0, 0, 0, 1]}], "frames": [{"frameId": "000", "timestamp": "1632321880.132833000", "annotations": {"2D_BOUNDING_BOX": [{"id": "2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "2D_person", "geometry": {"xMin": 335.0613528455474, "yMin": 73.37014729199004, "xMax": 340.6805183501037, "yMax": 91.12742254778364}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "720270fc-a11e-4b06-983e-203d1595c69b", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "2D_person", "geometry": {"xMin": 304.2236167003333, "yMin": 72.23946974536051, "xMax": 310.3921519920513, "yMax": 92.01238967968362}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "bb73422d-e2cd-4fc7-86e5-5f0df285a7b7", "objectId": "817e2a1c-63fe-4a6e-a695-96d7f4d548f5", "className": "2D_person", "geometry": {"xMin": 188.40749405733118, "yMin": 64.72499707814356, "xMax": 194.09358061839814, "yMax": 73.98141706127583}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "pose": "upright", "function": "passenger", "age": "adult", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "0a3375c8-23f4-412d-85a1-8de5cf4be4d9", "objectId": "d208f5e6-bc1a-44ef-9c54-88450cae72ca", "className": "2D_person", "geometry": {"xMin": 177.17953517456948, "yMin": 63.79344340785774, "xMax": 182.86562173563644, "yMax": 73.04986339099001}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "pose": "upright", "function": "passenger", "age": "adult", "occlusion": "75-100%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "f1e5e16e-5c86-47a9-974f-cf17eeb33717", "objectId": "1fb2a092-c6ce-4e65-aaae-7dd21ffc1ece", "className": "2D_crowd", "geometry": {"xMin": 225.3342096763332, "yMin": 58.01514036957722, "xMax": 237.54465336640024, "yMax": 72.38036824024432}, "attributes": {"size": "<25"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "da4fdbe6-05b3-4497-8fd6-950919e71d44", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "2D_person", "geometry": {"xMin": 2118.896487190378, "yMin": 432.5018147440208, "xMax": 2146.148097005178, "yMax": 465.98069983342845}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "4ab857e4-4a9a-40c0-bf77-d498143c7ea6", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "2D_person", "geometry": {"xMin": 2063.7330499901377, "yMin": 432.9369653978952, "xMax": 2073.9456499222456, "yMax": 455.5466196120661}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "bb5b00c0-a51f-454b-97c1-b73ac096ca1a", "objectId": "1fb2a092-c6ce-4e65-aaae-7dd21ffc1ece", "className": "2D_crowd", "geometry": {"xMin": 1904.5268781542197, "yMin": 403.10555844651935, "xMax": 1928.2916999767085, "yMax": 427.5324127178042}, "attributes": {"size": "<25"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "f5ef4106-a620-413c-b8a5-f2643053f275", "objectId": "817e2a1c-63fe-4a6e-a695-96d7f4d548f5", "className": "2D_person", "geometry": {"xMin": 1826.4236038945085, "yMin": 409.76178376153325, "xMax": 1837.4765795972799, "yMax": 433.2668460155281}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "pose": "upright", "function": "passenger", "age": "adult", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "e77a92ba-24f0-433f-ac2c-db1ae1e1420e", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "2D_person", "geometry": {"xMin": 1282.8870302764296, "yMin": 841.7938065809931, "xMax": 1290.1108660829127, "yMax": 862.5315711622637}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "d4e504f5-e390-4567-ba60-6f921dc29ec8", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "2D_person", "geometry": {"xMin": 1244.6318807670598, "yMin": 841.6253587738138, "xMax": 1251.4123764868605, "yMax": 862.4102202314651}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "occlusion": "0-25%", "pose": "upright"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}], "2D_POLYLINE": [{"id": "7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2", "objectId": "4d8eca35-6c1d-4159-8062-21c2f2c051df", "className": "ir_track", "geometry": {"points": [[493.20616179847406, 479.9728191806302], [542.6546426503735, 431.74800354374366], [589.9350150621058, 385.51547643522207], [639.9702598289472, 335.8846553104306]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_left", "uri": "A0001780_image/000_1632321843.100464380.png", "timestamp": "1632321843.100464380"}}, {"id": "3fb1fec6-9678-4f17-a9f9-5c3b1025bd4f", "objectId": "9aaac1af-fd50-4e87-9c69-d4c68440e741", "className": "ir_track", "geometry": {"points": [[313.0613164798889, 479.950990209001], [369.2321788528948, 440.19873150950764], [408.0805934961111, 413.4535348241989], [554.2742281489762, 310.98987264066847], [639.9596760216506, 250.63394942730864]]}, "attributes": {"occlusion": "0-25%", "railSide": "leftRail", "trackId": "left_first_track"}, "sensor": {"type": "ir_left", "uri": "A0001780_image/000_1632321843.100464380.png", "timestamp": "1632321843.100464380"}}, {"id": "e7643eea-91eb-410d-807e-65185267c96e", "objectId": "9394e5a5-4e10-40d3-97f1-38d2863fa0e8", "className": "ir_track", "geometry": {"points": [[0.03437888727654311, 396.32454437331694], [215.22773808785624, 318.6084462214661], [314.05208340365675, 283.71633252244044], [400.56402223701974, 252.50871268332327], [464.13052182889817, 229.98832632936805], [525.8464409491382, 208.55610276470978], [562.5647293996449, 194.64288217157042], [592.5721219482407, 183.45991089676374], [618.2702255735703, 173.13056538390248], [639.9499715405988, 163.9732386190436]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_left", "uri": "A0001780_image/000_1632321843.100464380.png", "timestamp": "1632321843.100464380"}}, {"id": "5c39d55d-54c8-4ed2-8c31-b6843156ae16", "objectId": "7e78d664-6dd2-4b17-9117-3b4f79f2bf6a", "className": "ir_track", "geometry": {"points": [[-0.004904975156543968, 348.01217475488], [470.7348877753093, 203.85478011624818], [580.2591036269735, 167.4198179772809], [639.9693979825845, 146.84127443269193]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_left", "uri": "A0001780_image/000_1632321843.100464380.png", "timestamp": "1632321843.100464380"}}, {"id": "1faeae82-911e-469e-aa36-78655d8f575d", "objectId": "d8a95754-f499-42f3-a4f7-4fdc375d9741", "className": "ir_track", "geometry": {"points": [[455.3800487473675, 479.98732853302573], [350.7176588703522, 113.00273667746536], [347.9771959854138, 101.651250445595], [344.5496129207221, 96.16711754208822], [336.3234135654619, 90.13457134823078], [325.3551477584484, 82.59388860590899], [319.70358796071935, 77.88853542725904], [317.0068934833211, 73.42564652000226]]}, "attributes": {"trackId": "ego_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "f87cff6f-086e-495b-8b09-c0ef1af44905", "objectId": "edd2e6a9-0de8-4081-baa3-4a85789b015e", "className": "ir_track", "geometry": {"points": [[0.042921983620601914, 395.9037371950096], [170.68694231780856, 228.13640564378], [282.15031405027196, 114.42578851348473], [294.40051606069954, 100.34334741607137], [295.4344412830434, 97.2066114961728], [293.97373177445263, 89.22889033386944], [292.17593545618706, 79.79045966297534], [291.7264863766207, 75.97014248666106], [292.17593545618706, 73.16108573937115]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "fd552130-d990-4935-a846-ec1963f75dc9", "objectId": "bc96ede0-181a-4b2f-b5c0-cc38209b7afe", "className": "ir_track", "geometry": {"points": [[0.0861200955535391, 298.3877763294074], [181.615945555359, 168.20615848280275], [245.88469188942295, 122.77225914737839], [262.77022801342713, 110.09062391268064], [277.0269882006692, 99.26995121369254], [279.20347623027305, 96.36428354054253], [279.68172058794295, 91.5135193413194], [280.2282855681371, 81.88031156539739], [281.1164536609526, 76.96122674364997], [282.21004569888004, 72.71356653819255]]}, "attributes": {"trackId": "left_first_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "179a69ee-1365-4b01-81f7-d3fe1a2f69b4", "objectId": "511c10af-7c23-45ad-9584-b7fb93762b68", "className": "ir_track", "geometry": {"points": [[0.10208334064097657, 198.03651774822134], [89.66049694043467, 161.5013529320002], [158.64383310143512, 134.2116230737762], [221.930332918142, 105.99877021821356], [238.5864827314941, 97.58587740967494], [248.0997627770862, 90.87966301609207], [260.26331201668734, 79.95729227032783], [263.24214040189577, 75.11669614436413], [262.99390470312835, 70.40021786778412], [258.029190727781, 66.05609313935516], [246.85858428324934, 60.470789917089355]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "5b7bd88c-6c67-4bed-a1f2-d68f41900482", "objectId": "8f787a6e-7156-4d86-86a4-639c054e9fe4", "className": "ir_track", "geometry": {"points": [[0.04951429204127547, 178.50135778524762], [174.4205176500624, 116.9579683475495], [229.32378878234135, 94.64266281011702], [235.4848916312007, 90.89870824457981], [241.39203407487483, 86.7275001138147], [254.03253590263736, 76.4619575236806], [256.3261294725185, 73.04492452249436], [254.5884795811469, 68.82491764344908], [244.9072873292195, 62.49490732488117]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "c258279c-c91d-4cb6-a4aa-495617a75780", "objectId": "6f09ed53-50fb-40b0-ab6a-a786e14aa3ca", "className": "ir_track", "geometry": {"points": [[275.6169521207512, 479.76386705594814], [301.4933431973199, 298.05767596796363], [320.97521361540646, 163.6165139117071], [328.858614856986, 111.1039643034198], [329.96822217025834, 99.95949983903851], [329.46056383852846, 97.88498656129265], [328.3061313614731, 96.37867054792011], [313.6263103156072, 83.55745217109013], [309.00460911729783, 79.01870745395739], [307.86157752643675, 73.53932787206075]]}, "attributes": {"trackId": "ego_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "be9a72de-69f2-432a-8b14-800132c93c00", "objectId": "4d8eca35-6c1d-4159-8062-21c2f2c051df", "className": "2D_track", "geometry": {"points": [[2252.521919069662, 2494.8555467437595], [4105.793257512472, 1040.6051634805779]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "new_left_rect", "uri": "S1213751_image/000_1632321843.100000003.png", "timestamp": "1632321843.100000003"}}, {"id": "bde752be-6feb-4fa0-90a5-3668233f10f8", "objectId": "9aaac1af-fd50-4e87-9c69-d4c68440e741", "className": "2D_track", "geometry": {"points": [[1232.7582968238873, 2495.7360659583305], [3722.798427272396, 1068.660656794148], [4106.676388260239, 847.4296538290098]]}, "attributes": {"occlusion": "0-25%", "railSide": "leftRail", "trackId": "left_first_track"}, "sensor": {"type": "new_left_rect", "uri": "S1213751_image/000_1632321843.100000003.png", "timestamp": "1632321843.100000003"}}, {"id": "b5035d8d-317a-40df-8e7d-5329aff67029", "objectId": "9394e5a5-4e10-40d3-97f1-38d2863fa0e8", "className": "2D_track", "geometry": {"points": [[5.141954821974915, 1907.7194687329416], [2882.787217971907, 1010.9085018107056], [4106.6149685702085, 630.7755159484894]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "new_left_rect", "uri": "S1213751_image/000_1632321843.100000003.png", "timestamp": "1632321843.100000003"}}, {"id": "dfc01a89-2399-4d1a-b9ae-afa70064fec9", "objectId": "7e78d664-6dd2-4b17-9117-3b4f79f2bf6a", "className": "2D_track", "geometry": {"points": [[4.995198903747977, 1686.48035498139], [1313.358240005028, 1338.1991865943585], [2141.896352037657, 1118.2479968669118], [2848.151285292086, 926.3600007293461], [3309.4870983201954, 803.471421713125], [3763.544725492926, 681.9224418759578], [4107.385075980987, 586.5791386584722]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "new_left_rect", "uri": "S1213751_image/000_1632321843.100000003.png", "timestamp": "1632321843.100000003"}}, {"id": "c7cf7644-9efa-436d-b51f-22a733bedee8", "objectId": "d8a95754-f499-42f3-a4f7-4fdc375d9741", "className": "2D_track", "geometry": {"points": [[2379.5501498790773, 2495.843175640668], [2146.1995006855764, 494.71563966703246], [2144.187805539245, 485.7495968229351], [2137.5994424088235, 477.816795841213], [2122.0509054210306, 465.05390113950074], [2106.539023171181, 451.9333233005486], [2096.657263973746, 441.53393022533686], [2093.066548582037, 436.2418751923561], [2092.3285817292713, 432.7707124257557], [2092.8428183454807, 429.16303997772394]]}, "attributes": {"trackId": "ego_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "93429ed1-e0d0-4bff-a591-45b40ffe0979", "objectId": "edd2e6a9-0de8-4081-baa3-4a85789b015e", "className": "2D_track", "geometry": {"points": [[4.250803034374943, 2191.4912534176565], [880.1070130443643, 1467.020135582246], [1557.3651181322214, 903.8208231780118], [1791.2392583338794, 703.5098464202181], [1974.8790868836134, 546.1371936384963], [2017.5062809544963, 508.1924791398625], [2043.4085436040327, 483.41879742844907], [2044.3676311728739, 479.7340444663073], [2044.1040966476571, 469.85149977067584], [2042.7864240215729, 452.06291931853923], [2041.864053183314, 436.5143823307458], [2041.864053183314, 432.5613644524932], [2042.522889496356, 430.8483900385837], [2044.2358639102654, 429.6624846751079]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "f8fb8eec-ee0b-4824-a85c-9ad09aa9acb7", "objectId": "bc96ede0-181a-4b2f-b5c0-cc38209b7afe", "className": "2D_track", "geometry": {"points": [[5.077810430167757, 1715.6012098494468], [746.7278472456529, 1268.7187827868531], [1182.4449633668828, 1004.181244382544], [1543.668383876772, 779.9769318855965], [1681.949798042588, 692.2100453384039], [1836.5346787869812, 594.2048612512249], [1949.4897810082625, 522.2759046480503], [1993.1799620561167, 493.68192436876365], [2006.3225368428696, 485.1570109935726], [2011.4084943686266, 480.3718719514265], [2014.4391414086203, 471.8069998818791], [2017.469788448614, 459.4208771966875], [2021.4228063268667, 442.8182021080264], [2022.6087116903425, 433.0674246750032], [2023.399315265993, 431.0909157358769], [2025.244056942511, 429.7732431097927]]}, "attributes": {"trackId": "left_first_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "a739ac4b-54d3-48e4-8478-7c5e92f7a324", "objectId": "c0ca7a4b-d4cf-40b1-aef8-77958a7a4af1", "className": "2D_track", "geometry": {"points": [[5.345579852404898, 1139.6289554668192], [626.4784259642481, 937.213162344719], [1186.2924335566424, 754.3446449144233], [1360.2973771359923, 696.3790486408805], [1492.28813472448, 649.1197869502381], [1592.5630215694891, 611.5661171068379], [1687.5672179101614, 576.3842579903892], [1765.836971899564, 547.2636929539278], [1832.6429740420342, 519.5925678061592], [1895.8912600940769, 494.556787910559], [1930.282515634875, 480.0623890236326], [1937.6614823409466, 475.7140693575547], [1948.2028633496204, 466.0950591871399], [1965.3326074887152, 450.54652219934604], [1980.3596038435626, 435.0095149716398]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "b6752d58-4bd5-4994-b447-6be889db56cc", "objectId": "2f789702-5056-49ea-8fd7-4b38e5a39246", "className": "2D_track", "geometry": {"points": [[5.07677327878637, 1042.1135161596303], [170.4232991339228, 995.8398154443826], [748.3073832673617, 832.7769644121568], [1213.6444983060373, 701.7432622348484], [1429.3175611910574, 634.4286844057459], [1579.9741292617484, 587.0372539140964], [1671.940919277424, 557.3153599926733], [1773.3937942772338, 522.4258609832299], [1862.9488677715194, 490.7519840023067], [1901.3755510123349, 476.60324707681815], [1917.7026285795296, 467.2683838055674], [1940.8455609349885, 451.1119970668506], [1956.8563946400232, 438.73998920386936], [1962.4032946518846, 432.8590103378675]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "c272b523-6c53-4b31-a542-c0b76a210b07", "objectId": "2b277f59-5aba-4e3a-a6e5-1c7ec73628af", "className": "2D_track", "geometry": {"points": [[1481.4807803515253, 2495.7243916334887], [2034.0941709659146, 739.1257762075705], [2089.995734596434, 554.1828905042668], [2109.716220214274, 489.17030055534343], [2110.5830547469263, 484.8361278920819], [2107.7658425158065, 478.33486889718955], [2091.5126950285758, 460.78146961098025], [2078.523305751377, 445.9933302489528], [2075.146902621431, 440.20521059761643], [2074.503778215727, 434.5778720477061], [2075.307683722857, 431.8445933234639], [2077.076275838543, 429.27209570064775]]}, "attributes": {"trackId": "ego_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "44252034-cce6-453c-8910-8a9b54c80e2d", "objectId": "4d8eca35-6c1d-4159-8062-21c2f2c051df", "className": "rgb_track", "geometry": {"points": [[1697.6940378391973, 1598.8718529609541], [2462.6073330906743, 1189.691203687655]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_left_rect", "uri": "S1206062_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "5b3dce74-6f32-4bb5-969b-8f41c7cb5687", "objectId": "9aaac1af-fd50-4e87-9c69-d4c68440e741", "className": "rgb_track", "geometry": {"points": [[1179.7577877572107, 1597.7023638171684], [1814.6619167783147, 1361.259718597823], [2260.1802401895616, 1192.8788439174455], [2462.7756263729925, 1114.6508187475927]]}, "attributes": {"trackId": "left_first_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_left_rect", "uri": "S1206062_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "a89176ac-b0a1-459a-8a3c-b6fb3ef2a8a8", "objectId": "9394e5a5-4e10-40d3-97f1-38d2863fa0e8", "className": "rgb_track", "geometry": {"points": [[0.9615269441112031, 1490.782328847835], [820.1650531369063, 1340.2897542982923], [1653.8745396593645, 1187.2560075008341], [2064.7904123234894, 1113.062481915935], [2328.9924108088576, 1064.429998018095], [2462.7681142942874, 1038.0277324303006]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_left_rect", "uri": "S1206062_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "5ad8a9e6-cb18-4158-8414-068e2850705b", "objectId": "7e78d664-6dd2-4b17-9117-3b4f79f2bf6a", "className": "rgb_track", "geometry": {"points": [[0.973375676160646, 1411.66629468312], [2154.4421919389893, 1075.249559114343], [2255.5205569104205, 1059.6054197985973], [2366.3809025509204, 1040.4539620173637], [2431.4565333652354, 1029.0985064023366], [2462.685987745156, 1023.6095675159935]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_left_rect", "uri": "S1206062_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "9ee274f7-c063-4a66-b184-02eb76d085e6", "objectId": "2f789702-5056-49ea-8fd7-4b38e5a39246", "className": "rgb_track", "geometry": {"points": [[0.18228617103899536, 1071.6870851605834], [196.34242681053047, 1042.0503963145543], [366.58313055707964, 1014.339496473875], [618.364621738675, 971.456430220993], [791.6865348442586, 941.6599109837097], [935.7305485431075, 912.0274595371485], [1031.3587574100368, 891.5582169979011], [1106.0624139634529, 874.5702134668036], [1138.0362066428231, 865.7995270811073], [1150.0965153523837, 861.8335046257143], [1156.78005445836, 858.6702170137303], [1173.7304788658505, 850.7062660675313], [1177.0613374409113, 847.3475553848799], [1178.5977011897958, 842.5921437811903], [1177.7197790475761, 840.1778578900864], [1174.3751985056422, 836.1322469550021], [1166.0280089312532, 829.049568085332]]}, "attributes": {"trackId": "left_second_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "eeea7212-cff8-4a17-b523-b3c2f16a69d0", "objectId": "edd2e6a9-0de8-4081-baa3-4a85789b015e", "className": "rgb_track", "geometry": {"points": [[0.9919881666479418, 1549.0804042409159], [635.1167749034076, 1204.612867249191], [894.2759820805727, 1064.1332960156167], [1151.2358883471509, 918.0928678125239], [1209.1736849650497, 884.460890262369], [1232.2943532571012, 870.2991582966198], [1233.542797048765, 868.8393021180595], [1233.8318623571056, 866.2529401466634], [1233.614593380072, 863.4464593311877], [1231.5574990532073, 856.6623885714913], [1230.026683675694, 845.3685263808782]]}, "attributes": {"trackId": "left_first_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "3eedec8b-6b0f-4a39-b4bc-41bfc941f387", "objectId": "bc96ede0-181a-4b2f-b5c0-cc38209b7afe", "className": "rgb_track", "geometry": {"points": [[-0.023919849634604078, 1351.059408407321], [353.9662095569844, 1215.766658807716], [371.1304190193142, 1208.9230514674648], [424.45353460913617, 1186.096308441622], [586.581805605285, 1122.998025052844], [745.7386816912161, 1060.3541444562288], [836.533912823775, 1024.0733342554736], [901.864552261756, 998.1084389880424], [1007.2122845901209, 953.9732978278183], [1109.0925132218777, 911.4893831405211], [1144.8830735876272, 896.6229767802525], [1173.5766184298927, 884.3325198391425], [1199.0799298938393, 873.1505816500435], [1206.5242556797848, 869.6859633594977], [1211.9589545757835, 866.7127238816331], [1212.8946231809143, 865.6622162947929], [1215.0785654769588, 858.2680903454101], [1215.738103694248, 852.8551631650888], [1216.1863929415165, 845.1993836478916]]}, "attributes": {"trackId": "left_first_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "93192984-67c3-444e-98db-9183d10022d9", "objectId": "c0ca7a4b-d4cf-40b1-aef8-77958a7a4af1", "className": "rgb_track", "geometry": {"points": [[0.09355045728567468, 1112.1310682517997], [194.19386776028125, 1073.866343194241], [380.9003638327005, 1039.2098778035734], [634.1775413304065, 989.1480116096307], [814.7116436690734, 952.3947646335607], [889.2271092927772, 936.0246211466389], [1026.4910491788678, 903.0066924560692], [1123.958441001044, 879.8319355933664], [1161.7488442931465, 868.3358535958287], [1166.2283490824573, 866.9771096021766], [1172.4147190807312, 863.465844042239], [1185.4985377487408, 853.4772166997619], [1191.1369323805438, 845.3668078772386]]}, "attributes": {"trackId": "left_second_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "229b3aac-c30f-424f-9e81-bc099486a458", "objectId": "d8a95754-f499-42f3-a4f7-4fdc375d9741", "className": "rgb_track", "geometry": {"points": [[1569.4467563030828, 1599.0113549693926], [1400.1520119851261, 1149.098552772457], [1347.0118107432909, 1006.0178244791554], [1324.6663399063134, 946.1537614390404], [1315.5730149803705, 922.2135067312662], [1307.7709343941797, 900.2678026828253], [1303.3165651042013, 888.447253426436], [1298.0031450316474, 872.6178453523355], [1297.3261552762115, 870.5523148223498], [1296.285952914028, 869.0782256913335], [1294.8865433308422, 868.1206994097241], [1283.6647996949398, 862.7922763001277], [1273.638113981567, 856.8142979551934], [1268.1670071961612, 853.7800151782487], [1266.8877136194494, 852.8467900953069], [1265.585833449315, 851.6699266608371], [1262.0460316475687, 846.4888788027661]]}, "attributes": {"trackId": "ego_track", "railSide": "rightRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "ab342776-e52b-4622-b1c7-09aaf429dade", "objectId": "20d912d4-275b-4af1-99c1-fbcbc4f8de72", "className": "rgb_track", "geometry": {"points": [[1013.8586324572403, 1597.8954721388798], [1074.47887328636, 1433.1813889440893], [1149.4604076399721, 1225.397633872001], [1210.508256445707, 1055.6377298078028], [1248.6586752454152, 947.922607453482], [1265.6170957954719, 899.8415241708789], [1272.8875729395256, 876.4035884979564], [1274.1648384628043, 871.773219127553], [1274.2630896569026, 869.1103862886732], [1273.064696524136, 867.4608645990032], [1271.315553833952, 865.8680968834275], [1263.6519606942802, 861.4643816958942]]}, "attributes": {"trackId": "ego_track", "railSide": "leftRail", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}], "2D_POLYGON": [{"id": "0f90cffa-2b6b-4e09-8fc2-527769a94e0a", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "2D_signal_pole", "geometry": {"points": [[127.71153737657284, -0.3861000079676791], [127.4762636010818, 328.04436391207815], [115.77703250958459, 334.4789410124016], [115.01063176442402, 411.0810690770479], [172.81920159167288, 415.5183358018615], [174.2731879670706, 333.8939794578268], [146.77999490205218, 330.3842101303776], [147.9547466526339, -0.38610000796767874], [127.71153737657284, -0.3861000079676791]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_left", "uri": "A0001780_image/000_1632321843.100464380.png", "timestamp": "1632321843.100464380"}}, {"id": "1949fa2e-baf2-4d49-98d7-6dd6fa49458d", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "2D_catenary_pole", "geometry": {"points": [[512.0809299028974, 0], [505.77495910572713, 205.17385354249294], [497.20072729722733, 212.82512282789605], [507.127283700637, 221.48304004969694], [547.315035761125, 216.59671445724544], [538.6611785160225, 202.61048178897374], [537.1322264443298, 0.8650673820311914], [512.0809299028974, 0]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "1e84c08e-a0a1-4ba1-b0a0-297d7e10a58a", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "2D_catenary_pole", "geometry": {"points": [[449.8045038171237, 0.4777862850816007], [448.3458460072902, 164.7080127849633], [469.789595777664, 162.6237479377495], [465.2513858132714, 0.4777862850816007], [449.8045038171237, 0.4777862850816007]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "61745003-0a68-4ad3-9495-a9917bac63cc", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "2D_catenary_pole", "geometry": {"points": [[422.8037496259689, -0.2388931425408004], [421.1691548634797, 144.9593612973092], [436.3450881109681, 145.06404701698122], [436.60164577691046, 0.2388931425408003], [422.8037496259689, -0.2388931425408004]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "3d1e6e20-f51a-44c1-9721-6bd37196a669", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "2D_catenary_pole", "geometry": {"points": [[409.82057545965785, 0], [406.39919882866906, 132.9858412563816], [423.226131513968, 131.8623469915388], [421.24031066294435, 0], [409.82057545965785, 0]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "298895cf-5fdb-4c69-96f6-ec6828c6f01d", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "2D_catenary_pole", "geometry": {"points": [[395.8629008832957, -0.3219918630540872], [393.40305776132107, 116.15508672316572], [406.01770020382617, 116.08050674337235], [406.4205548600286, 115.9063554935946], [406.59674871466507, 0.0], [395.8629008832957, -0.3219918630540872]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "41e8e05b-f72f-47b2-ac5a-eb38be544c31", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "2D_catenary_pole", "geometry": {"points": [[384.3938844492276, 30.911218853192374], [385.3594701100145, 110.3174038948946], [395.70644264619784, 110.8829740098017], [395.7403643468395, 110.72344820809218], [393.50694464223807, 31.877194442354636], [384.3938844492276, 30.911218853192374]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "50-75%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "add8316f-90c0-445b-8467-bc56ae3d55f6", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "2D_catenary_pole", "geometry": {"points": [[383.1504165882574, 30.844804203948534], [376.2412852574384, 30.59240121870129], [376.7710454583902, 105.39564002544259], [376.8342796289278, 105.50434150729063], [386.94934177489785, 105.43778451505278], [383.1504165882574, 30.844804203948534]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "34193907-b80b-424b-9ec6-fd0d1e6965a8", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "2D_catenary_pole", "geometry": {"points": [[374.1946290053524, 30.49938335904774], [368.13540779957196, 30.04811647302623], [367.8264341929743, 100.33618792895052], [367.9081997625635, 100.41536301885891], [376.65205686503003, 100.35721176521533], [374.1946290053524, 30.49938335904774]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "27a8c247-7bbb-4b79-a868-783d27036699", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "2D_catenary_pole", "geometry": {"points": [[361.2793866997643, 29.999957149058368], [354.70154716491754, 30.053567176242392], [355.7820615181083, 95.99573637615208], [363.2261794624086, 95.94802522778559], [363.3866050526753, 95.88680928293033], [361.2793866997643, 29.999957149058368]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "422502de-4154-4cf2-b604-afce0aba6187", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "2D_catenary_pole", "geometry": {"points": [[351.64125665066695, 30.188239436424734], [346.04153142738704, 29.356694690560282], [346.14161825086967, 92.26536713305427], [346.22087616968696, 92.3115417619917], [352.6884472192111, 92.26807190232373], [351.64125665066695, 30.188239436424734]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "6026324d-560a-4dd7-a233-57d312ef552e", "objectId": "bb1f0700-9c85-4560-a998-fb3f04eeb296", "className": "2D_catenary_pole", "geometry": {"points": [[343.0333131437304, 29.114267607754385], [338.96089960511915, 29.444904810639283], [339.37616291760537, 88.3712833715512], [339.3963402913408, 88.40596692905395], [345.2122881266227, 88.36713710047184], [343.0333131437304, 29.114267607754385]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "2388337a-f7d3-419f-873c-a42283162c12", "objectId": "3c0bd8ef-d8ce-4e9d-90cf-59fdb64fce7c", "className": "2D_train_front", "geometry": {"points": [[175.0800966023239, 53.26718137932871], [158.01247240932605, 53.41408743373945], [150.77128042272258, 58.65338309013694], [150.66540159428502, 78.74906119770993], [162.96210460359504, 83.37942828553], [174.2934160452979, 84.20193713075852], [175.0800966023239, 53.26718137932871]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "9fd18d88-6997-494a-bac6-f4996166b15e", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "2D_train", "geometry": {"points": [[215.62709625654094, 53.4079993383183], [158.5267011537117, 53.62114305308715], [151.1699117559335, 58.24109469168494], [150.91483764991762, 79.19799837255131], [163.29784681302075, 83.34905062262808], [173.26178034068562, 83.80351855519821], [195.46912552537503, 80.48103820012398], [195.2278216099351, 70.50365846294991], [215.41343786085795, 69.03561364470097], [215.62709625654094, 53.4079993383183]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "occlusion": "50-75%", "type": "regionalExpress"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "ea49b455-4b2c-49e9-9eb8-93a6360d4341", "objectId": "f24c55f7-2ff0-4137-9328-5ae022bfcb07", "className": "2D_train", "geometry": {"points": [[244.30329339815864, 49.494420868450625], [244.47916950238223, 66.35177630391422], [251.74754711251913, 72.15520888347997], [266.4279952950085, 72.52222008804219], [268.1749933416163, 56.16523521172498], [256.86139157780525, 49.93065428147774], [244.30329339815864, 49.494420868450625]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "50-75%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "a8da244f-31c6-43d7-8a1d-2e73b4a4f47a", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "2D_signal_pole", "geometry": {"points": [[250.45201617458773, 50.71780189143546], [250.663243587605, 101.5647481301877], [253.91983037484323, 101.20177344344175], [253.9103725015183, 50.33069510086087], [250.45201617458773, 50.71780189143546]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "af37bdd7-42a8-4dda-938a-f01974eb3f38", "objectId": "369bd0f5-4f00-4fbf-8f12-23c8839ce0e0", "className": "2D_signal_pole", "geometry": {"points": [[272.0669288666686, 59.84162990166313], [270.1984671266271, 59.85450315012786], [270.3111834965438, 75.20745659580398], [272.1792569208072, 75.19540176869818], [272.37307959320896, 75.19005892285799], [272.2608467084985, 59.84370500029018], [272.0669288666686, 59.84162990166313]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "187f64ea-508f-4b8e-b35e-3d2f7c716494", "objectId": "2b692b4e-1950-4d24-a19e-2514bf0529bd", "className": "2D_signal_pole", "geometry": {"points": [[307.12808392483356, 52.728027304047885], [305.2504034131327, 52.74018468727546], [305.17166010392066, 52.74818710334694], [305.30279098057986, 71.82560962914359], [305.3815771712672, 71.8268528365567], [307.2587617104768, 71.81419580625321], [307.12808392483356, 52.728027304047885]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "34d335c9-7931-4beb-b4c8-2573f819dece", "objectId": "cd045b17-320a-43bc-9983-bd93415a4a78", "className": "2D_train_front", "geometry": {"points": [[252.01573441935713, 72.17784138140094], [251.94209865880663, 52.443457553866736], [262.25110513587674, 53.17981515937175], [267.9210586982653, 56.56706014469478], [266.66925076890675, 72.69329170525445], [252.01573441935713, 72.17784138140094]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "3d1eb564-f0db-4178-b457-ca5cdd9bde8e", "objectId": "0fdb210a-34dd-4cee-8684-fe1756bbe023", "className": "2D_signal_pole", "geometry": {"points": [[329.69952912896287, 50.55930329622437], [327.821831092544, 50.57161863603358], [327.7430865286424, 50.579628384710794], [327.87571336357024, 69.65740281894985], [327.954501456596, 69.65863893368864], [329.83170164252846, 69.64582521654027], [329.69952912896287, 50.55930329622437]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "ir_middle", "uri": "A0001781_image/000_1632321843.100464760.png", "timestamp": "1632321843.100464760"}}, {"id": "d57eceac-9803-47d6-a392-5f160460d24d", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "2D_catenary_pole", "geometry": {"points": [[260.47855474249775, 0.39588499833790314], [251.17422598271824, 432.9234603337311], [239.49789164276353, 457.3153107487254], [220.64272580444697, 479.9645771809027], [361.18059413447105, 480.5211708073802], [347.0891214596523, 445.80185811149374], [345.1637373377166, -0.32537194632184674], [260.47855474249775, 0.39588499833790314]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": true, "occlusion": "0-25%"}, "sensor": {"type": "ir_right", "uri": "A0001782_image/000_1632321843.100464620.png", "timestamp": "1632321843.100464620"}}, {"id": "2555948f-980f-460d-ad95-b83ab40ae811", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "2D_signal_pole", "geometry": {"points": [[3175.499278056425, 77.62871536173974], [3147.0569816667157, 977.8463340950655], [3138.133657285401, 981.546321212347], [3127.829201000016, 1054.7674006832856], [3099.1451750410156, 1079.2021026783707], [3206.5808255746283, 1080.0916394031585], [3231.9456231632303, 1069.449425025522], [3210.607115916319, 1063.9684518725182], [3199.5595453318815, 974.5791194612398], [3189.6243929255374, 974.2188286641388], [3220.196192881666, 77.04753740082552], [3175.499278056425, 77.62871536173974]]}, "attributes": {"isTruncatedTop": false, "isTruncatedBottom": false, "structure": "structured", "occlusion": "0-25%"}, "sensor": {"type": "new_left_rect", "uri": "S1213751_image/000_1632321843.100000003.png", "timestamp": "1632321843.100000003"}}, {"id": "49a31b51-46c0-4094-bbf8-df9435bfb7d5", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "2D_catenary_pole", "geometry": {"points": [[3161.3216711175187, 0.39071438968773214], [3009.006971487705, 1.3797708807904194], [2949.1911833070244, 1081.322186667464], [2936.4881750043696, 1217.6786549335786], [2882.7653786733886, 1300.0536093077492], [2890.823798123036, 1356.462545455279], [2933.8020351878204, 1376.160904109972], [3216.7420958643193, 1377.0562840488217], [3219.4282356808685, 1320.6473479012918], [3168.3915791664367, 1292.890569796952], [3134.3671414901487, 1204.2479558508335], [3161.3216711175187, 0.39071438968773214]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "776daba6-3e3a-4236-9346-eb34eda502bd", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "2D_catenary_pole", "geometry": {"points": [[2460.535571748438, -1.1102230246251565e-16], [2435.064153898073, 588.8845489443853], [2427.1419789280126, 689.1102375738657], [2411.792944729856, 730.3102767373376], [2418.659617923768, 742.4279353148295], [2441.2792472684196, 750.1024524139076], [2472.381237617315, 741.2161694570802], [2499.44400844038, 725.0592913537579], [2489.273083685772, 692.0742456247332], [2495.794479401337, -0.9158659817003919], [2460.535571748438, -1.1102230246251565e-16]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "74a911f3-0c7d-4a9f-9373-d12053dd7ff2", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "2D_catenary_pole", "geometry": {"points": [[2351.379911877427, 8.258029543525254], [2327.2599089471564, 590.8180487672728], [2365.8104205974614, 590.4289551087207], [2368.6195265068827, 308.05074388988817], [2369.3205342643532, 7.1489559217262], [2351.379911877427, 8.258029543525254]]}, "attributes": {"structure": "solid", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "51584afe-1823-4aef-acc9-bf5a44264e24", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "2D_catenary_pole", "geometry": {"points": [[2302.833870518539, 54.86842863607177], [2282.1751580680025, 566.2440663274072], [2312.224013635023, 567.7771590017908], [2315.8855226936666, 54.91870297442928], [2302.833870518539, 54.86842863607177]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "5d25a9e6-b556-447b-b2e1-a912aaf2c597", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "2D_catenary_pole", "geometry": {"points": [[2276.794308563372, 157.15285310220145], [2263.395953445272, 525.0447007622206], [2284.741970674504, 528.3616748386789], [2289.3936892563497, 156.99704189099288], [2276.794308563372, 157.15285310220145]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "fd82e8bc-6bda-49bc-a856-25fcce5ae039", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "2D_catenary_pole", "geometry": {"points": [[2233.5462450051723, 342.54137871623647], [2229.9114287099533, 488.4340963104706], [2246.803260768546, 488.8046005162621], [2248.1063543264677, 344.00933406763033], [2233.5462450051723, 342.54137871623647]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "1d010155-3e0f-4bcc-8ad4-1d487af9fbcb", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "2D_catenary_pole", "geometry": {"points": [[2224.9002830684103, 343.0949753667184], [2220.797055477605, 476.46349344092516], [2229.209847062869, 474.05831339475594], [2230.833997993038, 468.0385362889514], [2235.2264259650246, 349.39188881426827], [2236.1139446572506, 342.8267208530416], [2224.9002830684103, 343.0949753667184]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "2c89194d-c278-4aa6-b77c-5d08be32af97", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "2D_catenary_pole", "geometry": {"points": [[2213.6684389743564, 345.7183962734416], [2208.4776777382954, 479.398732333697], [2221.088987182486, 470.63599619487826], [2221.329388307926, 450.2171673707418], [2222.451039164778, 345.469590953596], [2213.6684389743564, 345.7183962734416]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "4d813448-fa73-4a5a-94d7-65c79a48e08c", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "2D_catenary_pole", "geometry": {"points": [[2197.060247618329, 347.3263620106746], [2192.217735808335, 460.83356189552876], [2205.6201539714375, 461.50495187489605], [2205.3826858507173, 347.75680000179125], [2197.060247618329, 347.3263620106746]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "6917d6f6-a8c1-435b-9f65-19c1cc470b3a", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "2D_catenary_pole", "geometry": {"points": [[2168.528997114576, 463.14599972390846], [2179.499616129764, 459.04777040852105], [2180.958241573476, 347.9252443116278], [2172.3906243812958, 347.6935345603534], [2168.528997114576, 463.14599972390846]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "6e429973-8174-4842-8911-484c456aad25", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "2D_catenary_pole", "geometry": {"points": [[2154.1176305225163, 348.5369275080564], [2150.1291352690214, 443.2016067142174], [2159.02448850767, 437.038015617857], [2161.2452872560857, 348.6129699974628], [2154.1176305225163, 348.5369275080564]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "20650b4d-becb-4883-8317-2e66444dd03a", "objectId": "bb1f0700-9c85-4560-a998-fb3f04eeb296", "className": "2D_catenary_pole", "geometry": {"points": [[2138.694639344945, 348.21889060381244], [2137.159956268065, 440.093420014168], [2145.02206010892, 438.87753623368087], [2146.426829249604, 348.32942888482455], [2138.694639344945, 348.21889060381244]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "c2c02cf7-bb56-414a-9bb0-3ac1c90dbb78", "objectId": "3c0bd8ef-d8ce-4e9d-90cf-59fdb64fce7c", "className": "2D_train_front", "geometry": {"points": [[1761.8103863711867, 397.58627900059406], [1748.5089927444635, 416.9783755613854], [1749.1433306906524, 445.10069117576097], [1753.532981400018, 452.8570029466407], [1793.2421688993263, 453.79297374885994], [1792.9042818820558, 394.4957659982409], [1761.8103863711867, 397.58627900059406]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "8820acd4-ab74-4faf-b610-191813c35d61", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "2D_train", "geometry": {"points": [[1850.8851209937593, 396.3158065409051], [1845.8330277800248, 393.18313028325866], [1838.3025485245273, 390.8651595936834], [1761.7218275853063, 397.3226497044466], [1749.2487136928537, 416.6472678582471], [1749.0533043646296, 444.75263261986134], [1753.1906253145128, 453.03237953568686], [1791.2176999996639, 454.1019917279261], [1851.3940794943223, 442.0810721415158], [1850.8851209937593, 396.3158065409051]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "50-75%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "c3d8936e-b6bf-41d7-b1a8-8597b79bddd9", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "2D_signal_pole", "geometry": {"points": [[359.538259994467, 158.0338436212924], [364.2861917548877, 924.1442350235656], [367.6154240860531, 1071.5652241151531], [354.98634441163097, 1073.3693783543565], [350.9682742094059, 1154.409812921063], [319.9360055663172, 1174.009140485119], [381.45611708682634, 1175.6424177821236], [383.089394383831, 1205.585834893876], [419.2662882883578, 1225.5086734896336], [442.4318028416673, 1228.9961428176093], [452.2314666236953, 1223.5518851609272], [454.95359545203644, 1178.364546610465], [457.13129851470933, 1162.031773640418], [430.4544359969664, 1156.0430902180674], [420.1103464492702, 1070.0238192424881], [409.76625690157397, 1068.9349677111518], [403.33643620609973, 558.2177056612384], [405.3362152964708, 155.82304426606285], [359.538259994467, 158.0338436212924]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "32b1c496-884d-40f5-8a2e-881191605a9f", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "2D_signal_pole", "geometry": {"points": [[1952.812801141457, 383.23740479570677], [1953.6868326201968, 467.95600097837627], [1949.0177202631648, 468.1783396620445], [1949.0372574623295, 482.819277520456], [1961.0403577373318, 483.60265218665944], [1961.093687333148, 468.2413656999634], [1956.7663136216402, 467.9426967504117], [1956.7293177507577, 383.31103426974266], [1952.812801141457, 383.23740479570677]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "e5d2d8a0-3577-461d-b75d-f97a3bbd7b9e", "objectId": "369bd0f5-4f00-4fbf-8f12-23c8839ce0e0", "className": "2D_signal_pole", "geometry": {"points": [[1995.473190089172, 402.385831692978], [1995.3229181966692, 433.9762216267264], [1999.1459145633978, 433.9991582972469], [1999.5458025230832, 433.9931805796862], [1999.6980801977081, 402.4162872333341], [1999.2980697084083, 402.4070101665048], [1995.473190089172, 402.385831692978]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "01c7af60-01a5-4f25-b762-367757e8fdd3", "objectId": "0fdb210a-34dd-4cee-8684-fe1756bbe023", "className": "2D_signal_pole", "geometry": {"points": [[2116.6085494834365, 384.9464194831233], [2116.4500685386824, 384.96094201409693], [2116.2206360650357, 424.22596759522906], [2116.3789092553684, 424.23048184904286], [2120.221805659801, 424.25239847821723], [2120.453783549436, 384.96927180512665], [2116.6085494834365, 384.9464194831233]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "b805a7b3-c62c-4875-b7f0-eb92988467f5", "objectId": "f24c55f7-2ff0-4137-9328-5ae022bfcb07", "className": "2D_train", "geometry": {"points": [[1955.5417019593174, 425.28411561646215], [1963.2716206619928, 432.6703250390555], [1985.20125442574, 432.35855270848805], [1992.765084497648, 422.8840250135086], [1992.410292603328, 388.88423881658906], [1989.788318173949, 383.53522604363945], [1983.513255610033, 379.7383397289518], [1961.7090823003775, 379.9404703801594], [1961.7308477005781, 384.9000792389711], [1955.0647407892232, 384.6358109368824], [1955.5417019593174, 425.28411561646215]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "606977bc-894e-467a-9c8b-0a529b47b0a0", "objectId": "52e89909-b83c-48fe-8fa2-f7cc53a30f28", "className": "2D_train_front", "geometry": {"points": [[1955.2459135494394, 424.83995615389836], [1963.2537807897286, 432.637090045759], [1985.0646823520956, 432.637090045759], [1989.2793493206689, 425.8936228960417], [1988.8578826238115, 393.73039812669936], [1970.676872481212, 390.3095187520965], [1955.4373328386232, 393.4617226005362], [1955.2459135494394, 424.83995615389836]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "occlusion": "0-25%", "type": "regionalExpress"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "1819c462-be33-49e1-be47-dab495af8086", "objectId": "2b692b4e-1950-4d24-a19e-2514bf0529bd", "className": "2D_signal_pole", "geometry": {"points": [[2068.2983298784698, 383.6963462983226], [2070.603024393257, 384.0377825227355], [2071.115178729876, 431.155981491712], [2068.3460985066263, 430.98526337950557], [2068.2983298784698, 383.6963462983226]]}, "attributes": {"isTruncatedTop": false, "isTruncatedBottom": false, "structure": "solid", "occlusion": "0-25%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "7348a041-751a-4a34-8e94-bebfa5f07f64", "objectId": "deb1b1ce-a67e-45b6-9d27-a8f4c686fc83", "className": "2D_signal_pole", "geometry": {"points": [[1983.4621916646586, 363.01517739825175], [1983.5316908169596, 378.208559867431], [1985.8717746326101, 380.8165602675596], [1985.5053762339892, 363.0043188659464], [1983.4621916646586, 363.01517739825175]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "50-75%"}, "sensor": {"type": "new_middle_rect", "uri": "S1213752_image/000_1632321843.100000029.png", "timestamp": "1632321843.100000029"}}, {"id": "dff01b20-7660-4c5e-8e43-1f87ddeb84c0", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "2D_catenary_pole", "geometry": {"points": [[171.05932469243663, 1.1049542626990663], [177.5180319389281, 586.9263322272407], [168.56690880097162, 588.8321299278731], [167.87755439940221, 593.1404579423056], [176.71718850099478, 750.0439632455738], [181.45533808383928, 756.6824683635721], [186.41230217455458, 1083.1386809996147], [169.6204688841475, 1139.1828826464723], [155.46379993174136, 1144.5758993902461], [134.56586004961798, 1185.6976520615212], [105.99282202863137, 1186.7953996290837], [105.99282202863137, 1186.7953996290837], [105.55770328138306, 1238.9661003797], [147.3590595915058, 1316.7574230774912], [493.52900649555596, 1296.555049639185], [492.5081717137472, 1215.8659151642896], [482.4389105754769, 1201.3611471453269], [428.2724353582367, 1170.732590372043], [389.1287835165974, 1091.4416033082096], [328.2581061777954, 1.4893114044714075], [171.05932469243663, 1.1049542626990663]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "new_right_rect", "uri": "S1213755_image/000_1632321843.100000008.png", "timestamp": "1632321843.100000008"}}, {"id": "2c1c6858-7b50-40c8-86fe-adda56a23dfc", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "radar_catenary_pole", "geometry": {"points": [[1439.8360152542107, 1349.2173525736712], [1437.6979963389713, 1349.2905427154196], [1437.7930754279191, 1352.067978115298], [1439.9310943431585, 1351.9947879735496], [1439.8360152542107, 1349.2173525736712]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "f11d931e-f0cf-4160-ad11-109118fd20a0", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "radar_catenary_pole", "geometry": {"points": [[1440.8325573607628, 1206.6457597436624], [1438.498512817745, 1206.7411839383046], [1438.6082071714638, 1209.423572832615], [1440.9422517144815, 1209.3281486379728], [1440.8325573607628, 1206.6457597436624]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "68a18db8-7d94-4a3b-bd1e-7806e16ccba9", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "radar_catenary_pole", "geometry": {"points": [[1441.159828954818, 1077.7787671507328], [1438.3903961478536, 1077.8943546477062], [1438.5045708915802, 1080.629851797987], [1441.2740036985447, 1080.5142643010133], [1441.159828954818, 1077.7787671507328]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "a5355498-aa6b-4f42-83e7-e8ccc8eef7bb", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "radar_catenary_pole", "geometry": {"points": [[1438.675457584013, 975.6469676058257], [1438.6723265116416, 978.3219180861761], [1441.2686431819725, 978.3249474824191], [1441.2717742543439, 975.6499970020689], [1438.675457584013, 975.6469676058257]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "951b6b87-5eeb-4a65-9268-ec7e776ecae2", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "radar_catenary_pole", "geometry": {"points": [[1438.450028958643, 890.6810842234358], [1438.4032269690513, 893.3790921444261], [1441.2328313762835, 893.4281769210073], [1441.2796333658753, 890.7301690000171], [1438.450028958643, 890.6810842234358]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "dd4370f5-7ad6-47d5-a4c3-3a1eeb46de24", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "radar_catenary_pole", "geometry": {"points": [[1438.7846032470713, 703.6292833447095], [1438.6535575301843, 706.1475779715754], [1441.2241001270843, 706.2813425413782], [1441.3551458439713, 703.7630479145123], [1438.7846032470713, 703.6292833447095]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "0d3de5e4-79cb-44ed-8a9a-2e6fdcb861f4", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "radar_catenary_pole", "geometry": {"points": [[1439.032751474781, 617.6850921513459], [1438.9724842421797, 620.6127165617786], [1441.7728165709043, 620.6703633962615], [1441.8330838035058, 617.7427389858287], [1439.032751474781, 617.6850921513459]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "07ad037f-53d5-497d-b438-c567f5e6a036", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "radar_catenary_pole", "geometry": {"points": [[1438.9351838479977, 502.8919585036949], [1438.9187453990753, 505.5081567300267], [1441.5551313625306, 505.5247220252571], [1441.5715698114532, 502.90852379892533], [1438.9351838479977, 502.8919585036949]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "f04beb0f-ed2d-4805-ba25-233e7c2c5f09", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "radar_catenary_pole", "geometry": {"points": [[1438.0246136084365, 360.93539776251896], [1438.023625544474, 363.47922596093053], [1440.6537142878606, 363.48024752988204], [1440.654702351823, 360.93641933147046], [1438.0246136084365, 360.93539776251896]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "ba5fc5e5-20b2-467e-8bad-59ccc925cbe5", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "radar_catenary_pole", "geometry": {"points": [[1435.555890820227, 195.3801691569613], [1435.477589095975, 197.94155830314253], [1438.0642417949564, 198.0206323347761], [1438.1425435192084, 195.4592431885951], [1435.555890820227, 195.3801691569613]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "419ec603-be77-4f3b-8d0a-8e7e8856bf34", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "radar_catenary_pole", "geometry": {"points": [[1435.4388286177987, 12.308554035030284], [1432.856373226615, 12.329545178582293], [1432.877704168851, 14.953804625915836], [1435.4601595600348, 14.932813482363827], [1435.4388286177987, 12.308554035030284]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "7ee3ab51-692e-40f0-af97-4b6436f695fc", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "radar_person", "geometry": {"points": [[1428.378582949802, 141.44139601889424], [1428.3767753687102, 143.19232087274622], [1430.3680955589964, 143.1943766278605], [1430.3699031400881, 141.44345177400874], [1428.378582949802, 141.44139601889424]]}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "pose": "upright"}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "7f91cdf6-31b3-4f05-a39d-f57a5b869471", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "radar_person", "geometry": {"points": [[1417.313157999752, 140.03204520672944], [1417.1678673260371, 141.64153691848855], [1418.883982393948, 141.79645260557618], [1419.0292730676629, 140.18696089381706], [1417.313157999752, 140.03204520672944]]}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "pose": "upright"}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "725d0af2-1e89-475e-93d8-b6723658a670", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "radar_signal_pole", "geometry": {"points": [[1405.3035340107976, 1331.583243703627], [1405.1262873486369, 1335.483251093747], [1406.845894123657, 1335.5614033972206], [1407.0231407858178, 1331.6613960071006], [1405.3035340107976, 1331.583243703627]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "2a93ea65-df3c-471b-adab-b9cd84d911cc", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "radar_signal_pole", "geometry": {"points": [[1407.7386742626543, 505.3344603894534], [1406.1137396785648, 505.34934061806985], [1406.1408327649128, 508.30793044431914], [1407.7657673490023, 508.2930502157027], [1407.7386742626543, 505.3344603894534]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "fb78d714-595e-4c01-8b83-f718a70fa8ea", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "radar_train", "geometry": {"points": [[1353.8969115814423, 0], [1353.6554862680664, 22.22337701347442], [1363.6346201502863, 22.33178607854461], [1363.8772231736245, 0], [1353.8969115814423, 0]]}, "attributes": {"type": "regionalExpress", "isTruncatedBack": false, "isTruncatedFront": false}, "sensor": {"type": "radar", "uri": "talker1_Nvt_Polar/000_1632321843.027192325.png", "timestamp": "1632321843.027192325"}}, {"id": "20739d81-832e-465b-8975-db57b2557231", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "2D_signal_pole", "geometry": {"points": [[1840.259739207403, 586.9888686883753], [1811.6729290924995, 587.0475065025781], [1808.6417538101127, 1159.8943985029953], [1802.9428286208936, 1159.5552483670008], [1788.089556181469, 1214.266036416236], [1760.5353391306076, 1220.190854327238], [1757.4814742921417, 1251.7308025257362], [1791.8518732877808, 1255.2724671796257], [1812.6584835879223, 1257.0927025915546], [1823.7926845489806, 1256.7516424984933], [1842.1159710272218, 1258.5117913977], [1861.7986096095499, 1255.93033267796], [1860.5531550914154, 1219.5467457883506], [1845.0172199964084, 1208.6663029254175], [1844.7939075397458, 1204.0047759317044], [1843.1519977575706, 1202.7857223707513], [1843.1850260130368, 1168.093093418962], [1835.9330201528128, 1167.7246516957823], [1840.259739207403, 586.9888686883753]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_left_rect", "uri": "S1206062_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "5fa498c1-a513-48a5-b4fc-97e9f7704492", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "2D_catenary_pole", "geometry": {"points": [[1893.4424824331306, 0], [1858.6798648464548, 1112.012344001047], [1829.4336397702912, 1154.1406460777318], [1869.3833931998035, 1177.436037211573], [1999.8136172985292, 1178.6165248942204], [1987.6324871337947, 1122.787343330701], [1980.2028768997639, 26.37574881710415], [1998.8096351893048, 12.307224256719516], [2000.7497118910667, 0], [1893.4424824331306, 0]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "259463c8-350b-4e73-b1f4-c829497b4ba2", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "2D_catenary_pole", "geometry": {"points": [[1508.0743904136455, 293.92391722087376], [1489.8470865116817, 954.2570966218138], [1482.7593160062936, 966.701407390839], [1491.8602333751983, 968.4488360501778], [1540.880546724689, 969.1809374899037], [1534.4968739958802, 943.1188026151603], [1531.5137972675275, 294.19891781616553], [1508.0743904136455, 293.92391722087376]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "e240a423-0461-4eae-8e4e-b0d5d7b40149", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "2D_catenary_pole", "geometry": {"points": [[1429.7723897481976, 496.662619237473], [1417.9709868701955, 909.093230376385], [1411.5320753476578, 918.8263310683585], [1416.5166950259477, 923.366707442783], [1453.1151543533545, 923.5737291352001], [1447.7000020918176, 908.7124989997703], [1444.9398390080244, 495.9702542972437], [1429.7723897481976, 496.662619237473]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "0b3e2983-709b-4811-9109-a0599dfd9245", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "2D_catenary_pole", "geometry": {"points": [[1396.8963992679667, 575.2184478222009], [1387.198110771021, 898.5691645550961], [1382.1182582713577, 902.6199204767894], [1386.9727077395023, 905.8678710475407], [1413.5115792061276, 906.0784040648047], [1410.4710006527853, 895.4287780368143], [1408.0807642985515, 575.0495539174674], [1396.8963992679667, 575.2184478222009]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "d33935a9-69ca-4f27-930f-af988de9204b", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "2D_catenary_pole", "geometry": {"points": [[1380.5858822870807, 641.6983697593024], [1372.9173806407257, 886.956254705121], [1366.1184506059813, 893.757962795325], [1368.5448170045456, 896.1797661282585], [1368.5815624890067, 896.4323976298501], [1387.1441804742021, 896.1342500068392], [1388.7504025134258, 847.8864898017126], [1389.6163678671542, 641.3912580216855], [1380.5858822870807, 641.6983697593024]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "b29c606f-f052-4757-bc73-8caec454dabc", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "2D_catenary_pole", "geometry": {"points": [[1353.1866786531384, 772.3198800118771], [1351.4303042770193, 878.6133410441508], [1347.7114559198728, 881.7687275290026], [1349.5274529851645, 883.5364012967401], [1365.905690343086, 883.4809782497316], [1366.4438254135714, 883.3489892452908], [1363.8635282997197, 877.1937206689901], [1362.369848189636, 772.2009869317994], [1353.1866786531384, 772.3198800118771]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "a9873243-bf1d-4ccf-bb48-822a4c1e7a42", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "2D_catenary_pole", "geometry": {"points": [[1346.7184344258512, 772.4779383894455], [1343.2037609415133, 873.0914146956605], [1340.7245287034157, 877.1483401761841], [1344.1426523685234, 879.5989168060853], [1351.4071052354504, 878.6390021605228], [1353.243014221133, 772.3708649789136], [1346.7184344258512, 772.4779383894455]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "fb4e7f17-9e7c-4567-99e6-eea9815783c1", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "2D_catenary_pole", "geometry": {"points": [[1346.639018335579, 772.3563863874089], [1338.8878261122436, 772.5878890111999], [1335.8126380447975, 869.7355268460674], [1333.1985057507547, 873.2210365714581], [1335.995231596975, 875.3798050805625], [1336.0677001204238, 875.462194645895], [1343.2241442509455, 873.6698502425534], [1346.639018335579, 772.3563863874089]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "014a9a43-5678-475a-bac1-7ef6ab2b9462", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "2D_catenary_pole", "geometry": {"points": [[1334.7201642209902, 773.1489791986281], [1327.3368229430737, 772.9475646277244], [1325.7978766523484, 866.784206216471], [1324.4659173654507, 871.2677166062334], [1324.9344442617685, 871.6047910468338], [1334.4380235586611, 871.1920195678885], [1334.7201642209902, 773.1489791986281]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "3c4d1086-f870-436d-9cc2-6f9a41017c90", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "2D_catenary_pole", "geometry": {"points": [[1311.3993150654285, 773.1158531759936], [1309.5915561717518, 868.2548236758764], [1318.8212838757086, 867.8958336503314], [1319.511520405738, 773.2738906259249], [1311.3993150654285, 773.1158531759936]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "5697bb72-eb9c-4bd3-ae78-3a1044e8803c", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "2D_catenary_pole", "geometry": {"points": [[1305.011478225215, 773.3600779296899], [1299.2840543664431, 773.5129261735873], [1298.0695224753613, 862.1642920459913], [1297.3760010832505, 865.3256104739148], [1305.9078399853706, 865.4302321086479], [1305.6010459028425, 861.050047335264], [1305.011478225215, 773.3600779296899]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "b79aa3b4-9199-422d-bb75-8b2d3cd37b24", "objectId": "bb1f0700-9c85-4560-a998-fb3f04eeb296", "className": "2D_catenary_pole", "geometry": {"points": [[1295.6368893813826, 773.4737139298494], [1290.0979573973445, 773.6757219432446], [1288.44136710039, 859.6853786568572], [1289.724896708166, 861.5513179486206], [1295.9806358984758, 861.8820928760795], [1295.6368893813826, 773.4737139298494]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "59ef72ab-9075-4118-a7cc-24616bc35cde", "objectId": "66a022bf-5710-4c1e-8ac0-bbe5094700e7", "className": "2D_train_front", "geometry": {"points": [[1072.9385304996163, 820.6029811455239], [1050.4718558615953, 820.4027927827685], [1043.7147226410966, 827.2867279960363], [1044.4540876875944, 850.8642578121305], [1049.39820868585, 855.1893767293404], [1072.4197272011731, 854.3768664434587], [1072.9385304996163, 820.6029811455239]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "b1645d0d-98b1-48ee-a8c9-d17b6fcf1efe", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "2D_train", "geometry": {"points": [[1072.6689459498184, 820.1747921666355], [1050.3523714120151, 820.4913079727004], [1043.9974463072183, 827.1887587012947], [1044.7784866599316, 851.0826168789723], [1049.1892933799331, 855.3816921052442], [1102.8606122685214, 852.9492972403657], [1102.9657291431022, 834.0951862823231], [1113.2977758290815, 833.597256321553], [1113.6284876793097, 820.1066873895556], [1072.6689459498184, 820.1747921666355]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "50-75%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "2a394882-830d-467c-a40c-b608576574b5", "objectId": "f24c55f7-2ff0-4137-9328-5ae022bfcb07", "className": "2D_train", "geometry": {"points": [[1180.2118335044754, 810.5355474241367], [1162.7581072051173, 809.6917479823076], [1162.861055244903, 836.7134372428326], [1173.7164684966679, 841.3222845884613], [1174.5326199885967, 847.0353450319627], [1192.691990684012, 846.1171746035429], [1196.6842597953553, 842.9733566430691], [1197.078804953129, 822.5508002740993], [1195.6525647842336, 818.1074673588365], [1180.2118335044754, 810.5355474241367]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "25-50%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "a11b3ed6-1fd0-4f01-905b-add81b9ac278", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "2D_signal_pole", "geometry": {"points": [[228.66296943462015, 486.47137136167777], [201.9082497217064, 487.5939410164567], [197.85806586618497, 1014.1091048537821], [191.0507152724503, 1015.9243983454448], [161.81016088694605, 1131.3157288916213], [212.8342371724013, 1133.9184753035122], [216.46482415572646, 1167.50140489927], [230.39191376692142, 1168.0511223460135], [281.44722787021277, 1154.9685080629972], [271.11484727694494, 1109.531988235108], [242.84828615744928, 1103.5811332625826], [243.59214302901495, 1035.5182295143231], [230.94657621239847, 1029.195446106015], [242.98583524516027, 499.6333156130868], [228.66296943462015, 486.47137136167777]]}, "attributes": {"structure": "structured", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "460a9a22-dcc2-4a69-b30c-f8ac34147443", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "2D_signal_pole", "geometry": {"points": [[1174.5201235779195, 805.6413131288666], [1174.0986185622764, 863.024831660618], [1172.7726088699412, 874.8375648473062], [1179.5725487393338, 874.7113980121052], [1178.3626803810962, 862.7740044948051], [1178.2266272486443, 805.6539068430958], [1174.5201235779195, 805.6413131288666]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "ff25a847-6cbd-48e6-9a8d-b0b46b348bfc", "objectId": "369bd0f5-4f00-4fbf-8f12-23c8839ce0e0", "className": "2D_signal_pole", "geometry": {"points": [[1200.839457398577, 827.1698696378571], [1200.9146590862135, 846.8777719022014], [1202.6704232185214, 846.9594214731007], [1202.6412082104782, 827.1219885063941], [1200.839457398577, 827.1698696378571]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "ffe1367a-b06d-4f19-a41e-552c266eb67e", "objectId": "0fdb210a-34dd-4cee-8684-fe1756bbe023", "className": "2D_signal_pole", "geometry": {"points": [[1247.0668039463897, 816.077653590104], [1244.8856102492666, 816.050415878094], [1244.7770882102332, 841.1700414984541], [1247.1617559968734, 841.2594590709483], [1247.0668039463897, 816.077653590104]]}, "attributes": {"structure": "solid", "isTruncatedTop": false, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "840490c7-1173-43bf-83ad-a2dfe271bcac", "objectId": "d6851e35-b38f-4cf7-8f82-6f06f68aa1d4", "className": "2D_train_front", "geometry": {"points": [[1175.9219196013369, 846.7175436160134], [1173.6216727900107, 841.1312299313639], [1173.7038244618439, 823.8793788464169], [1179.4544414901595, 820.3468569575945], [1192.3522539679532, 820.4290086294276], [1196.7884442469394, 823.96153051825], [1196.7884442469394, 843.5957800863563], [1192.5987089834523, 846.2246335850149], [1175.9219196013369, 846.7175436160134]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "d90bb76b-f337-4685-829e-1ba2225c819a", "objectId": "3c0bd8ef-d8ce-4e9d-90cf-59fdb64fce7c", "className": "rgb_train_front", "geometry": {"points": [[1032.7564941648475, 747.4876390993209], [1025.5478894994296, 754.632663325361], [1027.6673629744796, 764.9126972541657], [1030.9139595433585, 767.7306314423582], [1056.0484706582115, 767.8946663065118], [1052.185820996805, 746.2196649083835], [1032.7564941648475, 747.4876390993209]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "type": "regionalExpress", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "c41eb197-3979-4c3f-b59a-c09064e46152", "objectId": "7c14c7ad-9feb-47a3-ab8a-900b5fd3b8ab", "className": "rgb_train_front", "geometry": {"points": [[1156.515907286252, 756.5792368064997], [1162.0624564008249, 759.3936355712935], [1175.8285338773094, 759.2957899030495], [1178.0606383720615, 756.8117668582964], [1175.754832730991, 745.0632667390316], [1164.0705125143288, 743.895514338119], [1154.6569095449531, 745.114694681854], [1156.515907286252, 756.5792368064997]]}, "attributes": {"isTruncatedBack": false, "isTruncatedFront": false, "occlusion": "0-25%", "type": "regionalExpress"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "5a9b494a-f4e9-4c74-b7a4-b4ab386c68fa", "objectId": "82d92a87-bd0e-4c69-bc10-53926b40c7be", "className": "2D_signal_pole", "geometry": {"points": [[906.2350607180429, 894.6548179292444], [906.2350607180429, 883.6813745992408], [910.807328772211, 883.4527611965324], [911.2645555776278, 811.6681527460926], [915.6082102290875, 811.4395393433841], [915.8368236317959, 883.2241477938239], [919.5449764694055, 883.4527611965324], [919.7232514778389, 894.8330929376777], [906.2350607180429, 894.6548179292444]]}, "attributes": {"isTruncatedTop": false, "isTruncatedBottom": false, "structure": "solid", "occlusion": "0-25%"}, "sensor": {"type": "rgb_middle_rect", "uri": "S1206063_image/000_1632321843.100000064.png", "timestamp": "1632321843.100000064"}}, {"id": "30a024aa-1012-49cb-8865-e2606d035e27", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "2D_catenary_pole", "geometry": {"points": [[66.51462399122317, 0], [75.79216260590809, 12.762761285326386], [51.26148437989676, 1154.4692403477159], [17.126272200689662, 1199.605467125662], [15.892545424294273, 1222.9151708184852], [56.39501786410631, 1255.791825491923], [242.65672996415782, 1247.3969234756282], [241.0830834447857, 1218.9545261157598], [206.37589510102717, 1206.1320913955078], [182.1347690792257, 1142.9065395710345], [158.2322835266631, 1.1102230246251565e-16], [66.51462399122317, 0]]}, "attributes": {"structure": "structured", "isTruncatedTop": true, "isTruncatedBottom": false, "occlusion": "0-25%"}, "sensor": {"type": "rgb_right_rect", "uri": "S1206064_image/000_1632321843.100000040.png", "timestamp": "1632321843.100000040"}}], "3D_BOUNDING_BOX": [{"id": "13478f94-d556-4f64-a72b-47662e94988e", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "3D_catenary_pole", "geometry": {"size": {"height": 12.887274595406309, "width": 0.9726718154765793, "length": 0.7487449536720978}, "center": {"x": 27.075067129430366, "y": -3.7850908693727328, "z": 6.671744016124283}, "quaternion": {"x": -7.710484478135359e-17, "y": -7.978951661384013e-17, "z": 0.7191010536652217, "w": 0.6949055148849863}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "f4241163-2e06-4bea-ba41-1e1c53a0d205", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "3D_catenary_pole", "geometry": {"size": {"height": 11.32591768831262, "width": 0.939620949660413, "length": 0.8177564977894478}, "center": {"x": 76.98025553964469, "y": -3.9908722013536506, "z": 5.664758071751996}, "quaternion": {"x": 0.007288131704766536, "y": -0.0066217671626138, "z": 0.7213715704319794, "w": 0.6924782254399036}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "af8bc1d1-fd49-4c06-9dca-f2ba7ebc7e0c", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "3D_catenary_pole", "geometry": {"size": {"height": 11.387714043893492, "width": 0.958257601454503, "length": 0.9701948653735709}, "center": {"x": 122.07535013383624, "y": -4.08383238353245, "z": 5.694251848572822}, "quaternion": {"x": 0.00369050768501779, "y": -0.003449208351798712, "z": 0.7216906047044083, "w": 0.6921973376112901}}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "1d158fb8-2d57-4a3d-94b5-07acccd5aefd", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "3D_catenary_pole", "geometry": {"size": {"height": 11.381109367137512, "width": 0.9362333565610912, "length": 0.9087736685183327}, "center": {"x": 157.85679712362287, "y": -4.123633777940086, "z": 5.689919294379574}, "quaternion": {"x": 0.004022454964914071, "y": -0.004251513903410736, "z": 0.7066813725292935, "w": 0.7075077258982393}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "36773490-19a5-4894-be95-ffd3ae1eba3f", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "3D_catenary_pole", "geometry": {"size": {"height": 10.49502347160944, "width": 0.9444448385557216, "length": 0.9905608549058221}, "center": {"x": 187.58179664895056, "y": -4.09161786056079, "z": 5.247033517550782}, "quaternion": {"x": 0.00353253715396303, "y": -0.0035943470674366512, "z": 0.7009389355960713, "w": 0.7132034845791636}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "a7c91545-2bb1-4342-94b3-cb2e638f0d5a", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "3D_catenary_pole", "geometry": {"size": {"height": 9.582128549471806, "width": 0.8825956882587561, "length": 0.9009072204283313}, "center": {"x": 253.06564046993464, "y": -4.201523090477218, "z": 4.791064274735957}, "quaternion": {"x": -6.154728725788275e-16, "y": 4.872960476144086e-16, "z": 0.6884884999636752, "w": 0.7252472581249572}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "0bfdf09d-7f99-4dae-a51a-edfea7a2a1e3", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "3D_catenary_pole", "geometry": {"size": {"height": 9.122189306988608, "width": 1.0248856333348373, "length": 0.9803239657538195}, "center": {"x": 283.0877952791687, "y": -4.340974407994947, "z": 4.561094653494363}, "quaternion": {"x": -7.190320005376218e-16, "y": 6.943797625527996e-16, "z": 0.6997923436831516, "w": 0.7143463275767867}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "e0d5d414-358a-4c9c-82a1-dbd489a9cc8c", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "3D_catenary_pole", "geometry": {"size": {"height": 9.166395843538007, "width": 0.915687454485901, "length": 0.9227533019559514}, "center": {"x": 323.32708090743336, "y": -4.285805161842478, "z": 4.5831979217690755}, "quaternion": {"x": -8.244463681442386e-16, "y": -8.19282370258064e-16, "z": 0.7048818303081658, "w": 0.7093247530584353}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "47135a62-ec1d-434d-aa3a-f90d41217e35", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "3D_catenary_pole", "geometry": {"size": {"height": 9.37049688364681, "width": 0.8903399366055378, "length": 0.9205311296242478}, "center": {"x": 373.02726207382983, "y": -3.9687073818519916, "z": 4.685248441823489}, "quaternion": {"x": -1.7061176000032221e-15, "y": -1.5484762827801308e-15, "z": 0.7069694420116025, "w": 0.7072440936917066}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "6f900dfc-f616-484e-a084-8ff2ec190eeb", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "3D_catenary_pole", "geometry": {"size": {"height": 9.776664883398379, "width": 0.8969049983018594, "length": 0.905751372471689}, "center": {"x": 430.9548597389459, "y": -3.0835232076570875, "z": 4.888332441699285}, "quaternion": {"x": -7.965776958244105e-17, "y": -7.725984220867584e-17, "z": 0.6962198832179828, "w": 0.7178285827493486}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "24bbde15-c191-4123-99d4-6886942104fc", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "3D_catenary_pole", "geometry": {"size": {"height": 10.407967291307775, "width": 0.9185211485654554, "length": 0.903889245569542}, "center": {"x": 495.0290872343344, "y": -2.155393237663728, "z": 5.203983645653888}, "quaternion": {"x": 0.0, "y": 0.0, "z": 0.709974681893895, "w": 0.7042272013133707}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "daf6c303-3afa-4e3d-869d-f3e6ad8309cf", "objectId": "bb1f0700-9c85-4560-a998-fb3f04eeb296", "className": "3D_catenary_pole", "geometry": {"size": {"height": 10.695883613948222, "width": 0.9155471412683369, "length": 0.9164999475445299}, "center": {"x": 558.1598656891119, "y": -1.3400174508002798, "z": 5.520953609346796}, "quaternion": {"x": 0.0, "y": 0.0, "z": 0.7076205615691006, "w": 0.7065926272221008}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "817ed6c3-8552-42dd-b858-00bfa37ab8fa", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "3D_signal_pole", "geometry": {"size": {"height": 4.7988247505808035, "width": 0.6024836240895444, "length": 1.3664115660093574}, "center": {"x": 33.04968675735167, "y": 7.673850076470425, "z": 2.215776549933256}, "quaternion": {"x": -1.1110812999709735e-16, "y": -2.5183074152334238e-18, "z": 0.9997421784990658, "w": 0.022706310355101483}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "6cdb49ee-118b-46c4-a17b-ab40bc4544a6", "objectId": "418cad49-3c39-4fce-ae3e-c4b6db04499d", "className": "3D_catenary_pole", "geometry": {"size": {"height": 11.573852501573896, "width": 0.9344866445474989, "length": 0.922698681194263}, "center": {"x": -28.19481220330679, "y": -3.8022877825654664, "z": 5.786926250786943}, "quaternion": {"x": -7.875897163939664e-17, "y": -7.824608558471171e-17, "z": 0.7047931256242187, "w": 0.709412891109856}}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "059f694f-3215-41ba-94b0-74a9cd42b898", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "3D_signal_pole", "geometry": {"size": {"height": 4.850638266050393, "width": 0.5687509502517719, "length": 1.0355498563207401}, "center": {"x": 322.4125816040898, "y": 7.371086270175778, "z": 2.425319133025268}, "quaternion": {"x": -1.1102579350230775e-16, "y": 5.083023179483528e-19, "z": 0.9999895182832635, "w": -0.004578572223589586}}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "9955441a-e315-49f7-85b4-cd8f1b3d0ba0", "objectId": "369bd0f5-4f00-4fbf-8f12-23c8839ce0e0", "className": "3D_signal_pole", "geometry": {"size": {"height": 2.7345535172334916, "width": 0.33526370122502774, "length": 0.30661435304516976}, "center": {"x": 631.3371213354337, "y": 10.956067406496267, "z": 3.392726206831182}, "quaternion": {"x": 0.0, "y": 0.0, "z": 0.9988417079216503, "w": 0.04811696702994255}}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "e7cbd2d8-16e4-46e0-9717-3c72d3c07996", "objectId": "0fdb210a-34dd-4cee-8684-fe1756bbe023", "className": "3D_signal_pole", "geometry": {"size": {"height": 3.560437779695704, "width": 0.35103706460382006, "length": 0.3207473820126947}, "center": {"x": 661.3799851877856, "y": 5.1146951761514226, "z": 4.615285122469505}, "quaternion": {"x": -4.800271803484849e-17, "y": -2.3540158457003534e-15, "z": 0.9996413941137189, "w": -0.02677840873503449}}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "72869011-d5c7-4857-8fc9-d4eb8ecba975", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "3D_train", "geometry": {"size": {"height": 4.388681071807479, "width": 3.4929029519399566, "length": 124.19055690880346}, "center": {"x": 554.1041190314115, "y": 23.599589571325012, "z": 2.93090209799623}, "quaternion": {"x": -0.0023581123241454573, "y": 1.2808386647010278e-05, "z": 0.9999824686485337, "w": 0.005431531809378548}}, "attributes": {"type": "regionalExpress"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "7bf23c73-a99b-4389-ac43-d3496cc34fff", "objectId": "f24c55f7-2ff0-4137-9328-5ae022bfcb07", "className": "3D_train", "geometry": {"size": {"height": 4.601779463996735, "width": 3.076443460527172, "length": 99.78402437119297}, "center": {"x": 759.509779717632, "y": 16.853355448605836, "z": 5.033459101870372}, "quaternion": {"x": -9.987652734909354e-05, "y": -0.011218637362343636, "z": 0.025316665088441877, "w": 0.9996165258083777}}, "attributes": {"type": "regionalExpress"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "875b494e-dbf6-4bb2-b5e6-d70b65fb5dbe", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "3D_person", "geometry": {"size": {"height": 2.630954167344089, "width": 0.6128240254104295, "length": 0.6969624379982348}, "center": {"x": 449.98873978681786, "y": -0.4806687390397098, "z": 1.794378919126175}, "quaternion": {"x": 1.5745208333913983e-15, "y": 3.1747009348730734e-16, "z": 0.706741693498347, "w": 0.7074716804728571}}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "pose": "upright"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "ee6e9de9-8854-4467-9f5c-a95073ae24ad", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "3D_person", "geometry": {"size": {"height": 2.5076941810839717, "width": 0.5656126646266026, "length": 0.6030825814604774}, "center": {"x": 450.4800128828465, "y": 3.4655004311025137, "z": 1.6427163119620496}, "quaternion": {"x": 4.991145492370739e-16, "y": 6.058734340669616e-16, "z": 0.7382091783501197, "w": -0.6745718708926732}}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "pose": "upright"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "b44b68c1-4e80-46f1-b822-00f453b1c227", "objectId": "e07310f7-02df-4d05-ac3c-6296117d27ea", "className": "3D_track", "geometry": {"size": {"height": 0.4700600559062381, "width": 1.7711582211586314, "length": 122.27286466409521}, "center": {"x": 61.44839751510897, "y": -0.11227183531167707, "z": -0.13687736210234927}, "quaternion": {"x": 9.864317113664832e-20, "y": -1.1102244878784408e-16, "z": -0.0009622825642576772, "w": 0.9999995370060261}}, "attributes": {"trackId": "ego_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "23b469d0-d2c0-4158-9794-29442f5288c1", "objectId": "fa0f2957-29a5-404c-95f1-45c44ff58103", "className": "3D_track", "geometry": {"size": {"height": 0.47775738489026043, "width": 1.819192891169864, "length": 127.96014613884917}, "center": {"x": 46.84626713678596, "y": 4.685932623267282, "z": -0.20159324395631406}, "quaternion": {"x": 7.014288024687007e-16, "y": -1.111171138321498e-16, "z": -0.00013516746707870067, "w": 0.9999999908648778}}, "attributes": {"trackId": "left_first_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "42116b2f-48f4-423e-9219-a8c6961ede1a", "objectId": "03a3625e-9082-41ec-8b44-24e92b7286eb", "className": "3D_track", "geometry": {"size": {"height": 0.612889360533506, "width": 1.846216879806486, "length": 137.81025130113468}, "center": {"x": 52.05160121842591, "y": 10.166373407059307, "z": 0.07494369318203808}, "quaternion": {"x": 1.9652476270442384e-20, "y": -1.1102230768065455e-16, "z": -0.00017701378071000032, "w": 0.9999999843330606}}, "attributes": {"trackId": "left_second_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "cff15e77-6e63-4d8c-b67b-ac45c96cd684", "objectId": "e4366b9f-a350-42a9-8ac2-61eeeee5513c", "className": "3D_signal_pole", "geometry": {"size": {"height": 4.212558513580023, "width": 1.1643567979785625, "length": 0.8081109969363183}, "center": {"x": 204.02671957014238, "y": 16.30381415441869, "z": 1.9281309299048264}, "quaternion": {"x": 1.080383758932362e-15, "y": 6.401082754169111e-16, "z": 0.7179686714245058, "w": -0.6960754175036853}}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}], "3D_SEGMENTATION": [{"id": "13478f94-d556-4f64-a72b-47662e94988e", "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [39814, 39815, 39816, 39817, 39818, 39819, 39820, 39821, 39822, 39823, 39824, 39825, 39826, 39827, 39828, 39829, 39830, 39831, 39832, 39833, 39834, 39835, 39836, 39837, 39838, 39839, 39840, 39841, 39842, 39843, 39844, 39845, 39846, 39847, 39848, 39849, 39850, 39851, 39852, 39853, 39855, 39858, 39859, 39861, 39864, 39867, 39870, 39871, 39873, 39875, 39876, 39877, 39879, 39881, 39882, 39883, 39884, 39885, 39886, 39887, 39888, 39889, 39890, 39891, 39892, 39893, 39894, 39895, 39896, 39897, 39898, 39899, 39900, 39901, 39902, 39903, 39904, 39905, 39906, 39907, 39908, 39909, 39910, 39911, 39912, 39913, 39914, 40184, 40185, 40186, 40187, 40189, 40190, 40191, 40192, 40193, 40194, 40195, 40196, 40197, 40198, 40199, 40200, 40201, 40202, 40203, 40204, 40205, 40206, 40207, 40208, 40209, 40210, 40211, 40212, 40213, 40214, 40215, 40216, 40217, 40218, 40220, 40221, 40222, 40223, 40224, 40226, 40227, 40228, 40229, 40230, 40232, 40233, 40234, 40235, 40237, 40238, 40239, 40240, 40241, 40242, 40243, 40244, 40245, 40246, 40247, 40248, 40249, 40250, 40251, 40252, 40253, 40254, 40255, 40256, 40257, 40258, 40259, 40260, 40261, 42607, 42608, 42609, 42610, 42611, 42612, 42613, 42614, 42615, 42616, 42617, 42618, 42619, 42620, 42621, 42622, 42623, 42624, 42625, 42626, 42627, 42628, 42629, 42630, 42631, 42632, 42633, 42634, 42635, 42636, 42637, 42638, 42640, 42641, 42643, 42644, 42646, 42647, 42649, 42650, 42652, 42653, 42655, 42656, 42657, 42658, 42659, 42661, 42663, 42664, 42665, 42666, 42667, 42668, 42669, 42670, 42671, 42672, 42673, 42674, 42675, 42676, 42677, 42678, 42679, 42680, 42681, 42682, 42683, 42684, 42685, 42686, 42687, 42688, 42689, 42690, 42691, 42692, 42693, 42696, 42697, 43790, 43791, 43792, 43793, 43794, 43795, 43796, 43797, 43798, 43799, 43800, 43801, 43802, 43803, 43804, 43805, 43806, 43807, 43808, 43809, 43810, 43811, 43812, 43813, 43814, 43815, 43816, 43817, 43818, 43819, 43820, 43821, 43822, 43823, 43824, 43825, 43827, 43828, 43829, 43830, 43834, 43835, 43836, 43840, 43841, 43846, 43847, 43850, 43852, 43853, 43854, 43855, 43856, 43857, 43858, 43859, 43860, 43861, 43862, 43863, 43864, 43865, 43866, 43867, 43868, 43869, 43870, 43871, 43872, 43873, 43874, 43875, 43876, 43877, 43878, 43879, 43880, 43881, 43882, 43883, 44264, 44265, 44266, 44267, 44268, 44269, 44270, 44271, 44272, 44273, 44274, 44275, 44276, 44277, 44278, 44279, 44280, 44281, 44282, 44283, 44284, 44285, 44286, 44287, 44288, 44289, 44290, 44291, 44292, 44294, 44295, 44296, 44297, 44298, 44300, 44301, 44302, 44303, 44304, 44306, 44307, 44308, 44309, 44310, 44312, 44313, 44314, 44315, 44316, 44317, 44318, 44319, 44320, 44321, 44322, 44323, 44324, 44325, 44326, 44327, 44328, 44329, 44330, 44331, 44332, 44333, 44334, 44335, 44336, 44337, 46721, 46722, 46723, 46724, 46725, 46726, 46727, 46728, 46729, 46730, 46731, 46732, 46733, 46734, 46735, 46736, 46737, 46738, 46739, 46740, 46741, 46742, 46743, 46744, 46745, 46746, 46747, 46748, 46749, 46750, 46751, 46752, 46753, 46754, 46755, 46756, 46757, 46758, 46760, 46764, 46765, 46766, 46771, 46772, 46777, 46778, 46783, 46784, 46785, 46789, 46790, 46791, 46795, 46796, 46797, 46798, 46799, 46800, 46801, 46802, 46803, 46804, 46805, 46806, 46807, 46808, 46809, 46810, 46811, 46812, 46813, 46814, 46815, 46816, 46817, 46818, 46819, 46820, 46822, 46823, 46824, 46825, 46826, 46828, 46829, 46830, 46831, 46832, 46834, 46835, 46836, 46837, 46838, 46840, 46841, 46842, 46843, 46844, 46846, 46847, 46848, 46849, 46850, 46853, 46854, 46855, 46856, 46859, 46860, 46865, 46866, 46872, 47855, 47856, 47857, 47858, 47859, 47860, 47861, 47862, 47863, 47864, 47865, 47866, 47867, 47868, 47869, 47870, 47871, 47872, 47873, 47874, 47875, 47876, 47877, 47878, 47879, 47880, 47881, 47882, 47883, 47884, 47885, 47886, 47887, 47889, 47891, 47892, 47895, 47897, 47898, 47901, 47903, 47904, 47906, 47907, 47908, 47909, 47910, 47911, 47912, 47913, 47914, 47915, 47916, 47917, 47918, 47919, 47920, 47921, 47922, 47923, 47924, 47925, 47926, 47927, 47928, 47929, 47930, 47931, 47932, 47933, 47934, 47935, 47936, 47938, 48600, 48601, 48602, 48603, 48604, 48605, 48606, 48607, 48608, 48609, 48610, 48611, 48612, 48613, 48614, 48615, 48616, 48617, 48618, 48619, 48620, 48621, 48622, 48623, 48624, 48625, 48626, 48627, 48628, 48629, 48630, 48633, 48634, 48636, 48639, 48640, 48642, 48644, 48645, 48646, 48648, 48650, 48651, 48652, 48653, 48654, 48655, 48656, 48657, 48658, 48659, 48660, 48661, 48662, 48663, 48664, 48665, 48666, 48667, 48668, 48669, 48670, 48671, 48672, 48673, 48674, 48675, 48676, 48677, 49568, 49569, 49570, 49571, 49572, 49573, 49574, 49575, 49576, 49577, 49578, 49579, 49580, 49581, 49582, 49583, 49584, 49585, 49586, 49587, 49588, 49589, 49590, 49591, 49592, 49593, 49594, 49595, 49596, 49597, 49598, 49599, 49600, 49601, 49602, 49603, 49604, 49605, 49606, 49607, 49608, 49609, 49610, 49611, 49612, 49613, 49614, 49615, 49616, 49617, 49618, 49619, 49620, 49621, 49622, 49623, 49624, 49625, 49626, 49627, 49628, 49629, 49630, 49631, 49632, 49633, 49634, 49635, 49636, 49637, 49638, 49639, 49640, 49641, 49642, 49643, 49644, 49645, 49646, 49647, 49648, 49649, 49650, 49651, 49652, 49653, 49654, 49655, 49656, 49657, 49658, 49659, 49660, 49661, 49662, 49663, 49664, 49665, 49666, 49667, 49668, 49669, 49670, 49671, 49672, 49673, 49674, 49675, 49676, 49677, 49678, 49679, 49680, 49681, 49682, 49683, 49684, 49685, 49686, 49687, 49688, 49689, 49690, 49691, 49692, 49693, 49694, 49695, 49696, 49697, 49698, 49699, 49700, 49701, 49702, 49703, 49704, 49705, 49706, 49707, 49708, 51094, 51095, 51096, 51097, 51098, 51099, 51100, 51101, 51102, 51103, 51104, 51105, 51106, 51107, 51108, 51109, 51110, 51111, 51112, 51113, 51114, 51115, 51116, 51117, 51118, 51119, 51120, 51121, 51122, 51123, 51124, 51125, 51127, 51128, 51129, 51130, 51131, 51134, 51135, 51136, 51137, 51140, 51141, 51142, 51143, 51146, 51147, 51148, 51149, 51153, 51154, 51155, 51159, 51160, 51161, 51166, 51167, 51172, 51173, 51178, 51179, 51185, 51191, 51197, 52158, 52159, 52160, 52161, 52162, 52163, 52164, 52165, 52166, 52167, 52168, 52169, 52170, 52171, 52172, 52173, 52174, 52175, 52176, 52177, 52178, 52179, 52180, 52181, 52182, 52183, 52184, 52185, 52186, 52190, 52191, 52192, 52193, 52196, 52197, 52198, 52199, 52202, 52203, 52204, 52205, 52207, 52208, 52209, 52210, 52211, 52212, 52213, 52214, 52215, 52216, 52217, 52218, 52219, 52220, 52221, 52222, 52223, 52224, 52225, 52226, 52227, 52228, 52229, 52230, 52231, 52232, 52234, 52235, 52240, 52984, 52985, 52986, 52987, 52988, 52989, 52990, 52991, 52992, 52993, 52994, 52995, 52996, 52997, 52998, 52999, 53000, 53001, 53002, 53003, 53004, 53005, 53006, 53007, 53008, 53009, 53010, 53011, 53012, 53013, 53014, 53018, 53020, 53024, 53025, 53030, 53031, 53036, 53037, 53041, 53043, 53044, 53045, 53046, 53047, 53048, 53049, 53050, 53051, 53052, 53053, 53054, 53055, 53056, 53057, 53058, 53059, 53060, 53061, 53062, 53063, 53064, 53065, 53066, 53067, 53068, 53069, 53070, 53071, 53072, 53709, 53710, 53711, 53712, 53713, 53714, 53715, 53716, 53717, 53718, 53719, 53720, 53721, 53722, 53723, 53724, 53725, 53726, 53727, 53728, 53729, 53730, 53731, 53732, 53733, 53734, 53735, 53736, 53737, 53738, 53739, 53740, 53741, 53742, 53743, 53744, 53745, 53746, 53747, 53748, 53749, 53750, 53751, 53752, 53753, 53754, 53755, 53756, 53757, 53758, 53759, 53760, 53761, 53762, 53763, 53764, 53765, 53766, 53767, 53768, 53769, 53770, 53771, 53772, 53774, 53775, 53776, 53777, 53778, 53780, 53781, 53782, 53783, 53784, 53785, 53786, 53787, 53788, 53789, 53790, 53791, 53792, 53793, 53794, 53795, 53796, 53797, 53798, 53799, 53800, 53801, 53802, 53803, 53804, 53805, 53806, 53807, 53808, 53809, 53810, 53811, 53812, 53813, 53814, 53815, 53816, 53817, 53818, 53819, 53820, 53821, 53822, 53823, 53824, 53825, 53826, 53827, 53828, 53829, 53830, 53831, 53832, 53833, 53834, 53835, 53836, 53837, 53838, 53839, 53840, 53841, 53842, 53843, 53844, 53845, 53846, 53847, 53848, 53849, 53850, 53851, 53852, 53853, 53854, 53855, 53856, 53857, 53858, 53859, 53860, 53861, 53862, 53863, 53864, 53866, 53869, 53873, 53937, 53943, 53944, 53945, 53946, 53949, 53950, 53951, 53952, 53953, 53954, 53955, 53956, 53957, 53958, 53959, 53960, 53961, 53962, 53963, 53964, 53965, 53966, 53967, 53968, 53969, 53970, 53971, 53972, 53973, 53974, 53975, 53976, 53977, 53978, 53979, 53980, 53981, 53982, 53983, 53984, 53985, 53986, 53987, 53988, 53989, 53990, 53991, 53992, 53993, 53994, 53995, 53996, 53997, 53998, 53999, 54000, 54001, 54002, 54003, 54004, 54005, 54006, 54007, 54008, 54009, 54010, 54011, 54012, 54013, 54014, 54016, 54018, 54019, 54020, 54021, 54022, 54023, 54025, 54026, 54027, 54028, 54029, 54031, 54032, 54034, 54035, 54038, 54040, 54041, 54044, 54046, 54047, 54050, 54052, 54053, 54054, 54056, 54058, 54059, 54060, 54061, 54062, 54063, 54064, 54065, 54066, 54067, 54068, 54069, 54070, 54071, 54072, 54073, 54074, 54075, 54076, 54077, 54078, 54079, 54080, 54081, 54082, 54083, 54084, 54085, 54086, 54087, 54088, 54089, 54090, 54091, 54092, 54093, 54094, 54095, 54096, 54097, 54098, 54099, 56456, 56457, 56458, 56459, 56460, 56461, 56462, 56463, 56464, 56465, 56466, 56467, 56468, 56469, 56470, 56471, 56472, 56473, 56474, 56475, 56476, 56477, 56478, 56479, 56480, 56483, 56484, 56485, 56486, 56489, 56490, 56491, 56492, 56493, 56495, 56496, 56497, 56498, 56499, 56500, 56501, 56502, 56503, 56504, 56505, 56506, 56507, 56508, 56509, 56510, 56511, 56512, 56513, 56514, 56515, 56516, 56517, 56518, 56519, 56520, 56521, 56522, 56523, 56524, 56525, 56526, 56527, 56528, 56531, 56532, 56533, 57248, 57253, 57254, 57260, 57261, 57262, 57266, 57267, 57268, 57272, 57273, 57274, 57278, 57279, 57280, 57284, 57285, 57286, 57289, 57290, 57291, 57292, 57295, 57296, 57301, 57302, 57305, 57307, 57311, 57313, 57319, 57325, 57326, 57327, 57328, 57331, 57332, 57333, 57334, 57336, 57337, 57338, 57339, 57340, 57342, 57343, 57344, 57345, 57346, 57347, 57348, 57349, 57350, 57351, 57352, 57353, 57354, 57355, 57356, 57357, 57358, 57359, 57360, 57361, 57362, 57890, 57891, 57892, 57893, 57894, 57895, 57896, 57897, 57898, 57899, 57900, 57901, 57902, 57903, 57904, 57905, 57906, 57907, 57908, 57909, 57910, 57911, 57912, 57913, 57914, 57915, 57916, 57917, 57918, 57919, 57920, 57921, 57922, 57923, 57924, 57925, 57926, 57927, 57928, 57929, 57930, 57931, 57932, 57933, 57934, 57935, 57936, 57937, 57938, 57939, 57940, 57941, 57942, 57943, 57945, 57946, 57947, 57948, 57949, 57950, 57951, 57952, 57953, 57954, 57955, 57956, 57957, 57958, 57959, 57960, 57961, 57962, 57963, 57964, 57965, 57966, 57967, 57968, 57969, 57970, 57971, 57972, 57973, 57974, 57975, 57976, 57977, 57978, 57979, 57980, 57981, 57982, 57983, 57984, 57985, 57986, 57987, 57988, 57989, 57990, 57991, 57992, 57993, 58193, 58194, 58195, 58196, 58197, 58198, 58199, 58200, 58201, 58202, 58203, 58204, 58205, 58206, 58207, 58208, 58209, 58210, 58211, 58212, 58213, 58214, 58215, 58216, 58217, 58218, 58219, 58220, 58221, 58222, 58223, 58224, 58225, 58226, 58227, 58228, 58229, 58230, 58231, 58233, 58234, 58235, 58236, 58237, 58239, 58240, 58241, 58242, 58243, 58246, 58247, 58248, 58249, 58251, 58252, 58254, 58255, 58256, 58257, 58258, 58259, 58260, 58261, 58262, 58263, 58264, 58265, 58266, 58267, 58268, 58269, 58270, 58271, 58272, 58273, 58274, 58275, 58276, 58277, 58278, 58279, 58280, 58281, 58282, 58283, 60621, 60622, 60623, 60624, 60625, 60626, 60627, 60628, 60629, 60630, 60631, 60632, 60633, 60634, 60635, 60636, 60637, 60638, 60639, 60640, 60641, 60642, 60643, 60644, 60645, 60647, 60649, 60650, 60651, 60652, 60654, 60655, 60656, 60657, 60658, 60660, 60661, 60662, 60663, 60664, 60665, 60666, 60667, 60668, 60669, 60670, 60671, 60672, 60673, 60674, 60675, 60676, 60677, 60678, 60679, 60680, 60681, 60682, 60683, 60684, 60685, 60686, 60687, 60688, 60689, 60690, 60691, 60692, 60693, 60694, 60695, 60697, 60698, 61444, 61450, 61455, 61456, 61461, 61462, 61466, 61467, 61468, 61472, 61477, 61478, 61924, 61925, 61926, 61927, 61928, 61929, 61930, 61931, 61932, 61933, 61934, 61935, 61936, 61937, 61938, 61939, 61940, 61941, 61942, 61943, 61944, 61945, 61946, 61947, 61948, 61949, 61950, 61951, 61952, 61953, 61954, 61955, 61956, 61957, 61958, 61959, 61960, 61961, 61962, 61963, 61964, 61965, 61966, 61967, 61968, 61970, 61971, 61973, 61974, 61976, 61977, 61979, 61980, 61982, 61983, 61985, 61986, 61988, 61989, 61990, 61992, 61993, 61994, 61995, 61996, 61997, 61998, 61999, 62000, 62001, 62002, 62003, 62004, 62005, 62006, 62007, 62008, 62009, 62010, 62011, 62012, 62013, 62014, 62015, 62016, 62017, 62018, 62019, 62020, 62021, 62022, 62023, 62024, 62025, 62026, 62029, 62030, 62032, 62034, 84698, 84699, 84700, 84701, 84702, 84703, 84704, 84705, 84706, 84707, 84708, 84709, 84710, 84711, 84712, 84713, 84714, 84715, 84716, 84717, 84718, 84719, 133190, 133204, 133237, 133240, 133246, 133251, 133283, 133287, 133290, 133294, 133299, 133331, 133335, 133338, 133342, 133347, 133380, 133383, 133387, 133393, 133399, 133433, 133437, 133441, 133447, 133453, 133487, 133491, 133503, 133534, 133538, 133542, 133550, 133581, 133584, 133587, 133597, 133630, 133634, 133637, 133647, 133684, 133697, 133730, 133734, 133781, 133785, 133789, 133793, 133799, 133833, 133837, 133841, 133845, 133884, 133887, 133891, 133895, 133901, 133934, 133937, 133941, 133945, 133951, 134001, 134050, 134300, 134306, 134311, 134352, 134358, 134363, 134395, 134399, 134403, 134408, 134413, 134446, 134450, 134454, 134459, 134464, 134498, 134502, 134507, 134512, 134518, 134552, 134556, 134561, 134566, 134572, 134606, 134611, 134616, 134626, 134660, 134665, 134670, 134680, 134719, 134723, 134733, 134766, 134771, 134776, 134824, 134840, 134874, 134879, 134884, 134889, 134929, 134933, 134938, 134947, 134980, 134984, 134989, 135030, 135034, 135044, 135082, 135086, 135091, 135096, 135102, 135402, 135411, 135416, 135422, 135456, 135461, 135466, 135472, 135478, 135513, 135518, 135523, 135529, 135535, 135569, 135574, 135580, 135586, 135592, 135627, 135632, 135638, 135644, 135650, 135690, 135695, 135707, 135747, 135752, 135764, 135804, 135861, 135879, 135914, 135925, 135971, 135977, 135983, 135989, 135995, 136029, 136034, 136040, 136046, 136052, 136087, 136092, 136098, 136104, 136110, 136149, 136155, 136161, 136167, 136202, 136206, 136212, 136218, 136224, 136561, 136621, 136627, 136662, 136663, 136669, 136675, 136681, 136687, 136722, 136723, 136724, 136729, 136735, 136741, 136747, 136782, 136783, 136784, 136789, 136795, 136801, 136807, 136842, 136843, 136844, 136845, 136846, 136852, 136858, 136869, 136903, 136904, 136905, 136906, 136907, 136913, 136919, 136924, 136930, 136964, 136965, 136966, 136967, 136974, 136980, 137027, 137028, 137029, 137030, 137037, 137043, 137090, 137091, 137092, 137100, 137123, 137151, 137152, 137153, 137161, 137178, 137212, 137213, 137214, 137215, 137217, 137222, 137228, 137234, 137240, 137273, 137274, 137275, 137276, 137277, 137278, 137283, 137289, 137295, 137301, 137334, 137335, 137336, 137337, 137338, 137339, 137344, 137349, 137355, 137361, 137396, 137397, 137398, 137399, 137400, 137401, 137406, 137411, 137417, 137423, 137458, 137459, 137460, 137461, 137468, 137474, 137486, 137521, 137522, 137523, 137524, 137525, 137526, 137531, 137537, 137543, 137549, 137892, 137898, 137910, 137952, 137958, 137964, 137970, 137976, 138012, 138018, 138024, 138030, 138036, 138072, 138078, 138084, 138090, 138131, 138137, 138143, 138149, 138190, 138196, 138208, 138214, 138250, 138256, 138262, 138268, 138274, 138310, 138334, 138370, 138431, 138455, 138492, 138498, 138504, 138510, 138553, 138559, 138565, 138571, 138613, 138619, 138625, 138631, 138673, 138679, 138685, 138691, 138734, 138740, 138746, 138752, 139062, 139123, 139160, 139166, 139172, 139178, 139184, 139221, 139227, 139233, 139239, 139245, 139282, 139288, 139294, 139300, 139343, 139349, 139355, 139361, 139404, 139410, 139416, 139422, 139463, 139469, 139475, 139481, 139523, 139529, 139583, 139589, 139642, 139666, 139703, 139715, 139765, 139777, 139783, 139827, 139833, 139839, 139845, 139889, 139895, 139901, 139907, 139913, 139951, 139957, 139963, 139969, 139975, 140037, 140075, 140081, 140087, 140093, 140099], "numberOfPointsInBox": 2236}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "f4241163-2e06-4bea-ba41-1e1c53a0d205", "objectId": "c8ef60c9-73b9-49cd-a2e8-14588b7c0be8", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [1387, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 2063, 2067, 2069, 2075, 2080, 2081, 4508, 4509, 4510, 4511, 4512, 4513, 4514, 4515, 4516, 4518, 4519, 4521, 4522, 4523, 4524, 4525, 4526, 4527, 4528, 4529, 4530, 5532, 5533, 5534, 5535, 5536, 5537, 5538, 5539, 5540, 5541, 5542, 5543, 5544, 5545, 5546, 7588, 7589, 7590, 7591, 7592, 7593, 7594, 7595, 7596, 7597, 7598, 7599, 7600, 7602, 7603, 7604, 7605, 7606, 7607, 7608, 7609, 7610, 7611, 7612, 8618, 8620, 8621, 8622, 8623, 8624, 8625, 8626, 8628, 8629, 8630, 8631, 8632, 8633, 8634, 8635, 8636, 8637, 8638, 8666, 8667, 8668, 8669, 8670, 8671, 8672, 8673, 8674, 8675, 8676, 8677, 8678, 8680, 8681, 8683, 8684, 8686, 8687, 8688, 8689, 8690, 8691, 8692, 8693, 8694, 8695, 8696, 8697, 8698, 8699, 8702, 10584, 10586, 10587, 10588, 10589, 10591, 10592, 10593, 10594, 10595, 10597, 10599, 10601, 10602, 10603, 10604, 10605, 10606, 10607, 10609, 10610, 10611, 10612, 11600, 11601, 11602, 11603, 11604, 11605, 11606, 11607, 11608, 11609, 11610, 11611, 11612, 11613, 11614, 11615, 11616, 11617, 11618, 11619, 11620, 11621, 11622, 11857, 11858, 11859, 11860, 11861, 11862, 11863, 11864, 11865, 11866, 11867, 11868, 11869, 11871, 11873, 11874, 11875, 11876, 11877, 11878, 11879, 11880, 11881, 11882, 13623, 13624, 13625, 13630, 13631, 13640, 13641, 13642, 13643, 13649, 14638, 14639, 14640, 14641, 14642, 14643, 14644, 14645, 14646, 14647, 14648, 14649, 14650, 14651, 14652, 14653, 14654, 14655, 14656, 14657, 14658, 14659, 14660, 15093, 15094, 15095, 15096, 15097, 15098, 15099, 15100, 15101, 15102, 15105, 15107, 15108, 15109, 15111, 15112, 15113, 15114, 15115, 15117, 48124, 48126, 48127, 48128, 48129, 48130, 48131, 48132, 48133, 48134, 48135, 48136, 48137, 48138, 48139, 48140, 48141, 48142, 48143, 48144, 48145, 48146, 48147, 48148, 48150, 48151, 48152, 48153, 48155, 48156, 48157, 48158, 48160, 48161, 48162, 48163, 48164, 48165, 48166, 48167, 48168, 48169, 48170, 48171, 48172, 48173, 48174, 48175, 48176, 48177, 48178, 48179, 48180, 48181, 48182, 48183, 48184, 48185, 48186, 48187, 48188, 48189, 48190, 48191, 48192, 48193, 48194, 48195, 48196, 48197, 48198, 48199, 48200, 48201, 48202, 48203, 48204, 48205, 48206, 48207, 48208, 48209, 48210, 48211, 48212, 48213, 48214, 48215, 48216, 48217, 48218, 48219, 48220, 48221, 48222, 48223, 48224, 48225, 48226, 48227, 48228, 48229, 48230, 48231, 48232, 48233, 48234, 48235, 48236, 48237, 48238, 48239, 48240, 48241, 48242, 48243, 48244, 48245, 48246, 48247, 48248, 48249, 48250, 48251, 48252, 48253, 48254, 48255, 48256, 48257, 48258, 48259, 48260, 48261, 48262, 48263, 48264, 48265, 48266, 48267, 48268, 48269, 48270, 48271, 48272, 48274, 48275, 48276, 48277, 48278, 48280, 48282, 48283, 48284, 48285, 48286, 48288, 48291, 48294, 48297, 48299, 48300, 48302, 48303, 48304, 48305, 48306, 48307, 48308, 48309, 48310, 48311, 48312, 48313, 48314, 48315, 48316, 48317, 48318, 48319, 48320, 48321, 48322, 48323, 48324, 48325, 48326, 48327, 48328, 52488, 52489, 52490, 52491, 52492, 52493, 52494, 52495, 52496, 52497, 52498, 52499, 52500, 52501, 52502, 52503, 52504, 52505, 52506, 52507, 52508, 52509, 52510, 52511, 52513, 52516, 52519, 52522, 52524, 52525, 52528, 52530, 52531, 52532, 52533, 52534, 52536, 52537, 52538, 52539, 52540, 52541, 52542, 52543, 52544, 52545, 52546, 52547, 52548, 52549, 52550, 52551, 52552, 52553, 52554, 52555, 52556, 52557, 52558, 52559, 52560, 52561, 52562, 52563, 52564, 52565, 52566, 52567, 52568, 52569, 52570, 52571, 52572, 52573, 52575, 52576, 52577, 52578, 52582, 52583, 52584, 52588, 52589, 52594, 52595, 52600, 52601, 52606, 52607, 52613, 52614, 52619, 52620, 52621, 52622, 52625, 52626, 52627, 52628, 52631, 52632, 52633, 52634, 52638, 52639, 52640, 52644, 52645, 52646, 52650, 52651, 52652, 52656, 52657, 52658, 52664, 52669, 52675, 52679, 52686, 52688, 52692, 52694, 52700, 52706, 52712, 56830, 56836, 56842, 56848, 56854, 131232, 131233, 131237, 131274, 131275, 131277, 131317, 131325, 131361, 131362, 131365, 131448, 132119, 132158, 132161, 132200, 132245, 132290, 132291, 132298, 132336, 132338, 132341, 133103, 133105, 133106, 133145, 133148, 133149, 133188, 133197, 133235, 133238, 133241, 133333, 134082, 134083, 134093, 134132, 134133, 134181, 134182, 134183, 134187, 134236, 134237, 134238, 134242, 134246, 134251, 134291, 134292, 134293, 134304, 134343, 134344, 134345, 134350, 134354, 135241, 135295, 135300, 135351, 135405, 135409, 135516, 135521, 136373, 136384, 136432, 136438, 136443, 136559, 136607, 136667, 136673], "numberOfPointsInBox": 659}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "af8bc1d1-fd49-4c06-9dca-f2ba7ebc7e0c", "objectId": "5029d606-305c-4276-9cb9-5ae2ef61d285", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [1364, 1365, 1366, 1367, 1369, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 1378, 2117, 2128, 2129, 4468, 4469, 4470, 4471, 4472, 4473, 4474, 4475, 4476, 4477, 4478, 4479, 4480, 4481, 5572, 5573, 5574, 5575, 5577, 5578, 5579, 5580, 5581, 5583, 5584, 5586, 5587, 5588, 5590, 5591, 5592, 5593, 5597, 5598, 7545, 7546, 7547, 7548, 7549, 7550, 7551, 7552, 7553, 7554, 7555, 7556, 7557, 7558, 8734, 8735, 8736, 8737, 8738, 8739, 8740, 8741, 8742, 8743, 8744, 8745, 8746, 8747, 8748, 10537, 10541, 10542, 10544, 10545, 10546, 10547, 10548, 10550, 10551, 10552, 10553, 10554, 11919, 11920, 11921, 11922, 11923, 11924, 11925, 11926, 11927, 11928, 11929, 11930, 11931, 11932, 11934, 13570, 13571, 13572, 13576, 13577, 13578, 13582, 13583, 13584, 14616, 14617, 14618, 14619, 14620, 14621, 14622, 14624, 14625, 14626, 15149, 15150, 15153, 15154, 15155, 15156, 15159, 15160, 15161, 15164, 16723, 16729, 130815, 130856, 130859, 130897, 130937, 130940, 131756, 131758, 131799, 131802, 131805, 131844, 131846, 131890, 131892, 132649, 132652, 132696, 132699, 132742, 132787, 132790, 133681, 133731, 133735, 133782, 133834, 133838, 134716, 134768, 134773, 134821, 134826, 134876, 134881, 135864, 135917, 135974, 135980], "numberOfPointsInBox": 177}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "1d158fb8-2d57-4a3d-94b5-07acccd5aefd", "objectId": "e32d3223-1292-4c78-b0cd-46ce6ad9a0bc", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [316, 323, 329, 335, 1346, 1347, 1348, 1349, 1352, 1354, 1357, 2141, 2146, 2147, 2152, 2387, 2389, 2391, 2393, 2394, 2396, 2397, 2401, 2404, 2405, 4450, 4451, 4452, 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460, 4461, 5333, 5606, 5607, 5608, 5609, 5610, 5613, 5614, 5615, 5616, 5617, 5619, 5620, 5622, 7525, 7526, 7527, 7528, 7529, 7531, 7532, 7533, 7534, 7535, 8758, 8759, 8760, 8761, 8762, 8763, 8764, 8765, 8766, 8767, 8768, 8769, 8770, 10520, 10521, 10522, 10523, 10524, 10526, 10527, 10528, 10529, 10530, 11942, 11943, 11944, 11945, 11946, 11947, 11948, 11949, 11950, 11951, 11952, 11953, 13552, 13553, 13554, 13557, 13558, 13559, 14605, 14606, 14607, 14610, 15172, 15173, 15174, 15177, 15179, 15180, 15183, 16698, 16699, 16705, 16710, 130635, 130680, 130723, 130726, 130769, 130772, 131582, 131625, 132476, 132521, 132568, 132607, 132610, 133488, 133492, 133535, 133539, 134504, 134510, 134558, 134608, 134613, 134662, 134667, 135583, 135693, 135698, 135750, 135755], "numberOfPointsInBox": 146}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "36773490-19a5-4894-be95-ffd3ae1eba3f", "objectId": "c305af21-7b22-402f-8cda-3d7b76e9d073", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [305, 311, 317, 1338, 1339, 1341, 1342, 1343, 1344, 2153, 2158, 2159, 2164, 2414, 2418, 2421, 2422, 2426, 2435, 4441, 4442, 4446, 4447, 4448, 5623, 5624, 5625, 5626, 5629, 5630, 5631, 5632, 5633, 5635, 7514, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7523, 7524, 8771, 8773, 8775, 8776, 8777, 8778, 8779, 8782, 10510, 10512, 10514, 10515, 10516, 10517, 10518, 11954, 11955, 11956, 11958, 11959, 11960, 11961, 11964, 13540, 13541, 13542, 13543, 13546, 13547, 13548, 15184, 15185, 15188, 15189, 15190, 15191, 16686, 16687, 16692, 16693, 130541, 130589, 131407, 131451, 131493, 131496, 131538, 131541, 132385, 132388, 132431, 132434, 133384, 133434, 133438, 135577, 135635], "numberOfPointsInBox": 101}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "a7c91545-2bb1-4342-94b3-cb2e638f0d5a", "objectId": "0650ff28-41a6-4a5f-ac17-89a769d5ffad", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [289, 295, 1325, 2177, 5643, 5644, 5649, 5650, 7504, 7505, 7506, 8788, 10499, 10500, 10501, 10502, 10505, 11969, 11970, 11971, 13532, 13533, 15198, 15200, 15201, 16668, 16669, 16670, 16674], "numberOfPointsInBox": 29}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "0bfdf09d-7f99-4dae-a51a-edfea7a2a1e3", "objectId": "e03bf406-e5fb-4b6b-8bfb-4388de4b1cf1", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [2181, 5362, 7499, 7500, 8793, 15206, 16663, 16664], "numberOfPointsInBox": 8}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "e0d5d414-358a-4c9c-82a1-dbd489a9cc8c", "objectId": "27c9e593-ff11-4b1c-9253-aff4706ddb5b", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [283, 284, 2182, 2185, 5661, 7498, 8798, 10494, 10495, 11979, 15209, 16659, 16660], "numberOfPointsInBox": 13}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "47135a62-ec1d-434d-aa3a-f90d41217e35", "objectId": "0da518da-8c14-4364-b494-646809641015", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [274, 1312, 2188, 4417, 5373, 7489, 7490, 10490, 11981, 15211], "numberOfPointsInBox": 10}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "6f900dfc-f616-484e-a084-8ff2ec190eeb", "objectId": "2165789a-d0d5-42df-93b7-192189b558a2", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [271, 13518, 16654], "numberOfPointsInBox": 3}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "24bbde15-c191-4123-99d4-6886942104fc", "objectId": "e9b747e7-e01f-4209-b082-6986ce17ce2d", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [], "numberOfPointsInBox": 0}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "daf6c303-3afa-4e3d-869d-f3e6ad8309cf", "objectId": "bb1f0700-9c85-4560-a998-fb3f04eeb296", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [], "numberOfPointsInBox": 0}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "817ed6c3-8552-42dd-b858-00bfa37ab8fa", "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", "className": "3D_signal_pole", "geometry": {"associatedPoints": [18760, 18761, 18762, 18763, 18764, 18765, 18766, 18767, 18768, 18769, 18770, 18771, 18772, 18773, 18774, 18775, 18776, 18777, 18778, 18779, 19471, 19477, 19483, 19489, 19494, 19495, 19499, 19500, 19501, 19505, 19506, 19507, 19511, 19512, 19513, 19517, 19518, 19519, 19523, 19524, 19525, 19529, 19530, 19531, 19536, 19537, 20713, 20714, 20715, 20716, 20717, 20718, 20719, 20720, 20721, 20722, 20723, 20724, 20725, 20726, 20727, 20728, 20729, 20730, 20731, 20732, 20733, 20734, 23181, 23182, 23183, 23184, 23185, 23186, 23187, 23188, 23189, 23190, 23191, 23192, 23193, 23194, 23195, 23196, 23197, 23198, 23199, 23200, 23202, 23203, 24429, 24446, 24452, 24458, 24463, 24464, 24469, 24470, 24475, 24476, 24481, 24482, 24487, 24493, 24499, 24505, 24511, 24517, 25141, 25143, 25144, 25145, 25146, 25147, 25148, 25149, 25150, 25151, 25152, 25153, 25154, 25155, 25156, 25157, 25158, 25159, 25160, 25161, 25162, 27580, 27586, 27590, 27591, 27592, 27593, 27594, 27595, 27596, 27597, 27598, 27599, 27600, 27601, 27602, 27603, 27604, 27605, 27606, 27607, 27608, 27609, 27610, 27611, 27612, 27613, 27614, 27615, 27616, 27617, 27622, 27623, 28777, 28778, 28779, 28780, 28781, 28782, 28783, 28784, 28785, 28786, 28787, 28788, 28789, 28790, 28791, 28792, 28793, 28794, 28795, 28796, 28797, 28798, 28799, 28800, 28804, 28805, 28806, 28809, 28810, 28811, 28815, 29533, 29535, 29536, 29537, 29538, 29539, 29540, 29541, 29542, 29543, 29544, 29545, 29546, 29547, 29548, 29549, 29550, 29551, 29552, 29553, 31852, 31856, 31857, 31858, 31862, 31863, 31864, 31865, 31868, 31869, 31870, 31871, 31874, 31875, 31876, 31877, 31880, 31881, 31882, 31883, 31884, 31886, 31887, 31888, 31889, 31890, 31892, 31893, 31894, 31895, 31896, 31898, 31899, 31900, 31901, 31902, 31903, 31904, 31905, 31906, 31907, 31908, 31909, 31910, 31911, 31912, 31913, 31914, 31915, 31916, 31917, 31918, 31919, 31920, 31921, 31923, 31924, 31925, 31926, 31927, 31929, 31930, 31931, 31932, 31933, 31936, 31937, 31938, 31939, 31943, 31944, 31945, 31949, 31950, 31951, 31956, 31957, 31962, 31963, 31968, 31969, 31975, 31981, 31987, 32995, 32997, 32998, 32999, 33000, 33001, 33002, 33003, 33004, 33005, 33006, 33007, 33008, 33009, 33010, 33011, 33012, 33013, 33014, 33015, 33016, 33018, 33019, 33024, 33025, 33782, 33784, 33785, 33786, 33787, 33788, 33789, 33790, 33791, 33792, 33793, 33794, 33795, 33796, 33797, 33798, 33799, 33800, 33801, 33802, 33803, 33804, 33806, 33812, 33818, 33824, 37166, 37167, 37168, 37169, 37170, 37171, 37172, 37173, 37174, 37175, 37176, 37177, 37178, 37179, 37180, 37181, 37182, 37183, 37184, 37937, 37943, 37944, 37945, 37946, 37947, 37949, 37950, 37951, 37952, 37953, 37954, 37955, 37956, 37957, 37958, 37959, 37960, 37961, 37962, 37963, 37964, 37965, 37966, 37967, 37968, 37969, 37970, 37971, 37972, 37973, 37974, 37975, 37976, 37979, 37980, 37981, 37982, 37985, 37986, 37987, 37988, 37991, 37992, 37993, 37997, 37998, 37999, 38915, 38921, 38927, 38934, 38940, 38946, 38959, 38965, 38971, 38978, 38984, 38987, 38990, 38993, 38999, 39000, 39005, 39006, 39009, 39011, 39012, 39017, 39018, 39019, 39024, 39025, 39028, 39029, 39030, 39031, 39032, 39033, 39035, 39036, 39037, 39038, 39039, 39040, 39041, 39042, 39043, 39044, 39045, 39046, 39047, 39048, 39049, 39050, 39051, 39052, 39053, 39054, 39055, 39056, 39057, 39058, 39059, 39060, 39061, 39062, 39063, 39064, 39065, 39066, 39067, 39068, 39069, 39070, 39071, 39072, 39073, 39074, 39075, 39076, 39077, 39078, 39079, 91770, 91771, 91772, 91773, 91774, 122356, 122402, 122420, 122460, 122466, 122472, 122478, 122484, 122524, 122529, 122535, 122541, 122547, 122587, 122592, 122598, 122604, 122610, 122670, 122730, 122794, 122852, 122858, 122921, 122984, 123719, 123725, 123737, 123776, 123782, 123788, 123794, 123799, 123837, 123843, 123849, 123855, 123860, 123898, 123904, 123915, 123921, 123960, 123966, 123977, 123983, 124044, 124105, 124166, 124227, 124972, 125012, 125035, 125075, 125081, 125087, 125092, 125098, 125138, 125143, 125149, 125155, 125161, 125167, 125201, 125206, 125212, 125218, 125224, 125230, 125282, 125288, 125340, 125346, 125402, 125407, 125463, 125468, 125526, 125589, 126256, 126306, 126311, 126316, 126354, 126355, 126371, 126376, 126382, 126415, 126416, 126421, 126426, 126432, 126437, 126443, 126476, 126477, 126481, 126485, 126496, 126502, 126535, 126536, 126540, 126544, 126555, 126561, 126614, 126620, 126673, 126679, 126733, 126793, 127388, 127444, 127489, 127494, 127500, 127549, 127554, 127587, 127592, 127596, 127601, 127606, 127640, 127644, 127649, 127653, 127659, 127694, 127698, 127703, 127707, 127713, 127758, 127764, 127797, 127801, 127809, 127815, 127863, 127917, 127922, 127972, 128027, 128549, 128591, 128595, 128600, 128642, 128646, 128651, 128687, 128690, 128694, 128699, 128703, 128739, 128742, 128746, 128751, 128755, 128791, 128803, 128809, 128845, 128849, 128857, 128863, 128912, 128918, 128967, 128973, 129021, 129074, 129079], "numberOfPointsInBox": 674}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "6cdb49ee-118b-46c4-a17b-ab40bc4544a6", "objectId": "418cad49-3c39-4fce-ae3e-c4b6db04499d", "className": "3D_catenary_pole", "geometry": {"associatedPoints": [84422, 92241, 92242, 92243, 92244, 92245, 92246, 92247, 92248, 92249, 92250, 92251, 92252, 92253, 92254, 92255, 92256, 92257, 92258, 92259], "numberOfPointsInBox": 20}, "attributes": {"structure": "structured"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "059f694f-3215-41ba-94b0-74a9cd42b898", "objectId": "af9f7ffb-9c2a-481c-beab-843cfa428cda", "className": "3D_signal_pole", "geometry": {"associatedPoints": [246, 247, 2526, 4368, 5695, 5696, 7459, 7462, 8824, 10469, 12002, 15240, 15241], "numberOfPointsInBox": 13}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "9955441a-e315-49f7-85b4-cd8f1b3d0ba0", "objectId": "369bd0f5-4f00-4fbf-8f12-23c8839ce0e0", "className": "3D_signal_pole", "geometry": {"associatedPoints": [], "numberOfPointsInBox": 0}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "e7cbd2d8-16e4-46e0-9717-3c72d3c07996", "objectId": "0fdb210a-34dd-4cee-8684-fe1756bbe023", "className": "3D_signal_pole", "geometry": {"associatedPoints": [], "numberOfPointsInBox": 0}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "72869011-d5c7-4857-8fc9-d4eb8ecba975", "objectId": "decf17bb-d644-4eb8-821e-c578738b3082", "className": "3D_train", "geometry": {"associatedPoints": [5718, 5723, 8834, 10448, 10452, 12031, 12034, 12037, 13470, 13473, 16597, 35457, 35459], "numberOfPointsInBox": 13}, "attributes": {"type": "regionalExpress"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "7bf23c73-a99b-4389-ac43-d3496cc34fff", "objectId": "f24c55f7-2ff0-4137-9328-5ae022bfcb07", "className": "3D_train", "geometry": {"associatedPoints": [], "numberOfPointsInBox": 0}, "attributes": {"type": "regionalExpress"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "875b494e-dbf6-4bb2-b5e6-d70b65fb5dbe", "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", "className": "3D_person", "geometry": {"associatedPoints": [269, 2194, 2498, 3408, 4402, 5377, 5680, 6528, 6530, 7481, 8485, 8809, 11480, 15217, 15731, 16651], "numberOfPointsInBox": 16}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "age": "adult", "function": "worker", "pose": "upright"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "ee6e9de9-8854-4467-9f5c-a95073ae24ad", "objectId": "87563e91-2b69-475b-9a8b-07f794d1bdb2", "className": "3D_person", "geometry": {"associatedPoints": [1282, 2200, 3402, 5387, 7477, 8813, 15221, 16648], "numberOfPointsInBox": 8}, "attributes": {"isDummy": false, "carries": "nothing", "connectedTo": [], "isDistracted": false, "function": "worker", "age": "adult", "pose": "upright"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "b44b68c1-4e80-46f1-b822-00f453b1c227", "objectId": "e07310f7-02df-4d05-ac3c-6296117d27ea", "className": "3D_track", "geometry": {"associatedPoints": [618, 624, 625, 630, 631, 636, 637, 638, 642, 643, 644, 648, 649, 650, 651, 654, 655, 656, 657, 660, 661, 662, 663, 664, 666, 667, 668, 669, 670, 672, 673, 674, 675, 676, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1116, 1117, 1118, 1119, 1120, 1122, 1123, 1124, 1125, 1126, 1128, 1129, 1130, 1131, 1134, 1135, 1136, 1137, 1140, 1141, 1142, 1143, 1146, 1147, 1148, 1152, 1153, 1154, 1158, 1159, 1160, 1164, 1165, 1170, 1171, 1176, 1177, 1182, 1183, 1188, 1194, 1200, 1206, 1212, 2191, 3409, 3410, 3411, 3413, 3419, 3422, 3428, 3433, 3439, 3445, 3451, 3457, 3463, 3469, 3475, 3481, 3487, 3493, 3499, 3505, 3511, 3517, 3523, 3529, 3535, 3536, 3541, 3542, 3547, 3548, 3553, 3554, 3559, 3560, 3565, 3566, 3567, 3571, 3572, 3573, 3577, 3578, 3579, 3583, 3584, 3585, 3586, 3589, 3590, 3591, 3592, 3595, 3596, 3597, 3598, 3601, 3602, 3603, 3604, 3605, 3607, 3608, 3609, 3610, 3611, 3613, 3614, 3615, 3616, 3617, 3619, 3620, 3621, 3622, 3623, 3624, 3625, 3626, 3627, 3628, 3629, 3630, 3631, 3632, 3633, 3634, 3635, 3636, 3637, 3638, 3639, 3640, 3641, 3642, 3643, 3644, 3645, 3646, 3647, 3648, 3649, 3650, 3651, 3652, 3653, 3654, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662, 3663, 3664, 3665, 3666, 3667, 3668, 3669, 3670, 3671, 3672, 3673, 3674, 3675, 3676, 3677, 3678, 3679, 3680, 3681, 3682, 3683, 3684, 3685, 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694, 3695, 3696, 3697, 3698, 3699, 3700, 3701, 3702, 3703, 3704, 3705, 3706, 3707, 3708, 3709, 3710, 3711, 3712, 3713, 3714, 3715, 3716, 3717, 3718, 3719, 3720, 3721, 3722, 3723, 3724, 3725, 3726, 3727, 3728, 3729, 3730, 3731, 3732, 3733, 3734, 3735, 3736, 3737, 3738, 3739, 3740, 3741, 3742, 3743, 3744, 3745, 3746, 3747, 3748, 3749, 3750, 3751, 3752, 3753, 3754, 3755, 3756, 3757, 3758, 3759, 3760, 3761, 3762, 3763, 3764, 3765, 3766, 3767, 3768, 3769, 3770, 3771, 3772, 3773, 3774, 3775, 3776, 3777, 3778, 3779, 3780, 3781, 3782, 3783, 3784, 3785, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 3794, 3795, 3796, 3797, 3798, 3799, 3800, 3801, 3802, 3803, 3804, 3805, 3806, 3807, 3808, 3809, 3810, 3811, 3812, 3813, 3814, 3815, 3816, 3817, 3818, 3819, 3820, 3821, 3822, 3823, 3824, 3825, 3826, 3827, 3828, 3829, 3830, 3831, 3832, 3833, 3834, 3835, 3836, 3837, 3838, 3839, 3840, 3841, 3842, 3843, 3844, 3845, 3846, 3847, 3848, 3849, 3850, 3851, 3852, 3853, 3854, 3855, 3856, 3857, 3858, 3859, 3860, 3861, 3862, 3863, 3864, 3865, 3866, 3867, 3868, 3869, 3870, 3871, 3872, 3873, 3874, 3875, 3876, 3877, 3878, 3879, 3880, 3881, 3882, 3883, 3884, 3885, 3886, 3887, 3888, 3889, 3890, 3891, 3892, 3893, 3894, 3895, 3896, 3897, 3898, 3899, 3900, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3909, 3910, 3911, 3912, 3913, 3914, 3915, 3916, 3917, 3918, 3919, 3920, 3921, 3922, 3923, 3924, 3925, 3926, 3927, 3928, 3929, 3930, 3931, 3932, 3933, 3934, 3935, 3936, 3937, 3938, 3939, 3940, 3941, 3942, 3943, 3944, 3945, 3946, 3947, 3948, 3949, 3950, 3951, 3952, 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 3970, 3971, 3972, 3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 3981, 3982, 3983, 3984, 3985, 3986, 3987, 3988, 3989, 3990, 3991, 3992, 3993, 3994, 3995, 3996, 3997, 3998, 3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049, 4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061, 4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4075, 4076, 4077, 4078, 4079, 4080, 4081, 4082, 4083, 4084, 4085, 4087, 4088, 4089, 4090, 4091, 4093, 4094, 4095, 4096, 4099, 4100, 4101, 4102, 4105, 4106, 4107, 4108, 4111, 4112, 4113, 4117, 4118, 4119, 4123, 4124, 4129, 4130, 4135, 4136, 4141, 4147, 5368, 5375, 5376, 6531, 6532, 6533, 6534, 6535, 6536, 6537, 6538, 6539, 6540, 6541, 6542, 6544, 6545, 6546, 6548, 6549, 6550, 6551, 6552, 6553, 6555, 6556, 6557, 6558, 6559, 6560, 6561, 6562, 6563, 6565, 6566, 6567, 6568, 6569, 6570, 6571, 6572, 6573, 6574, 6575, 6576, 6577, 6578, 6579, 6580, 6581, 6582, 6583, 6584, 6585, 6586, 6587, 6588, 6589, 6590, 6591, 6592, 6593, 6594, 6595, 6596, 6597, 6598, 6599, 6600, 6601, 6602, 6603, 6604, 6605, 6606, 6607, 6608, 6609, 6610, 6611, 6612, 6613, 6614, 6615, 6616, 6617, 6618, 6619, 6620, 6621, 6622, 6623, 6624, 6625, 6626, 6627, 6628, 6629, 6630, 6631, 6632, 6633, 6634, 6635, 6636, 6637, 6638, 6639, 6640, 6641, 6642, 6643, 6644, 6645, 6646, 6647, 6648, 6649, 6650, 6651, 6652, 6653, 6654, 6655, 6656, 6657, 6658, 6659, 6660, 6661, 6662, 6663, 6664, 6665, 6666, 6667, 6668, 6669, 6670, 6671, 6672, 6673, 6674, 6675, 6676, 6677, 6678, 6679, 6680, 6681, 6682, 6683, 6684, 6685, 6686, 6687, 6688, 6689, 6690, 6691, 6692, 6693, 6694, 6695, 6696, 6697, 6698, 6699, 6700, 6701, 6702, 6703, 6704, 6705, 6706, 6707, 6708, 6709, 6710, 6711, 6712, 6713, 6714, 6715, 6716, 6717, 6718, 6719, 6720, 6721, 6722, 6723, 6724, 6725, 6726, 6727, 6728, 6729, 6730, 6731, 6732, 6733, 6734, 6735, 6736, 6737, 6738, 6739, 6740, 6741, 6742, 6743, 6744, 6745, 6746, 6747, 6748, 6749, 6750, 6751, 6752, 6753, 6754, 6755, 6756, 6757, 6758, 6759, 6760, 6761, 6762, 6763, 6764, 6765, 6766, 6767, 6768, 6769, 6770, 6771, 6772, 6773, 6774, 6775, 6776, 6777, 6778, 6779, 6780, 6781, 6782, 6783, 6784, 6785, 6786, 6787, 6788, 6789, 6790, 6791, 6792, 6793, 6794, 6795, 6796, 6797, 6798, 6799, 6800, 6801, 6802, 6803, 6804, 6805, 6806, 6807, 6808, 6809, 6810, 6811, 6812, 6813, 6814, 6815, 6816, 6817, 6818, 6819, 6820, 6821, 6822, 6823, 6824, 6825, 6826, 6827, 6828, 6829, 6830, 6831, 6832, 6833, 6834, 6835, 6836, 6837, 6838, 6839, 6840, 6841, 6842, 6843, 6844, 6845, 6846, 6847, 6848, 6849, 6850, 6851, 6852, 6853, 6854, 6855, 6856, 6857, 6858, 6859, 6860, 6861, 6862, 6863, 6864, 6865, 6866, 6867, 6868, 6869, 6870, 6871, 6872, 6873, 6874, 6875, 6876, 6877, 6878, 6879, 6880, 6881, 6882, 6883, 6884, 6885, 6886, 6887, 6888, 6889, 6890, 6891, 6892, 6893, 6894, 6895, 6896, 6897, 6898, 6899, 6900, 6901, 6902, 6903, 6904, 6905, 6906, 6907, 6908, 6909, 6910, 6911, 6912, 6913, 6914, 6915, 6916, 6917, 6918, 6919, 6920, 6921, 6922, 6923, 6924, 6925, 6926, 6927, 6928, 6929, 6930, 6931, 6932, 6933, 6934, 6935, 6936, 6937, 6938, 6939, 6940, 6941, 6942, 6943, 6944, 6945, 6946, 6947, 6948, 6949, 6950, 6951, 6952, 6953, 6954, 6955, 6956, 6957, 6958, 6959, 6960, 6961, 6962, 6963, 6964, 6965, 6966, 6967, 6968, 6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978, 6979, 6980, 6981, 6982, 6983, 6984, 6985, 6986, 6987, 6988, 6989, 6990, 6991, 6992, 6993, 6994, 6995, 6996, 6997, 6998, 6999, 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7008, 7009, 7010, 7011, 7012, 7014, 7015, 7016, 7017, 7018, 7020, 7021, 7022, 7023, 7026, 7027, 7028, 7029, 7032, 7033, 7034, 7038, 7039, 7040, 7044, 7045, 7050, 7051, 7056, 7062, 8415, 8421, 8427, 8433, 8434, 8439, 8440, 8445, 8446, 8447, 8451, 8452, 8453, 8454, 8455, 8459, 8460, 8463, 8464, 8465, 8468, 8469, 8470, 8471, 8472, 8474, 8475, 8477, 8478, 8479, 8480, 8481, 8483, 8484, 9584, 9585, 9586, 9587, 9588, 9589, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9628, 9629, 9630, 9631, 9632, 9633, 9634, 9635, 9636, 9637, 9638, 9639, 9640, 9641, 9642, 9643, 9644, 9645, 9646, 9647, 9648, 9649, 9650, 9651, 9652, 9653, 9654, 9655, 9656, 9657, 9658, 9659, 9660, 9661, 9662, 9663, 9664, 9665, 9666, 9667, 9668, 9669, 9670, 9671, 9672, 9673, 9674, 9675, 9676, 9677, 9678, 9679, 9680, 9681, 9682, 9683, 9684, 9685, 9686, 9687, 9688, 9689, 9690, 9691, 9692, 9693, 9694, 9695, 9696, 9697, 9698, 9699, 9700, 9701, 9702, 9703, 9704, 9705, 9706, 9707, 9708, 9709, 9710, 9711, 9712, 9713, 9714, 9715, 9716, 9717, 9718, 9719, 9720, 9721, 9722, 9723, 9724, 9725, 9726, 9727, 9728, 9729, 9730, 9731, 9732, 9733, 9734, 9735, 9736, 9737, 9738, 9739, 9740, 9741, 9742, 9743, 9744, 9745, 9746, 9747, 9748, 9749, 9750, 9751, 9752, 9753, 9754, 9755, 9756, 9757, 9758, 9759, 9760, 9761, 9762, 9763, 9764, 9765, 9766, 9767, 9768, 9769, 9770, 9771, 9772, 9773, 9774, 9775, 9776, 9777, 9778, 9779, 9780, 9781, 9782, 9783, 9784, 9785, 9786, 9787, 9788, 9789, 9790, 9791, 9792, 9793, 9794, 9795, 9796, 9797, 9798, 9799, 9800, 9801, 9802, 9803, 9804, 9805, 9806, 9807, 9808, 9809, 9810, 9811, 9812, 9813, 9814, 9815, 9816, 9817, 9818, 9819, 9820, 9821, 9822, 9823, 9824, 9825, 9826, 9827, 9828, 9829, 9830, 9831, 9832, 9833, 9834, 9835, 9836, 9837, 9838, 9839, 9840, 9841, 9842, 9843, 9844, 9845, 9846, 9847, 9848, 9849, 9850, 9851, 9852, 9853, 9854, 9855, 9856, 9857, 9858, 9860, 9861, 9862, 9863, 9864, 9866, 9867, 9868, 9869, 9870, 9872, 9873, 9874, 9875, 9878, 9879, 9880, 9881, 9884, 9885, 9886, 9887, 9890, 9891, 9892, 9896, 9897, 9898, 9902, 9903, 9908, 9909, 9914, 9915, 9920, 9926, 11278, 11284, 11290, 11291, 11296, 11297, 11302, 11303, 11308, 11309, 11310, 11314, 11315, 11316, 11320, 11321, 11322, 11323, 11326, 11327, 11328, 11329, 11332, 11333, 11334, 11335, 11338, 11339, 11340, 11341, 11342, 11344, 11345, 11346, 11347, 11348, 11350, 11351, 11352, 11353, 11354, 11356, 11357, 11358, 11359, 11360, 11362, 11363, 11364, 11365, 11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 11374, 11375, 11376, 11377, 11378, 11379, 11380, 11381, 11382, 11383, 11384, 11385, 11386, 11387, 11388, 11389, 11390, 11391, 11392, 11393, 11394, 11395, 11396, 11397, 11398, 11399, 11400, 11401, 11402, 11403, 11404, 11405, 11406, 11407, 11408, 11409, 11410, 11411, 11412, 11413, 11414, 11415, 11416, 11417, 11418, 11419, 11420, 11421, 11422, 11423, 11424, 11425, 11426, 11427, 11428, 11429, 11430, 11431, 11432, 11433, 11434, 11435, 11436, 11437, 11438, 11439, 11440, 11441, 11442, 11443, 11444, 11445, 11446, 11447, 11448, 11449, 11450, 11451, 11452, 11453, 11454, 11455, 11456, 11457, 11458, 11459, 11460, 11461, 11462, 11463, 11464, 11465, 11466, 11467, 11468, 11469, 11470, 11471, 11472, 11473, 11474, 11475, 11477, 11478, 11479, 11481, 11482, 11483, 11484, 11485, 11486, 11487, 12649, 12650, 12651, 12652, 12653, 12654, 12655, 12656, 12657, 12658, 12659, 12660, 12663, 12664, 12665, 12667, 12668, 12669, 12671, 12672, 12673, 12676, 12677, 12678, 12679, 12680, 12681, 12685, 12686, 12687, 12688, 12690, 12691, 12692, 12693, 12696, 12697, 12698, 12699, 12702, 12703, 12704, 12705, 12708, 12709, 12710, 12711, 12714, 12715, 12716, 12720, 12721, 12722, 12726, 12727, 12728, 12732, 12733, 12734, 12738, 12739, 12740, 12744, 12745, 12746, 12750, 12751, 12756, 12757, 12762, 12763, 12768, 12769, 12774, 12775, 12780, 12786, 12792, 12798, 14114, 14120, 14126, 14127, 14132, 14133, 14138, 14139, 14140, 14144, 14145, 14146, 14147, 14150, 14151, 14152, 14153, 14156, 14157, 14158, 14159, 14162, 14163, 14164, 14165, 14166, 14168, 14169, 14170, 14171, 14172, 14174, 14175, 14176, 14177, 14178, 14179, 14180, 14181, 14182, 14183, 14184, 14185, 14186, 14187, 14188, 14189, 14190, 14191, 14192, 14193, 14194, 14195, 14196, 14197, 14198, 14199, 14200, 14201, 14202, 14203, 14204, 14205, 14206, 14207, 14208, 14209, 14210, 14211, 14212, 14213, 14214, 14215, 14216, 14217, 14218, 14219, 14220, 14221, 14222, 14223, 14224, 14225, 14226, 14227, 14228, 14229, 14230, 14231, 14232, 14233, 14234, 14235, 14236, 14237, 14238, 14239, 14240, 14241, 14242, 14243, 14244, 14245, 14246, 14247, 14248, 14249, 14250, 14251, 14252, 14253, 14254, 14255, 14256, 14257, 14258, 14259, 14260, 14261, 14262, 14263, 14264, 14265, 14266, 14267, 14268, 14269, 14270, 14271, 14272, 14273, 14274, 14275, 14276, 14277, 14278, 14279, 14280, 14281, 14282, 14283, 14284, 14285, 14286, 14287, 14288, 14289, 14290, 14291, 14292, 14293, 14294, 14295, 14296, 14297, 14298, 14299, 14300, 14301, 14302, 14303, 14304, 14305, 14306, 14307, 14308, 14309, 14310, 14311, 14312, 14313, 14314, 14315, 14316, 14317, 14318, 14319, 14320, 14321, 14322, 14323, 14324, 14325, 14326, 14327, 14328, 14329, 14330, 14331, 14332, 14333, 14334, 14335, 14336, 14337, 14338, 14339, 14340, 14341, 14342, 14343, 14344, 14345, 14346, 14347, 14348, 14349, 14350, 14351, 14352, 14353, 14354, 14355, 14356, 14357, 14358, 14359, 14360, 14361, 14362, 14363, 14364, 14365, 14366, 14367, 14368, 14369, 14370, 14371, 14372, 14373, 14374, 14375, 14376, 14377, 14378, 14379, 14380, 14381, 14382, 14383, 14384, 14385, 14386, 14387, 14388, 14389, 14390, 14391, 14392, 14393, 14394, 14395, 14396, 14397, 14398, 14399, 14400, 14401, 14402, 14403, 14404, 14405, 14406, 14407, 14408, 14409, 14410, 14411, 14412, 14413, 14414, 14415, 14416, 14417, 14418, 14419, 14420, 14421, 14422, 14423, 14424, 14425, 14426, 14427, 14428, 14429, 14430, 14431, 14432, 14433, 14434, 14435, 14436, 14437, 14438, 14439, 14440, 14441, 14442, 14443, 14444, 14445, 14446, 14447, 14448, 14449, 14450, 14451, 14452, 14453, 14454, 14455, 14456, 14457, 14458, 14459, 14460, 14461, 14462, 14463, 14464, 14465, 14466, 14467, 14468, 14469, 14470, 14471, 14472, 14473, 14474, 14475, 14476, 14477, 14478, 14479, 14480, 14481, 14482, 14483, 14484, 14485, 14486, 14487, 14488, 14489, 14490, 14491, 14492, 14493, 14494, 14495, 14496, 14498, 14499, 14500, 14501, 14502, 14503, 14504, 62211, 62212, 62213, 62214, 62215, 62216, 62217, 62288, 62289, 62290, 62291, 62292, 62293, 62294, 62295, 62296, 62297, 62298, 62299, 62300, 62301, 62302, 62303, 62304, 62305, 62306, 62307, 62308, 62309, 62310, 62311, 62374, 62375, 62376, 62377, 62378, 62379, 62380, 62381, 62382, 62383, 62384, 62385, 62386, 62387, 62388, 62389, 62390, 62391, 62392, 62393, 62394, 62395, 62396, 62397, 62398, 62399, 62400, 62461, 62462, 62463, 62464, 62465, 62466, 62467, 62468, 62469, 62470, 62471, 62472, 62473, 62474, 62475, 62476, 62477, 62478, 62479, 62480, 62481, 62482, 62483, 62484, 62485, 62486, 62487, 62488, 62489, 62551, 62552, 62553, 62554, 62555, 62556, 62557, 62558, 62559, 62560, 62561, 62562, 62563, 62564, 62565, 62566, 62567, 62568, 62569, 62570, 62571, 62572, 62573, 62574, 62575, 62576, 62577, 62578, 62643, 62644, 62645, 62646, 62647, 62648, 62649, 62650, 62651, 62652, 62653, 62654, 62655, 62656, 62657, 62658, 62659, 62660, 62661, 62662, 62663, 62664, 62665, 62666, 62667, 62668, 62669, 62742, 62743, 62744, 62745, 62746, 62747, 62748, 62749, 62750, 62751, 62752, 62753, 62754, 62755, 62756, 62757, 62758, 62759, 62760, 62761, 62762, 62763, 62764, 62765, 62766, 62846, 62847, 62848, 62849, 62850, 62851, 62852, 62853, 62854, 62855, 62856, 62857, 62858, 62859, 62860, 62861, 62862, 62863, 62864, 62865, 62866, 62867, 62868, 62956, 62957, 62958, 62959, 62960, 62961, 62962, 62963, 62964, 62965, 62966, 62967, 62968, 62969, 62970, 62971, 62972, 62973, 62974, 62975, 62976, 63071, 63072, 63073, 63074, 63075, 63076, 63077, 63078, 63079, 63080, 63081, 63082, 63083, 63084, 63085, 63086, 63087, 63088, 63089, 63090, 63188, 63189, 63190, 63191, 63192, 63193, 63194, 63195, 63196, 63197, 63198, 63199, 63200, 63201, 63202, 63203, 63204, 63205, 63206, 63303, 63304, 63305, 63306, 63307, 63308, 63309, 63310, 63311, 63312, 63313, 63314, 63315, 63316, 63317, 63318, 63319, 63320, 63433, 63434, 63435, 63436, 63437, 63438, 63439, 63440, 63441, 63442, 63443, 63444, 63445, 63446, 63447, 63448, 63449, 63564, 63565, 63566, 63567, 63568, 63569, 63570, 63571, 63572, 63573, 63574, 63575, 63576, 63577, 63578, 63579, 63701, 63702, 63703, 63704, 63705, 63706, 63707, 63708, 63709, 63710, 63711, 63712, 63713, 63714, 63715, 63843, 63844, 63845, 63846, 63847, 63848, 63849, 63850, 63851, 63852, 63853, 63854, 63855, 63856, 63857, 63985, 63986, 63987, 63988, 63989, 63990, 63991, 63992, 63993, 63994, 63995, 63996, 63997, 63998, 63999, 64129, 64130, 64131, 64132, 64133, 64134, 64135, 64136, 64137, 64138, 64139, 64140, 64141, 64142, 64274, 64275, 64276, 64277, 64278, 64279, 64280, 64281, 64282, 64283, 64284, 64285, 64286, 64423, 64424, 64425, 64426, 64427, 64428, 64429, 64430, 64431, 64432, 64433, 64434, 64435, 64563, 64564, 64565, 64566, 64567, 64568, 64569, 64570, 64571, 64572, 64573, 64574, 64708, 64709, 64710, 64711, 64712, 64713, 64714, 64715, 64716, 64717, 64718, 64854, 64855, 64856, 64857, 64858, 64859, 64860, 64861, 64862, 64863, 64864, 64996, 64997, 64998, 64999, 65000, 65001, 65002, 65003, 65004, 65005, 65139, 65140, 65141, 65142, 65143, 65144, 65145, 65146, 65147, 65148, 65287, 65288, 65289, 65290, 65291, 65292, 65293, 65294, 65295, 65427, 65428, 65429, 65430, 65431, 65432, 65433, 65434, 65569, 65570, 65571, 65572, 65573, 65574, 65575, 65710, 65711, 65712, 65713, 65714, 65715, 65716, 65852, 65853, 65854, 65855, 65856, 65857, 65997, 65998, 65999, 66000, 66001, 66002, 66146, 66147, 66148, 66149, 66150, 66289, 66290, 69497, 69498, 69499, 69500, 69501, 69502, 69589, 69590, 69591, 69592, 69593, 69594, 69595, 69596, 69597, 69598, 69599, 69600, 69601, 69602, 69603, 69604, 69605, 69606, 69607, 69608, 69609, 69610, 69611, 69612, 69613, 69686, 69687, 69688, 69689, 69690, 69691, 69692, 69693, 69694, 69695, 69696, 69697, 69698, 69699, 69700, 69701, 69702, 69703, 69704, 69705, 69706, 69707, 69708, 69709, 69710, 69711, 69712, 69783, 69784, 69785, 69786, 69787, 69788, 69789, 69790, 69791, 69792, 69793, 69794, 69795, 69796, 69797, 69798, 69799, 69800, 69801, 69802, 69803, 69804, 69805, 69806, 69807, 69808, 69809, 69810, 69811, 69882, 69883, 69884, 69885, 69886, 69887, 69888, 69889, 69890, 69891, 69892, 69893, 69894, 69895, 69896, 69897, 69898, 69899, 69900, 69901, 69902, 69903, 69904, 69905, 69906, 69907, 69908, 69909, 69910, 69995, 69996, 69997, 69998, 69999, 70000, 70001, 70002, 70003, 70004, 70005, 70006, 70007, 70008, 70009, 70010, 70011, 70012, 70013, 70014, 70015, 70016, 70017, 70018, 70019, 70020, 70021, 70095, 70096, 70097, 70098, 70099, 70100, 70101, 70102, 70103, 70104, 70105, 70106, 70107, 70108, 70109, 70110, 70111, 70112, 70113, 70114, 70115, 70116, 70117, 70118, 70119, 70212, 70213, 70214, 70215, 70216, 70217, 70218, 70219, 70220, 70221, 70222, 70223, 70224, 70225, 70226, 70227, 70228, 70229, 70230, 70231, 70232, 70233, 70234, 70335, 70337, 70338, 70339, 70340, 70341, 70342, 70343, 70344, 70345, 70346, 70347, 70348, 70349, 70350, 70351, 70352, 70353, 70354, 70355, 70356, 70357, 70454, 70455, 70456, 70457, 70458, 70459, 70460, 70461, 70462, 70463, 70464, 70465, 70466, 70467, 70468, 70469, 70470, 70471, 70472, 70473, 70580, 70581, 70582, 70583, 70584, 70585, 70586, 70587, 70588, 70589, 70590, 70591, 70592, 70593, 70594, 70595, 70596, 70597, 70598, 70709, 70710, 70711, 70712, 70713, 70714, 70715, 70716, 70717, 70718, 70719, 70720, 70721, 70722, 70723, 70724, 70725, 70843, 70844, 70845, 70846, 70847, 70848, 70849, 70850, 70851, 70852, 70853, 70854, 70855, 70856, 70857, 70858, 70859, 70976, 70977, 70978, 70979, 70980, 70981, 70982, 70983, 70984, 70985, 70986, 70987, 70988, 70989, 70990, 70991, 71115, 71116, 71117, 71118, 71119, 71120, 71121, 71122, 71123, 71124, 71125, 71126, 71127, 71128, 71129, 71251, 71252, 71253, 71254, 71255, 71256, 71257, 71258, 71259, 71260, 71261, 71262, 71263, 71264, 71265, 71391, 71392, 71393, 71394, 71395, 71396, 71397, 71398, 71399, 71400, 71401, 71402, 71403, 71404, 71405, 71534, 71535, 71536, 71537, 71538, 71539, 71540, 71541, 71542, 71543, 71544, 71545, 71546, 71547, 71682, 71683, 71684, 71685, 71686, 71687, 71688, 71689, 71690, 71691, 71692, 71693, 71694, 71830, 71831, 71832, 71833, 71834, 71835, 71836, 71837, 71838, 71839, 71840, 71841, 71842, 71973, 71974, 71975, 71976, 71977, 71978, 71979, 71980, 71981, 71982, 71983, 71984, 72124, 72125, 72126, 72127, 72128, 72129, 72130, 72131, 72132, 72133, 72134, 72135, 72270, 72271, 72272, 72273, 72274, 72275, 72276, 72277, 72278, 72279, 72409, 72410, 72411, 72412, 72413, 72414, 72415, 72416, 72417, 72418, 72552, 72553, 72554, 72555, 72556, 72557, 72558, 72559, 72560, 72561, 72696, 72697, 72698, 72699, 72700, 72701, 72702, 72703, 72842, 72843, 72844, 72845, 72846, 72847, 72848, 72849, 72986, 72987, 72988, 72989, 72990, 72991, 72992, 73127, 73128, 73129, 73130, 73131, 73132, 73271, 73272, 73273, 73274, 73275, 73276, 73416, 73417, 73418, 73419, 73420, 73562, 73563, 73564, 73702, 80416, 80417, 80566, 80567, 80568, 80569, 80570, 80571, 80718, 80719, 80720, 80721, 80722, 80723, 80724, 80874, 80875, 80876, 80877, 80878, 80879, 80880, 80881, 81031, 81032, 81033, 81034, 81035, 81036, 81037, 81038, 81039, 81185, 81186, 81187, 81188, 81189, 81190, 81191, 81192, 81193, 81332, 81333, 81334, 81335, 81336, 81337, 81338, 81339, 81340, 81341, 81485, 81486, 81487, 81488, 81489, 81490, 81491, 81492, 81493, 81494, 81495, 81631, 81632, 81633, 81634, 81635, 81636, 81637, 81638, 81639, 81640, 81641, 81781, 81782, 81783, 81784, 81785, 81786, 81787, 81788, 81789, 81790, 81791, 81792, 81921, 81922, 81923, 81924, 81925, 81926, 81927, 81928, 81929, 81930, 81931, 81932, 82064, 82065, 82066, 82067, 82068, 82069, 82070, 82071, 82072, 82073, 82074, 82075, 82207, 82208, 82209, 82210, 82211, 82212, 82213, 82214, 82215, 82216, 82217, 82218, 82219, 82351, 82352, 82353, 82354, 82355, 82356, 82357, 82358, 82359, 82360, 82361, 82362, 82363, 82364, 82490, 82491, 82492, 82493, 82494, 82495, 82496, 82497, 82498, 82499, 82500, 82501, 82502, 82503, 82630, 82631, 82632, 82633, 82634, 82635, 82636, 82637, 82638, 82639, 82640, 82641, 82642, 82643, 82769, 82770, 82771, 82772, 82773, 82774, 82775, 82776, 82777, 82778, 82779, 82780, 82781, 82782, 82783, 82892, 82893, 82894, 82895, 82896, 82897, 82898, 82899, 82900, 82901, 82902, 82903, 82904, 82905, 82906, 82907, 83021, 83022, 83023, 83024, 83025, 83026, 83027, 83028, 83029, 83030, 83031, 83032, 83033, 83034, 83035, 83036, 83037, 83148, 83149, 83150, 83151, 83152, 83153, 83154, 83155, 83156, 83157, 83158, 83159, 83160, 83161, 83162, 83163, 83164, 83278, 83279, 83280, 83281, 83282, 83283, 83284, 83285, 83286, 83287, 83288, 83289, 83290, 83291, 83292, 83293, 83294, 83295, 83413, 83414, 83415, 83416, 83417, 83418, 83419, 83420, 83421, 83422, 83423, 83424, 83425, 83426, 83427, 83428, 83429, 83430, 83431, 83432, 83540, 83541, 83542, 83543, 83544, 83545, 83546, 83547, 83548, 83549, 83550, 83551, 83552, 83553, 83554, 83555, 83556, 83557, 83558, 83559, 83560, 83663, 83664, 83665, 83666, 83667, 83668, 83669, 83670, 83671, 83672, 83673, 83674, 83675, 83676, 83677, 83678, 83679, 83680, 83681, 83682, 83683, 83684, 83781, 83783, 83784, 83785, 83786, 83787, 83788, 83789, 83790, 83791, 83792, 83793, 83794, 83795, 83796, 83797, 83798, 83799, 83800, 83801, 83802, 83803, 83804, 83805, 83901, 83902, 83903, 83904, 83905, 83906, 83907, 83908, 83909, 83910, 83911, 83912, 83913, 83914, 83915, 83916, 83917, 83918, 83919, 83920, 83921, 83922, 83923, 83924, 83925, 84009, 84010, 84011, 84012, 84013, 84014, 84015, 84016, 84017, 84018, 84019, 84020, 84021, 84022, 84023, 84024, 84025, 84026, 84027, 84028, 84029, 84030, 84031, 84032, 84033, 84034, 84118, 84119, 84120, 84121, 84122, 84123, 84124, 84125, 84126, 84127, 84128, 84129, 84130, 84131, 84132, 84133, 84134, 84135, 84136, 84137, 84138, 84139, 84140, 84141, 84142, 84143, 84144, 84145, 84222, 84223, 84224, 84225, 84226, 84227, 84228, 84229, 84230, 84231, 84232, 84233, 84234, 84235, 84236, 84237, 84238, 84239, 84240, 84241, 84242, 84243, 84244, 84245, 84246, 84247, 84248, 84323, 84324, 84325, 84326, 84327, 84328, 84329, 84330, 84331, 84332, 84333, 84334, 84335, 84336, 84337, 84338, 84339, 84340, 84341, 84342, 84343, 84344, 84345, 84346, 84347, 84348, 84423, 84424, 84425, 84426, 84427, 84428, 84429, 84430, 84431, 84432, 84433, 84434, 84435, 84436, 84437, 84438, 84439, 84440, 84441, 84442, 84443, 84529, 84530, 84531, 88231, 88232, 88378, 88379, 88380, 88381, 88382, 88527, 88528, 88529, 88530, 88531, 88532, 88533, 88687, 88688, 88689, 88690, 88691, 88692, 88693, 88694, 88842, 88843, 88844, 88845, 88846, 88847, 88848, 88849, 88997, 88998, 88999, 89000, 89001, 89002, 89003, 89004, 89005, 89149, 89150, 89151, 89152, 89153, 89154, 89155, 89156, 89157, 89158, 89299, 89300, 89301, 89302, 89303, 89304, 89305, 89306, 89307, 89308, 89309, 89443, 89444, 89445, 89446, 89447, 89448, 89449, 89450, 89451, 89452, 89453, 89588, 89589, 89590, 89591, 89592, 89593, 89594, 89595, 89596, 89597, 89598, 89599, 89732, 89733, 89734, 89735, 89736, 89737, 89738, 89739, 89740, 89741, 89742, 89743, 89876, 89877, 89878, 89879, 89880, 89881, 89882, 89883, 89884, 89885, 89886, 89887, 90018, 90019, 90020, 90021, 90022, 90023, 90024, 90025, 90026, 90027, 90028, 90029, 90030, 90157, 90158, 90159, 90160, 90161, 90162, 90163, 90164, 90165, 90166, 90167, 90168, 90169, 90170, 90304, 90305, 90306, 90307, 90308, 90309, 90310, 90311, 90312, 90313, 90314, 90315, 90316, 90317, 90441, 90442, 90443, 90444, 90445, 90446, 90447, 90448, 90449, 90450, 90451, 90452, 90453, 90454, 90455, 90579, 90580, 90581, 90582, 90583, 90584, 90585, 90586, 90587, 90588, 90589, 90590, 90591, 90592, 90593, 90707, 90708, 90709, 90710, 90711, 90712, 90713, 90714, 90715, 90716, 90717, 90718, 90719, 90720, 90721, 90722, 90837, 90838, 90839, 90840, 90841, 90842, 90843, 90844, 90845, 90846, 90847, 90848, 90849, 90850, 90851, 90852, 90853, 90961, 90962, 90963, 90964, 90965, 90966, 90967, 90968, 90969, 90970, 90971, 90972, 90973, 90974, 90975, 90976, 90977, 91092, 91093, 91094, 91095, 91096, 91097, 91098, 91099, 91100, 91101, 91102, 91103, 91104, 91105, 91106, 91107, 91108, 91109, 91230, 91231, 91232, 91233, 91234, 91235, 91236, 91237, 91238, 91239, 91240, 91241, 91242, 91243, 91244, 91245, 91246, 91247, 91248, 91249, 91358, 91359, 91360, 91361, 91362, 91363, 91364, 91365, 91366, 91367, 91368, 91369, 91370, 91371, 91372, 91373, 91374, 91375, 91376, 91377, 91378, 91482, 91483, 91484, 91485, 91486, 91487, 91488, 91489, 91490, 91491, 91492, 91493, 91494, 91495, 91496, 91497, 91498, 91499, 91500, 91501, 91502, 91503, 91608, 91609, 91610, 91611, 91612, 91613, 91614, 91615, 91616, 91617, 91618, 91619, 91620, 91621, 91622, 91623, 91624, 91625, 91626, 91627, 91628, 91629, 91630, 91631, 91732, 91733, 91734, 91735, 91736, 91737, 91738, 91739, 91740, 91741, 91742, 91743, 91744, 91745, 91746, 91747, 91748, 91749, 91750, 91751, 91752, 91753, 91754, 91755, 91756, 91757, 91865, 91866, 91867, 91868, 91869, 91870, 91871, 91872, 91873, 91874, 91875, 91876, 91877, 91878, 91879, 91880, 91881, 91882, 91883, 91884, 91885, 91886, 91887, 91888, 91889, 91890, 91891, 91892, 91975, 91976, 91977, 91978, 91979, 91980, 91981, 91982, 91983, 91984, 91985, 91986, 91987, 91988, 91989, 91990, 91991, 91992, 91993, 91994, 91995, 91996, 91997, 91998, 91999, 92000, 92001, 92002, 92003, 92004, 92092, 92093, 92094, 92095, 92096, 92097, 92098, 92099, 92100, 92101, 92102, 92103, 92104, 92105, 92106, 92107, 92108, 92109, 92110, 92111, 92112, 92113, 92114, 92115, 92116, 92117, 92118, 92119, 92213, 92214, 92215, 92216, 92217, 92218, 92219, 92220, 92221, 92222, 92223, 92224, 92225, 92226, 92227, 92228, 92229, 92230, 92231, 92232, 92233, 92234, 92235, 92236, 92237, 92238, 92239, 92344, 92345, 92346, 92347, 92348, 92349, 92350, 92351, 92352, 92353, 92354, 92355, 92356, 92357, 92358, 92359, 92360, 92361, 92362, 92363, 125807, 125867, 125926, 125985, 126047, 126109, 126169, 126229, 126289, 126349, 126410, 126471, 126530, 126589, 126648, 126707, 126767, 126827, 126889, 126951, 127012, 127073, 127132, 127191, 127249, 127307, 127362, 127417, 127473, 127529, 127581, 127633, 127687, 127741, 127792, 127843, 127897, 127951, 128006, 128061, 128112, 128113, 128164, 128165, 128217, 128218, 128270, 128271, 128323, 128324, 128376, 128377, 128425, 128426, 128474, 128475, 128526, 128527, 128578, 128579, 128629, 128630, 128680, 128681, 128720, 128732, 128733, 128772, 128784, 128785, 128826, 128838, 128839, 128880, 128892, 128893, 128935, 128947, 128948, 128990, 129002, 129003, 129037, 129043, 129055, 129056, 129090, 129096, 129108, 129109, 129141, 129147, 129159, 129160, 129192, 129198, 129210, 129211, 129243, 129249, 129261, 129262, 129294, 129300, 129312, 129313, 129338, 129344, 129350, 129362, 129363, 129388, 129394, 129400, 129412, 129413, 129437, 129443, 129449, 129460, 129461, 129462, 129486, 129492, 129498, 129509, 129510, 129511, 129536, 129542, 129548, 129559, 129560, 129561, 129586, 129592, 129598, 129609, 129610, 129611, 129632, 129638, 129644, 129655, 129656, 129657, 129678, 129684, 129690, 129701, 129702, 129703, 129722, 129728, 129734, 129740, 129750, 129751, 129752, 129753, 129772, 129778, 129784, 129790, 129800, 129801, 129802, 129803, 129821, 129827, 129833, 129838, 129839, 129849, 129850, 129851, 129852, 129870, 129876, 129882, 129887, 129888, 129898, 129899, 129900, 129901, 129919, 129925, 129931, 129936, 129946, 129947, 129948, 129949, 129967, 129973, 129979, 129984, 129994, 129995, 129996, 129997, 130014, 130020, 130025, 130030, 130031, 130040, 130041, 130042, 130043, 130044, 130061, 130067, 130072, 130077, 130078, 130087, 130088, 130089, 130090, 130091, 130112, 130117, 130118, 130123, 130124, 130133, 130134, 130135, 130136, 130137, 130158, 130163, 130164, 130169, 130170, 130179, 130180, 130181, 130182, 130183, 130197, 130203, 130208, 130209, 130214, 130215, 130224, 130225, 130226, 130227, 130228, 130242, 130248, 130253, 130254, 130259, 130260, 130269, 130270, 130271, 130272, 130273, 130284, 130289, 130295, 130300, 130301, 130306, 130307, 130315, 130316, 130317, 130318, 130319, 130320, 130331, 130336, 130342, 130347, 130348, 130353, 130354, 130362, 130363, 130364, 130365, 130366, 130367, 130385, 130390, 130391, 130396, 130397, 130405, 130406, 130407, 130408, 130409, 130410, 130428, 130433, 130434, 130439, 130440, 130448, 130449, 130450, 130451, 130452, 130453, 130468, 130469, 130474, 130475, 130480, 130481, 130489, 130490, 130491, 130492, 130493, 130494, 130509, 130510, 130515, 130516, 130521, 130522, 130530, 130531, 130532, 130533, 130534, 130535, 130551, 130557, 130558, 130563, 130564, 130569, 130570, 130577, 130578, 130579, 130580, 130581, 130582, 130583, 130599, 130605, 130606, 130611, 130612, 130617, 130618, 130625, 130626, 130627, 130628, 130629, 130630, 130631, 130650, 130651, 130656, 130657, 130662, 130663, 130670, 130671, 130672, 130673, 130674, 130675, 130676, 130695, 130696, 130701, 130702, 130707, 130708, 130715, 130716, 130717, 130718, 130719, 130720, 130721, 130735, 130741, 130742, 130747, 130748, 130753, 130754, 130760, 130761, 130762, 130763, 130764, 130765, 130766, 130767, 130781, 130787, 130788, 130793, 130794, 130799, 130800, 130806, 130807, 130808, 130809, 130810, 130811, 130812, 130813, 130824, 130829, 130835, 130839, 130840, 130841, 130847, 130848, 130849, 130850, 130851, 130852, 130853, 130854, 130866, 130871, 130877, 130881, 130882, 130883, 130889, 130890, 130891, 130892, 130893, 130894, 130895, 130896, 130906, 130911, 130917, 130921, 130922, 130923, 130929, 130930, 130931, 130932, 130933, 130934, 130935, 130936, 130947, 130952, 130958, 130962, 130963, 130964, 130970, 130971, 130972, 130973, 130974, 130975, 130976, 130977, 130992, 130997, 130998, 131002, 131003, 131004, 131010, 131011, 131012, 131013, 131014, 131015, 131016, 131017, 131033, 131038, 131039, 131043, 131044, 131045, 131051, 131052, 131053, 131054, 131055, 131056, 131057, 131058, 131071, 131076, 131081, 131082, 131087, 131088, 131089, 131094, 131095, 131096, 131097, 131098, 131099, 131100, 131101, 131102, 131115, 131120, 131125, 131126, 131131, 131132, 131133, 131138, 131139, 131140, 131141, 131142, 131143, 131144, 131145, 131146, 131162, 131167, 131168, 131173, 131174, 131175, 131180, 131181, 131182, 131183, 131184, 131185, 131186, 131187, 131188, 131205, 131210, 131211, 131216, 131217, 131218, 131223, 131224, 131225, 131226, 131227, 131228, 131229, 131230, 131231, 131247, 131252, 131253, 131258, 131259, 131260, 131264, 131265, 131266, 131267, 131268, 131269, 131270, 131271, 131272, 131273, 131289, 131294, 131295, 131300, 131301, 131302, 131306, 131307, 131308, 131309, 131310, 131311, 131312, 131313, 131314, 131315, 131333, 131334, 131338, 131339, 131344, 131345, 131350, 131351, 131352, 131353, 131354, 131355, 131356, 131357, 131358, 131359, 131377, 131378, 131382, 131383, 131388, 131389, 131394, 131395, 131396, 131397, 131398, 131399, 131400, 131401, 131402, 131403, 131420, 131421, 131426, 131427, 131432, 131433, 131438, 131439, 131440, 131441, 131442, 131443, 131444, 131445, 131446, 131447, 131464, 131465, 131470, 131471, 131476, 131477, 131482, 131483, 131484, 131485, 131486, 131487, 131488, 131489, 131490, 131491, 131509, 131510, 131515, 131516, 131521, 131522, 131526, 131527, 131528, 131529, 131530, 131531, 131532, 131533, 131534, 131535, 131536, 131554, 131555, 131560, 131561, 131566, 131567, 131571, 131572, 131573, 131574, 131575, 131576, 131577, 131578, 131579, 131580, 131581, 131597, 131598, 131603, 131604, 131609, 131610, 131614, 131615, 131616, 131617, 131618, 131619, 131620, 131621, 131622, 131623, 131624, 131640, 131641, 131646, 131647, 131652, 131653, 131657, 131658, 131659, 131660, 131661, 131662, 131663, 131664, 131665, 131666, 131667, 131678, 131684, 131690, 131691, 131696, 131697, 131701, 131702, 131703, 131704, 131705, 131706, 131707, 131708, 131709, 131710, 131711, 131722, 131728, 131734, 131735, 131740, 131741, 131745, 131746, 131747, 131748, 131749, 131750, 131751, 131752, 131753, 131754, 131755, 131777, 131778, 131782, 131783, 131784, 131788, 131789, 131790, 131791, 131792, 131793, 131794, 131795, 131796, 131797, 131798, 131822, 131823, 131827, 131828, 131829, 131833, 131834, 131835, 131836, 131837, 131838, 131839, 131840, 131841, 131842, 131843, 131856, 131862, 131868, 131869, 131873, 131874, 131875, 131879, 131880, 131881, 131882, 131883, 131884, 131885, 131886, 131887, 131888, 131889, 131902, 131908, 131914, 131915, 131919, 131920, 131921, 131925, 131926, 131927, 131928, 131929, 131930, 131931, 131932, 131933, 131934, 131935, 131947, 131953, 131959, 131964, 131965, 131966, 131970, 131971, 131972, 131973, 131974, 131975, 131976, 131977, 131978, 131979, 131980, 131992, 131998, 132004, 132009, 132010, 132011, 132015, 132016, 132017, 132018, 132019, 132020, 132021, 132022, 132023, 132024, 132025, 132037, 132041, 132046, 132047, 132052, 132053, 132054, 132058, 132059, 132060, 132061, 132062, 132063, 132064, 132065, 132066, 132067, 132068, 132081, 132085, 132090, 132091, 132096, 132097, 132098, 132102, 132103, 132104, 132105, 132106, 132107, 132108, 132109, 132110, 132111, 132112, 132123, 132128, 132133, 132134, 132139, 132140, 132141, 132145, 132146, 132147, 132148, 132149, 132150, 132151, 132152, 132153, 132154, 132155, 132167, 132172, 132177, 132178, 132183, 132184, 132185, 132189, 132190, 132191, 132192, 132193, 132194, 132195, 132196, 132197, 132198, 132199, 132217, 132222, 132223, 132228, 132229, 132234, 132235, 132236, 132237, 132238, 132239, 132240, 132241, 132242, 132243, 132244, 132262, 132267, 132268, 132273, 132274, 132279, 132280, 132281, 132282, 132283, 132284, 132285, 132286, 132287, 132288, 132289, 132308, 132313, 132314, 132319, 132320, 132325, 132326, 132327, 132328, 132329, 132330, 132331, 132332, 132333, 132334, 132335, 132355, 132360, 132361, 132366, 132367, 132372, 132373, 132374, 132375, 132376, 132377, 132378, 132379, 132380, 132381, 132382, 132400, 132401, 132406, 132407, 132412, 132413, 132418, 132419, 132420, 132421, 132422, 132423, 132424, 132425, 132426, 132427, 132428, 132446, 132447, 132452, 132453, 132458, 132459, 132464, 132465, 132466, 132467, 132468, 132469, 132470, 132471, 132472, 132473, 132474, 132491, 132496, 132497, 132502, 132503, 132507, 132508, 132509, 132510, 132511, 132512, 132513, 132514, 132515, 132516, 132517, 132518, 132536, 132541, 132542, 132547, 132548, 132552, 132553, 132554, 132555, 132556, 132557, 132558, 132559, 132560, 132561, 132562, 132563, 132577, 132583, 132584, 132588, 132589, 132593, 132594, 132595, 132596, 132597, 132598, 132599, 132600, 132601, 132602, 132603, 132604, 132619, 132625, 132626, 132630, 132631, 132635, 132636, 132637, 132638, 132639, 132640, 132641, 132642, 132643, 132644, 132645, 132646, 132660, 132671, 132672, 132677, 132678, 132682, 132683, 132684, 132685, 132686, 132687, 132688, 132689, 132690, 132691, 132692, 132693, 132707, 132718, 132719, 132724, 132725, 132729, 132730, 132731, 132732, 132733, 132734, 132735, 132736, 132737, 132738, 132739, 132740, 132751, 132757, 132763, 132764, 132768, 132769, 132770, 132774, 132775, 132776, 132777, 132778, 132779, 132780, 132781, 132782, 132783, 132784, 132785, 132797, 132803, 132809, 132810, 132814, 132815, 132816, 132820, 132821, 132822, 132823, 132824, 132825, 132826, 132827, 132828, 132829, 132830, 132831, 132847, 132853, 132858, 132859, 132860, 132864, 132865, 132866, 132867, 132868, 132869, 132870, 132871, 132872, 132873, 132874, 132875, 132891, 132897, 132902, 132903, 132904, 132908, 132909, 132910, 132911, 132912, 132913, 132914, 132915, 132916, 132917, 132918, 132919, 132932, 132938, 132944, 132949, 132950, 132951, 132955, 132956, 132957, 132958, 132959, 132960, 132961, 132962, 132963, 132964, 132965, 132966, 132979, 132985, 132991, 132996, 132997, 132998, 133002, 133003, 133004, 133005, 133006, 133007, 133008, 133009, 133010, 133011, 133012, 133013, 133030, 133035, 133040, 133041, 133042, 133046, 133047, 133048, 133049, 133050, 133051, 133052, 133053, 133054, 133055, 133056, 133057, 133075, 133080, 133085, 133086, 133087, 133091, 133092, 133093, 133094, 133095, 133096, 133097, 133098, 133099, 133100, 133101, 133102, 133116, 133121, 133122, 133127, 133128, 133133, 133134, 133135, 133136, 133137, 133138, 133139, 133140, 133141, 133142, 133143, 133144, 133159, 133164, 133165, 133170, 133171, 133176, 133177, 133178, 133179, 133180, 133181, 133182, 133183, 133184, 133185, 133186, 133187, 133206, 133211, 133212, 133217, 133218, 133223, 133224, 133225, 133226, 133227, 133228, 133229, 133230, 133231, 133232, 133233, 133234, 133253, 133258, 133259, 133264, 133265, 133270, 133271, 133272, 133273, 133274, 133275, 133276, 133277, 133278, 133279, 133280, 133281, 133301, 133306, 133307, 133312, 133313, 133318, 133319, 133320, 133321, 133322, 133323, 133324, 133325, 133326, 133327, 133328, 133329, 133349, 133354, 133355, 133360, 133361, 133366, 133367, 133368, 133369, 133370, 133371, 133372, 133373, 133374, 133375, 133376, 133377, 133401, 133402, 133407, 133408, 133413, 133414, 133418, 133419, 133420, 133421, 133422, 133423, 133424, 133425, 133426, 133427, 133428, 133429, 133430, 133455, 133456, 133461, 133462, 133467, 133468, 133472, 133473, 133474, 133475, 133476, 133477, 133478, 133479, 133480, 133481, 133482, 133483, 133484, 133505, 133511, 133515, 133516, 133519, 133520, 133521, 133522, 133523, 133524, 133525, 133526, 133527, 133528, 133529, 133530, 133531, 133552, 133558, 133562, 133563, 133566, 133567, 133568, 133569, 133570, 133571, 133572, 133573, 133574, 133575, 133576, 133577, 133578, 133593, 133604, 133605, 133610, 133611, 133615, 133616, 133617, 133618, 133619, 133620, 133621, 133622, 133623, 133624, 133625, 133626, 133627, 133643, 133654, 133655, 133660, 133661, 133665, 133666, 133667, 133668, 133669, 133670, 133671, 133672, 133673, 133674, 133675, 133676, 133677, 133693, 133699, 133705, 133706, 133709, 133710, 133711, 133715, 133716, 133717, 133718, 133719, 133720, 133721, 133722, 133723, 133724, 133725, 133726, 133727, 133744, 133750, 133756, 133757, 133760, 133761, 133762, 133766, 133767, 133768, 133769, 133770, 133771, 133772, 133773, 133774, 133775, 133776, 133777, 133778, 133795, 133801, 133807, 133812, 133813, 133814, 133818, 133819, 133820, 133821, 133822, 133823, 133824, 133825, 133826, 133827, 133828, 133829, 133830, 133847, 133853, 133859, 133864, 133865, 133866, 133870, 133871, 133872, 133873, 133874, 133875, 133876, 133877, 133878, 133879, 133880, 133881, 133882, 133897, 133903, 133909, 133914, 133915, 133916, 133920, 133921, 133922, 133923, 133924, 133925, 133926, 133927, 133928, 133929, 133930, 133931, 133932, 133947, 133953, 133959, 133964, 133965, 133966, 133970, 133971, 133972, 133973, 133974, 133975, 133976, 133977, 133978, 133979, 133980, 133981, 133982, 134003, 134008, 134013, 134014, 134015, 134019, 134020, 134021, 134022, 134023, 134024, 134025, 134026, 134027, 134028, 134029, 134030, 134031, 134052, 134057, 134062, 134063, 134064, 134068, 134069, 134070, 134071, 134072, 134073, 134074, 134075, 134076, 134077, 134078, 134079, 134080, 134101, 134106, 134107, 134112, 134113, 134118, 134119, 134120, 134121, 134122, 134123, 134124, 134125, 134126, 134127, 134128, 134129, 134130, 134151, 134156, 134157, 134162, 134163, 134168, 134169, 134170, 134171, 134172, 134173, 134174, 134175, 134176, 134177, 134178, 134179, 134180, 134200, 134206, 134211, 134212, 134217, 134218, 134223, 134224, 134225, 134226, 134227, 134228, 134229, 134230, 134231, 134232, 134233, 134234, 134235, 134255, 134261, 134266, 134267, 134272, 134273, 134278, 134279, 134280, 134281, 134282, 134283, 134284, 134285, 134286, 134287, 134288, 134289, 134290, 134313, 134318, 134319, 134324, 134325, 134330, 134331, 134332, 134333, 134334, 134335, 134336, 134337, 134338, 134339, 134340, 134341, 134342, 134365, 134370, 134371, 134376, 134377, 134382, 134383, 134384, 134385, 134386, 134387, 134388, 134389, 134390, 134391, 134392, 134393, 134394, 134415, 134416, 134421, 134422, 134427, 134428, 134433, 134434, 134436, 134437, 134438, 134439, 134440, 134441, 134442, 134443, 134444, 134445, 134466, 134467, 134472, 134473, 134478, 134479, 134484, 134485, 134487, 134488, 134489, 134490, 134491, 134492, 134493, 134494, 134495, 134496, 134520, 134526, 134527, 134532, 134533, 134538, 134539, 134541, 134542, 134543, 134544, 134545, 134546, 134547, 134548, 134549, 134550, 134574, 134580, 134581, 134586, 134587, 134592, 134593, 134595, 134596, 134597, 134598, 134599, 134600, 134601, 134602, 134603, 134604, 134628, 134634, 134635, 134640, 134641, 134646, 134647, 134650, 134651, 134652, 134653, 134654, 134655, 134656, 134657, 134658, 134682, 134688, 134689, 134694, 134695, 134700, 134701, 134704, 134705, 134706, 134707, 134708, 134709, 134710, 134711, 134712, 134740, 134741, 134746, 134747, 134752, 134753, 134756, 134757, 134758, 134759, 134760, 134761, 134762, 134763, 134764, 134793, 134794, 134799, 134800, 134805, 134806, 134809, 134810, 134811, 134812, 134813, 134814, 134815, 134816, 134817, 134836, 134842, 134848, 134854, 134855, 134860, 134861, 134864, 134865, 134866, 134867, 134868, 134869, 134870, 134871, 134872, 134891, 134897, 134903, 134909, 134910, 134915, 134916, 134919, 134920, 134921, 134922, 134923, 134924, 134925, 134926, 134927, 134954, 134960, 134961, 134966, 134967, 134971, 134972, 134973, 134974, 134975, 134976, 134977, 134978, 135005, 135011, 135012, 135017, 135018, 135022, 135023, 135024, 135025, 135026, 135027, 135028, 135029, 135046, 135052, 135057, 135063, 135064, 135069, 135070, 135074, 135075, 135076, 135077, 135078, 135079, 135080, 135081, 135098, 135104, 135109, 135115, 135116, 135121, 135122, 135126, 135127, 135128, 135129, 135130, 135131, 135132, 135133, 135155, 135161, 135167, 135173, 135174, 135178, 135179, 135180, 135181, 135182, 135183, 135184, 135185, 135207, 135213, 135219, 135225, 135226, 135230, 135231, 135232, 135233, 135234, 135235, 135236, 135237, 135262, 135267, 135273, 135279, 135280, 135285, 135286, 135287, 135288, 135289, 135290, 135291, 135310, 135316, 135321, 135327, 135333, 135334, 135339, 135340, 135341, 135342, 135343, 135344, 135345, 135371, 135381, 135387, 135388, 135393, 135394, 135395, 135396, 135397, 135398, 135399, 135425, 135435, 135441, 135442, 135447, 135448, 135449, 135450, 135451, 135452, 135453, 135486, 135492, 135498, 135504, 135505, 135506, 135507, 135508, 135509, 135510, 135543, 135549, 135555, 135561, 135562, 135563, 135564, 135565, 135566, 135567, 135601, 135607, 135613, 135620, 135621, 135622, 135623, 135624, 135625, 135659, 135665, 135671, 135678, 135679, 135680, 135681, 135682, 135683, 135716, 135722, 135728, 135735, 135736, 135737, 135738, 135739, 135740, 135773, 135779, 135785, 135792, 135793, 135794, 135795, 135796, 135797, 135830, 135836, 135842, 135850, 135851, 135852, 135853, 135854, 135888, 135894, 135900, 135908, 135909, 135910, 135911, 135912, 135951, 135957, 135965, 135966, 135967, 135968, 135969, 136009, 136015, 136023, 136024, 136025, 136026, 136027, 136067, 136073, 136081, 136082, 136083, 136084, 136085, 136125, 136131, 136139, 136140, 136141, 136142, 136143, 136182, 136188, 136197, 136198, 136199, 136200, 136239, 136245, 136254, 136255, 136256, 136257, 136300, 136309, 136310, 136311, 136312, 136355, 136364, 136365, 136366, 136367, 136414, 136423, 136424, 136425, 136426, 136473, 136482, 136483, 136484, 136485, 136531, 136541, 136542, 136543, 136589, 136599, 136600, 136601, 136659, 136660, 136661, 136719, 136720, 136721, 136779, 136780, 136781, 136839, 136840, 136841, 136901, 136902, 136962, 136963, 137025, 137026, 137088, 137089, 137149, 137150, 137210, 137211, 137271, 137272, 137332, 137333, 137394, 137395, 137456, 137457, 137519, 137520, 137582, 137583, 137644, 137645, 137706, 137707, 137765, 137766, 137824, 137825, 137884, 137885, 137944, 137945, 138004, 138005, 138064, 138065, 138123, 138124, 138182, 138183, 138242, 138243, 138302, 138303, 138362, 138363, 138422, 138423, 138483, 138484, 138544, 138545, 138605, 138665, 138726, 138787, 138848, 138909, 138970, 139031, 139092, 139153, 139214, 139275, 139336, 139397, 139456, 139515, 139575, 139635, 139696, 139757, 139819, 139881, 139943, 140005, 140067, 140129, 140191, 140253, 140315, 140377, 140439, 140501, 140563, 140625, 140687, 140749, 140811, 140873], "numberOfPointsInBox": 6792}, "attributes": {"trackId": "ego_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "23b469d0-d2c0-4158-9794-29442f5288c1", "objectId": "fa0f2957-29a5-404c-95f1-45c44ff58103", "className": "3D_track", "geometry": {"associatedPoints": [2567, 2579, 2596, 2601, 2606, 2607, 2608, 2612, 2617, 2622, 2623, 2627, 2628, 2632, 2633, 2638, 2639, 2642, 2643, 2644, 2648, 2649, 2650, 2654, 2655, 2656, 2659, 2660, 2661, 2665, 2666, 2667, 2671, 2672, 2673, 2676, 2677, 2678, 2682, 2683, 2684, 2688, 2689, 2690, 2694, 2695, 2696, 2700, 2701, 2702, 2704, 2705, 2706, 2708, 2709, 2710, 2714, 2715, 2716, 2720, 2721, 2725, 2726, 2731, 2732, 2736, 2737, 2741, 2742, 2745, 2749, 2754, 2760, 2766, 2772, 5716, 5719, 5724, 5726, 5728, 5730, 5734, 5737, 5740, 5744, 5746, 7349, 7355, 7361, 7366, 7372, 7382, 7387, 7388, 7393, 7398, 7406, 7411, 7416, 10266, 10272, 10278, 10284, 10289, 10290, 10295, 10296, 10301, 10302, 10307, 10308, 10313, 10314, 10318, 10319, 10320, 10324, 10325, 10326, 10330, 10331, 10332, 10336, 10337, 10338, 10342, 10343, 10344, 10347, 10348, 10349, 10352, 10353, 10354, 10358, 10359, 10360, 10363, 10364, 10365, 10369, 10370, 10371, 10375, 10376, 10381, 10382, 10386, 10387, 10388, 10391, 10392, 10393, 10397, 10398, 10399, 10402, 10403, 10404, 10407, 10408, 10411, 10412, 10415, 10416, 10418, 10422, 10423, 10426, 10427, 10429, 10430, 10431, 10433, 10434, 10436, 10437, 10439, 10441, 10444, 10449, 10453, 10455, 10457, 10459, 13157, 13163, 13169, 13175, 13180, 13181, 13186, 13187, 13192, 13193, 13198, 13199, 13203, 13204, 13205, 13209, 13210, 13211, 13215, 13216, 13217, 13221, 13222, 13223, 13227, 13228, 13229, 13232, 13233, 13234, 13235, 13238, 13239, 13240, 13241, 13244, 13245, 13246, 13247, 13250, 13251, 13252, 13253, 13256, 13257, 13258, 13261, 13262, 13263, 13264, 13267, 13268, 13269, 13270, 13273, 13274, 13275, 13276, 13279, 13280, 13281, 13282, 13284, 13285, 13286, 13287, 13290, 13291, 13292, 13293, 13296, 13297, 13298, 13299, 13302, 13303, 13304, 13305, 13308, 13309, 13310, 13311, 13314, 13315, 13316, 13320, 13321, 13322, 13326, 13327, 13328, 13332, 13333, 13334, 13338, 13339, 13340, 13344, 13345, 13350, 13351, 13355, 13356, 13360, 13361, 13365, 13366, 13370, 13371, 13376, 13381, 13386, 13391, 13395, 13399, 13404, 16049, 16055, 16061, 16067, 16073, 16079, 16084, 16085, 16090, 16091, 16096, 16097, 16102, 16103, 16108, 16109, 16113, 16114, 16115, 16119, 16120, 16121, 16125, 16126, 16127, 16131, 16132, 16133, 16137, 16138, 16139, 16142, 16143, 16144, 16145, 16148, 16149, 16150, 16151, 16154, 16155, 16156, 16157, 16160, 16161, 16162, 16163, 16165, 16166, 16167, 16168, 16169, 16171, 16172, 16173, 16174, 16177, 16178, 16179, 16180, 16183, 16184, 16185, 16186, 16189, 16190, 16191, 16192, 16194, 16195, 16196, 16197, 16198, 16200, 16201, 16202, 16203, 16206, 16207, 16208, 16209, 16212, 16213, 16214, 16215, 16218, 16219, 16220, 16221, 16224, 16225, 16226, 16230, 16231, 16232, 16236, 16237, 16238, 16242, 16243, 16244, 16248, 16249, 16250, 16254, 16255, 16260, 16261, 16266, 16267, 16272, 16273, 16278, 16279, 16284, 16290, 16296, 16302, 16308, 17698, 17704, 17710, 17711, 17716, 17717, 17722, 17723, 17728, 17729, 17730, 17734, 17735, 17736, 17740, 17741, 17742, 17743, 17746, 17747, 17748, 17749, 17752, 17753, 17754, 17755, 17758, 17759, 17760, 17761, 17762, 17764, 17765, 17766, 17767, 17768, 17770, 17771, 17772, 17773, 17774, 17776, 17777, 17778, 17779, 17780, 17781, 17782, 17783, 17784, 17785, 17786, 17787, 17788, 17789, 17790, 17791, 17792, 17793, 17794, 17795, 17796, 17797, 17798, 17799, 17800, 17801, 17802, 17803, 17804, 17805, 17806, 17807, 17808, 17809, 17810, 17811, 17813, 17814, 17815, 17816, 17817, 17819, 17820, 17821, 17822, 17823, 17825, 17826, 17827, 17828, 17829, 17832, 17833, 17834, 17835, 17838, 17839, 17840, 17841, 17845, 17846, 17847, 17851, 17852, 17853, 17857, 17858, 17859, 17864, 17865, 17870, 17871, 17876, 17877, 17883, 17889, 17895, 17901, 18285, 18291, 18297, 18303, 18308, 18309, 18314, 18315, 18320, 18321, 18325, 18326, 18327, 18331, 18332, 18333, 18337, 18338, 18339, 18342, 18343, 18344, 18345, 18348, 18349, 18350, 18351, 18354, 18355, 18356, 18357, 18359, 18360, 18361, 18362, 18365, 18366, 18367, 18368, 18371, 18372, 18373, 18374, 18376, 18377, 18378, 18379, 18382, 18383, 18384, 18385, 18388, 18389, 18390, 18391, 18394, 18395, 18396, 18400, 18401, 18402, 18406, 18407, 18408, 18412, 18413, 18414, 18418, 18419, 18424, 18425, 18430, 18431, 18435, 18440, 18445, 22120, 22126, 22132, 22138, 22139, 22144, 22145, 22150, 22151, 22152, 22156, 22157, 22158, 22162, 22163, 22164, 22168, 22169, 22170, 22171, 22174, 22175, 22176, 22177, 22180, 22181, 22182, 22183, 22184, 22186, 22187, 22188, 22189, 22190, 22192, 22193, 22194, 22195, 22196, 22198, 22199, 22200, 22201, 22202, 22203, 22204, 22205, 22206, 22207, 22208, 22209, 22210, 22211, 22212, 22213, 22214, 22215, 22216, 22217, 22218, 22219, 22220, 22221, 22223, 22224, 22225, 22226, 22227, 22229, 22230, 22231, 22232, 22233, 22236, 22237, 22238, 22239, 22242, 22243, 22244, 22245, 22248, 22249, 22250, 22251, 22255, 22256, 22257, 22261, 22262, 22263, 22267, 22268, 22269, 22274, 22275, 22280, 22281, 22286, 22287, 22293, 22299, 22305, 22311, 22677, 22683, 22689, 22695, 22700, 22701, 22706, 22707, 22712, 22713, 22717, 22718, 22719, 22723, 22724, 22725, 22729, 22730, 22731, 22735, 22736, 22737, 22740, 22741, 22742, 22746, 22747, 22748, 22752, 22753, 22754, 22757, 22758, 22759, 22760, 22763, 22764, 22765, 22769, 22770, 22771, 22774, 22775, 22776, 22777, 22780, 22781, 22782, 22786, 22787, 22788, 22791, 22792, 22793, 22796, 22797, 22798, 22801, 22802, 22806, 22807, 22811, 22812, 22815, 22819, 22824, 23581, 23587, 23593, 23599, 23605, 23611, 23612, 23617, 23618, 23623, 23624, 23629, 23630, 23635, 23636, 23641, 23642, 23643, 23647, 23648, 23649, 23653, 23654, 23655, 23659, 23660, 23661, 23665, 23666, 23667, 23671, 23672, 23673, 23677, 23678, 23679, 23680, 23683, 23684, 23685, 23686, 23689, 23690, 23691, 23692, 23695, 23696, 23697, 23698, 23701, 23702, 23703, 23704, 23707, 23708, 23709, 23710, 23713, 23714, 23715, 23716, 23719, 23720, 23721, 23722, 23725, 23726, 23727, 23728, 23731, 23732, 23733, 23734, 23735, 23737, 23738, 23739, 23740, 23741, 23743, 23744, 23745, 23746, 23747, 23749, 23750, 23751, 23752, 23753, 23755, 23756, 23757, 23758, 23759, 23761, 23762, 23763, 23764, 23765, 23767, 23768, 23769, 23770, 23771, 23773, 23774, 23775, 23776, 23777, 23779, 23780, 23781, 23782, 23783, 23785, 23786, 23787, 23788, 23789, 23791, 23792, 23793, 23794, 23795, 23797, 23798, 23799, 23800, 23801, 23803, 23804, 23805, 23806, 23807, 23809, 23810, 23811, 23812, 23813, 23815, 23816, 23817, 23818, 23819, 23821, 23822, 23823, 23824, 23825, 23827, 23828, 23829, 23830, 23831, 23833, 23834, 23835, 23836, 23837, 23839, 23840, 23841, 23842, 23843, 23845, 23846, 23847, 23848, 23849, 23851, 23852, 23853, 23854, 23857, 23858, 23859, 23860, 23863, 23864, 23865, 23866, 23869, 23870, 23871, 23872, 23875, 23876, 23877, 23878, 23881, 23882, 23883, 23884, 23887, 23888, 23889, 23890, 23893, 23894, 23895, 23896, 23899, 23900, 23901, 23902, 23905, 23906, 23907, 23911, 23912, 23913, 23917, 23918, 23919, 23923, 23924, 23925, 23929, 23930, 23931, 23935, 23936, 23937, 23941, 23942, 23947, 23948, 23953, 23954, 23959, 23960, 23965, 23966, 23971, 23977, 23983, 23989, 26552, 26558, 26564, 26565, 26570, 26571, 26576, 26577, 26582, 26583, 26584, 26588, 26589, 26590, 26594, 26595, 26596, 26600, 26601, 26602, 26603, 26606, 26607, 26608, 26609, 26612, 26613, 26614, 26615, 26618, 26619, 26620, 26621, 26622, 26624, 26625, 26626, 26627, 26628, 26630, 26631, 26632, 26633, 26634, 26636, 26637, 26638, 26639, 26640, 26641, 26642, 26643, 26644, 26645, 26646, 26647, 26649, 26650, 26651, 26652, 26653, 26655, 26656, 26657, 26658, 26659, 26661, 26662, 26663, 26664, 26665, 26668, 26669, 26670, 26671, 26674, 26675, 26676, 26677, 26680, 26681, 26682, 26683, 26687, 26688, 26689, 26693, 26694, 26695, 26699, 26700, 26701, 26706, 26707, 26712, 26713, 26718, 26719, 26724, 26725, 26731, 26737, 26743, 26749, 27019, 27025, 27031, 27037, 27043, 27048, 27053, 27054, 27059, 27060, 27065, 27066, 27070, 27071, 27072, 27076, 27077, 27082, 27083, 27087, 27088, 27091, 27092, 27093, 27097, 27098, 27102, 27103, 27107, 27108, 27111, 27112, 27113, 27117, 27118, 27121, 27122, 27124, 27125, 27126, 27128, 27129, 27130, 27133, 27134, 27137, 27138, 27140, 27141, 27144, 27145, 27149, 27153, 27156, 27159, 27832, 27838, 27844, 27850, 27851, 27856, 27857, 27862, 27863, 27868, 27869, 27874, 27875, 27876, 27880, 27881, 27882, 27886, 27887, 27888, 27892, 27893, 27894, 27898, 27899, 27900, 27901, 27904, 27905, 27906, 27907, 27910, 27911, 27912, 27913, 27916, 27917, 27918, 27919, 27922, 27923, 27924, 27925, 27926, 27928, 27929, 27930, 27931, 27932, 27934, 27935, 27936, 27937, 27938, 27940, 27941, 27942, 27943, 27944, 27946, 27947, 27948, 27949, 27950, 27951, 27952, 27953, 27954, 27955, 27956, 27957, 27958, 27959, 27960, 27961, 27962, 27963, 27964, 27965, 27966, 27967, 27968, 27969, 27970, 27971, 27972, 27973, 27974, 27975, 27976, 27977, 27978, 27979, 27980, 27981, 27982, 27983, 27984, 27985, 27986, 27987, 27988, 27989, 27990, 27991, 27992, 27993, 27994, 27995, 27996, 27997, 27998, 27999, 28000, 28001, 28002, 28003, 28004, 28005, 28006, 28007, 28008, 28009, 28010, 28011, 28012, 28013, 28014, 28015, 28016, 28017, 28018, 28019, 28020, 28021, 28022, 28023, 28024, 28025, 28026, 28027, 28028, 28029, 28030, 28031, 28032, 28033, 28034, 28035, 28036, 28037, 28038, 28039, 28040, 28041, 28042, 28043, 28044, 28045, 28046, 28047, 28048, 28049, 28050, 28051, 28052, 28053, 28054, 28055, 28056, 28057, 28058, 28059, 28060, 28061, 28062, 28063, 28064, 28065, 28066, 28067, 28068, 28069, 28070, 28071, 28072, 28073, 28074, 28075, 28076, 28077, 28078, 28079, 28080, 28081, 28082, 28083, 28084, 28085, 28086, 28087, 28088, 28089, 28090, 28091, 28092, 28093, 28094, 28095, 28096, 28097, 28098, 28099, 28100, 28101, 28103, 28104, 28105, 28106, 28107, 28109, 28110, 28111, 28112, 28113, 28115, 28116, 28117, 28118, 28119, 28120, 28121, 28122, 28123, 28124, 28125, 28126, 28127, 28128, 28129, 28130, 28131, 28133, 28134, 28135, 28136, 28137, 28139, 28140, 28141, 28142, 28143, 28145, 28146, 28147, 28148, 28149, 28151, 28152, 28153, 28154, 28155, 28157, 28158, 28159, 28160, 28161, 28163, 28164, 28165, 28166, 28167, 28169, 28170, 28171, 28172, 28173, 28175, 28176, 28177, 28178, 28179, 28181, 28182, 28183, 28184, 28185, 28187, 28188, 28189, 28190, 28191, 28193, 28194, 28195, 28196, 28197, 28199, 28200, 28201, 28202, 28203, 28205, 28206, 28207, 28208, 28209, 28211, 28212, 28213, 28214, 28215, 28217, 28218, 28219, 28220, 28221, 28222, 28223, 28224, 28225, 28226, 28227, 28228, 28229, 28230, 28231, 28232, 28233, 28234, 28235, 28236, 28237, 28238, 28239, 28240, 28241, 28242, 28243, 28244, 28245, 28246, 28247, 28248, 28249, 28250, 28251, 28252, 28253, 28254, 28255, 28256, 28257, 28258, 28259, 28260, 28261, 28262, 28263, 28264, 28265, 28266, 28267, 28268, 28269, 28270, 28271, 28272, 28273, 28274, 28275, 28276, 28277, 28278, 28279, 28280, 28281, 28282, 28283, 28284, 28285, 28286, 28287, 28288, 28289, 28290, 28291, 28292, 28293, 28294, 28295, 28296, 28297, 28298, 28299, 28300, 28301, 28302, 28303, 28304, 28305, 28306, 28307, 28308, 28309, 28310, 28312, 28313, 28314, 28315, 28316, 28318, 28319, 28320, 28321, 28322, 28324, 28325, 28326, 28327, 28328, 28330, 28331, 28332, 28333, 28336, 28337, 28338, 28339, 28342, 28343, 28344, 28345, 28348, 28349, 28350, 28351, 28354, 28355, 28356, 28360, 28361, 28362, 28366, 28367, 28368, 28372, 28373, 28374, 28378, 28379, 28384, 28385, 28390, 28391, 28396, 28402, 28408, 30964, 30970, 30976, 30982, 30983, 30988, 30989, 30994, 30995, 31000, 31001, 31002, 31006, 31007, 31008, 31012, 31013, 31014, 31018, 31019, 31020, 31024, 31025, 31026, 31027, 31030, 31031, 31032, 31033, 31036, 31037, 31038, 31039, 31042, 31043, 31044, 31045, 31048, 31049, 31050, 31051, 31054, 31055, 31056, 31057, 31058, 31060, 31061, 31062, 31063, 31064, 31067, 31068, 31069, 31070, 31073, 31074, 31075, 31076, 31079, 31080, 31081, 31082, 31085, 31086, 31087, 31088, 31089, 31092, 31093, 31094, 31095, 31098, 31099, 31100, 31101, 31104, 31105, 31106, 31107, 31110, 31111, 31112, 31113, 31116, 31117, 31118, 31119, 31123, 31124, 31125, 31129, 31130, 31131, 31135, 31136, 31137, 31141, 31142, 31143, 31147, 31148, 31149, 31153, 31154, 31155, 31160, 31161, 31166, 31167, 31172, 31173, 31178, 31179, 31184, 31185, 31190, 31191, 31196, 31197, 31202, 31203, 31208, 31209, 31214, 31215, 31220, 31221, 31226, 31227, 31232, 31233, 31238, 31239, 31244, 31245, 31250, 31251, 31256, 31257, 31262, 31263, 31268, 31269, 31274, 31275, 31280, 31281, 31286, 31287, 31292, 31297, 31302, 31314, 31318, 31323, 31327, 31346, 31350, 31353, 31362, 31365, 31372, 31374, 31378, 31994, 32000, 32006, 32012, 32013, 32018, 32019, 32024, 32025, 32030, 32031, 32036, 32037, 32038, 32042, 32043, 32044, 32048, 32049, 32050, 32054, 32055, 32056, 32057, 32060, 32061, 32062, 32063, 32066, 32067, 32068, 32069, 32072, 32073, 32074, 32075, 32076, 32078, 32079, 32080, 32081, 32082, 32084, 32085, 32086, 32087, 32088, 32090, 32091, 32092, 32093, 32094, 32096, 32097, 32098, 32099, 32100, 32101, 32102, 32103, 32104, 32105, 32106, 32107, 32108, 32109, 32110, 32111, 32112, 32113, 32114, 32115, 32116, 32117, 32118, 32119, 32120, 32121, 32122, 32123, 32124, 32125, 32126, 32127, 32128, 32129, 32130, 32131, 32132, 32133, 32134, 32135, 32136, 32137, 32138, 32139, 32140, 32141, 32142, 32143, 32144, 32145, 32146, 32147, 32148, 32149, 32150, 32151, 32152, 32153, 32154, 32155, 32156, 32157, 32158, 32159, 32160, 32161, 32162, 32163, 32164, 32165, 32166, 32167, 32168, 32169, 32170, 32171, 32172, 32173, 32174, 32175, 32176, 32177, 32178, 32179, 32181, 32182, 32183, 32184, 32185, 32187, 32188, 32189, 32190, 32191, 32193, 32194, 32195, 32196, 32197, 32199, 32200, 32201, 32202, 32203, 32205, 32206, 32207, 32208, 32209, 32211, 32212, 32213, 32214, 32215, 32218, 32219, 32220, 32221, 32224, 32225, 32226, 32227, 32230, 32231, 32232, 32233, 32237, 32238, 32239, 32243, 32244, 32245, 32249, 32250, 32251, 32255, 32256, 32257, 32261, 32262, 32263, 32268, 32269, 32274, 32275, 32280, 32281, 32286, 32287, 32292, 32293, 32298, 32299, 32304, 32305, 32310, 32311, 32316, 32317, 32322, 32323, 32328, 32329, 32335, 32341, 32347, 32353, 32359, 32365, 32371, 32377, 32383, 32389, 32395, 32401, 32407, 32413, 32419, 32425, 32431, 32437, 32443, 32448, 32449, 32454, 32455, 32460, 32461, 32466, 32467, 32472, 32473, 32478, 32479, 32484, 32485, 32490, 32491, 32496, 32497, 32501, 32502, 32503, 32507, 32508, 32509, 32513, 32514, 32515, 32519, 32520, 32521, 32524, 32525, 32526, 32527, 32530, 32531, 32532, 32533, 32536, 32537, 32538, 32539, 32542, 32543, 32544, 32545, 32548, 32549, 32550, 32551, 32553, 32554, 32555, 32556, 32557, 32559, 32560, 32561, 32562, 32563, 32565, 32566, 32567, 32568, 32569, 32571, 32572, 32573, 32574, 32575, 32576, 32577, 32578, 32579, 32580, 32581, 32582, 32583, 32584, 32585, 32586, 32587, 32588, 32589, 32590, 32591, 32592, 32593, 32594, 32595, 32596, 32597, 32598, 32600, 32601, 32602, 32603, 32604, 32606, 32607, 32608, 32609, 32610, 32612, 32613, 32614, 32615, 32618, 32619, 32620, 32621, 32624, 32625, 32626, 32627, 32630, 32631, 32632, 32636, 32637, 32638, 32642, 32643, 32644, 32648, 32649, 32654, 32655, 32660, 32661, 32666, 32672, 32678, 35260, 35265, 35270, 35274, 35279, 35285, 35286, 35290, 35291, 35295, 35296, 35299, 35300, 35304, 35305, 35309, 35310, 35314, 35315, 35318, 35319, 35323, 35324, 35328, 35329, 35333, 35334, 35335, 35337, 35338, 35339, 35341, 35342, 35343, 35346, 35347, 35348, 35352, 35353, 35354, 35358, 35359, 35360, 35364, 35365, 35366, 35369, 35370, 35371, 35375, 35376, 35377, 35380, 35381, 35382, 35385, 35386, 35387, 35390, 35391, 35392, 35395, 35396, 35397, 35400, 35401, 35402, 35405, 35406, 35407, 35409, 35410, 35413, 35414, 35417, 35418, 35420, 35421, 35423, 35424, 35426, 35429, 35434, 36131, 36137, 36143, 36149, 36150, 36155, 36156, 36161, 36162, 36163, 36167, 36168, 36169, 36173, 36174, 36175, 36179, 36180, 36181, 36182, 36185, 36186, 36187, 36188, 36191, 36192, 36193, 36194, 36197, 36198, 36199, 36200, 36201, 36203, 36204, 36205, 36206, 36207, 36209, 36210, 36211, 36212, 36213, 36215, 36216, 36217, 36218, 36219, 36220, 36221, 36222, 36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36234, 36235, 36236, 36237, 36238, 36239, 36240, 36241, 36242, 36243, 36244, 36245, 36246, 36247, 36248, 36249, 36250, 36251, 36252, 36253, 36254, 36255, 36256, 36257, 36258, 36259, 36260, 36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36272, 36273, 36274, 36275, 36276, 36277, 36278, 36279, 36280, 36282, 36283, 36284, 36285, 36286, 36288, 36289, 36290, 36291, 36292, 36295, 36296, 36297, 36298, 36301, 36302, 36303, 36304, 36307, 36308, 36309, 36310, 36313, 36314, 36315, 36316, 36320, 36321, 36322, 36326, 36327, 36328, 36332, 36333, 36334, 36339, 36340, 36345, 36346, 36351, 36352, 36357, 36358, 36363, 36364, 36370, 36376, 36382, 36388, 36682, 36688, 36694, 36700, 36705, 36706, 36711, 36712, 36717, 36718, 36723, 36724, 36728, 36729, 36730, 36734, 36735, 36736, 36740, 36741, 36742, 36745, 36746, 36747, 36748, 36751, 36752, 36753, 36754, 36757, 36758, 36759, 36760, 36763, 36764, 36765, 36766, 36768, 36769, 36770, 36771, 36772, 36774, 36775, 36776, 36777, 36778, 36780, 36781, 36782, 36783, 36784, 36785, 36786, 36787, 36788, 36789, 36791, 36792, 36793, 36794, 36795, 36797, 36798, 36799, 36800, 36801, 36803, 36804, 36805, 36806, 36809, 36810, 36811, 36812, 36815, 36816, 36817, 36818, 36821, 36822, 36823, 36827, 36828, 36829, 36833, 36834, 36835, 36839, 36840, 36845, 36846, 36851, 36857, 36863, 62495, 62496, 62497, 62498, 62589, 62590, 62591, 62592, 62593, 62594, 62595, 62691, 62692, 62693, 62694, 62695, 62696, 62697, 62698, 62699, 62798, 62799, 62800, 62801, 62802, 62803, 62804, 62805, 62806, 62807, 62908, 62909, 62910, 62911, 62912, 62913, 62914, 62915, 62916, 62917, 62918, 62919, 62920, 63027, 63028, 63029, 63030, 63031, 63032, 63033, 63034, 63035, 63036, 63037, 63038, 63039, 63145, 63147, 63148, 63149, 63150, 63151, 63152, 63153, 63154, 63155, 63156, 63157, 63158, 63159, 63263, 63264, 63265, 63266, 63267, 63268, 63269, 63270, 63271, 63272, 63273, 63274, 63275, 63276, 63396, 63397, 63398, 63399, 63400, 63401, 63402, 63403, 63404, 63405, 63406, 63407, 63408, 63528, 63529, 63530, 63531, 63532, 63533, 63534, 63535, 63536, 63537, 63538, 63539, 63540, 63667, 63668, 63669, 63670, 63671, 63672, 63673, 63674, 63675, 63676, 63677, 63678, 63679, 63810, 63811, 63812, 63813, 63814, 63815, 63816, 63817, 63818, 63819, 63820, 63821, 63953, 63955, 63956, 63957, 63958, 63959, 63960, 63961, 63962, 63963, 63964, 63965, 63966, 64100, 64101, 64102, 64103, 64104, 64105, 64106, 64107, 64108, 64109, 64110, 64246, 64247, 64248, 64249, 64250, 64251, 64252, 64253, 64254, 64255, 64256, 64396, 64397, 64398, 64399, 64400, 64401, 64402, 64403, 64404, 64405, 64406, 64538, 64539, 64540, 64541, 64542, 64543, 64544, 64545, 64546, 64547, 64683, 64684, 64685, 64686, 64687, 64688, 64689, 64690, 64691, 64692, 64693, 64830, 64831, 64832, 64833, 64834, 64835, 64836, 64837, 64838, 64839, 64973, 64974, 64975, 64976, 64977, 64978, 64979, 64980, 64981, 64982, 65116, 65117, 65118, 65119, 65120, 65121, 65122, 65123, 65124, 65125, 65265, 65266, 65267, 65268, 65269, 65270, 65271, 65272, 65273, 65405, 65406, 65407, 65408, 65409, 65410, 65411, 65412, 65413, 65414, 65548, 65549, 65550, 65551, 65552, 65553, 65554, 65555, 65556, 65689, 65690, 65691, 65692, 65693, 65694, 65695, 65696, 65697, 65831, 65832, 65833, 65834, 65835, 65836, 65837, 65838, 65839, 65977, 65978, 65979, 65980, 65981, 65982, 65983, 65984, 65985, 66126, 66127, 66128, 66129, 66130, 66131, 66132, 66133, 66134, 66269, 66270, 66271, 66272, 66273, 66274, 66275, 66276, 66277, 66414, 66415, 66416, 66417, 66418, 66419, 66420, 66421, 66422, 66551, 66552, 66553, 66554, 66555, 66556, 66557, 66558, 66559, 66678, 66679, 66680, 66681, 66682, 66683, 66684, 66685, 66686, 66790, 66791, 66792, 66793, 66794, 66795, 66796, 66797, 66891, 66892, 66893, 66894, 66895, 66896, 66897, 66898, 66899, 66968, 66969, 66970, 66971, 66972, 66973, 66974, 66975, 66976, 67084, 67085, 67086, 67087, 67088, 67089, 67090, 67091, 67092, 67186, 67187, 67188, 67189, 67190, 67191, 67192, 67193, 67194, 67321, 67322, 67323, 67324, 67325, 67326, 67327, 67328, 67329, 67414, 67415, 67416, 67417, 67418, 67419, 67420, 67421, 67422, 67512, 67513, 67514, 67515, 67516, 67517, 67518, 67519, 67520, 67608, 67609, 67610, 67611, 67612, 67613, 67614, 67615, 67616, 67689, 67690, 67691, 67692, 67693, 67694, 67695, 67696, 67697, 67773, 67774, 67775, 67776, 67777, 67778, 67779, 67780, 67781, 67856, 67857, 67858, 67859, 67860, 67861, 67862, 67863, 67864, 67936, 67937, 67938, 67939, 67940, 67941, 67942, 67943, 67944, 67945, 68014, 68015, 68016, 68017, 68018, 68019, 68020, 68021, 68022, 68090, 68092, 68093, 68094, 68095, 68096, 68097, 68098, 68099, 68100, 68163, 68164, 68165, 68166, 68167, 68168, 68169, 68170, 68171, 68172, 68229, 68230, 68231, 68232, 68233, 68234, 68235, 68236, 68237, 68297, 68298, 68299, 68300, 68301, 68302, 68303, 68304, 68305, 68306, 68361, 68362, 68363, 68364, 68365, 68366, 68367, 68368, 68369, 68370, 68422, 68424, 68425, 68426, 68427, 68428, 68429, 68430, 68431, 68432, 68433, 68482, 68483, 68484, 68485, 68486, 68487, 68488, 68489, 68490, 68491, 68553, 68554, 68555, 68556, 68557, 68558, 68559, 68560, 68561, 68562, 68563, 68612, 68613, 68614, 68615, 68616, 68617, 68618, 68619, 68620, 68621, 68622, 68680, 68682, 68683, 68684, 68685, 68686, 68687, 68688, 68689, 68690, 68691, 68692, 68743, 68744, 68745, 68746, 68747, 68748, 68749, 68750, 68751, 68752, 68753, 68754, 68798, 68800, 68801, 68802, 68803, 68804, 68805, 68806, 68807, 68808, 68809, 68810, 68811, 68850, 68851, 68852, 68853, 68854, 68855, 68856, 68857, 68858, 68859, 68860, 68861, 68898, 68899, 68900, 68901, 68902, 68903, 68904, 68905, 68906, 68907, 68908, 68909, 68910, 68945, 68946, 68947, 68948, 68949, 68950, 68951, 68952, 68953, 68954, 68955, 68956, 68993, 68994, 68995, 68996, 68997, 68998, 68999, 69000, 69001, 69002, 69003, 69004, 69045, 69046, 69047, 69048, 69049, 69050, 69051, 69052, 69053, 69054, 69089, 69090, 69091, 69092, 69093, 69094, 69095, 69096, 69097, 69098, 69133, 69134, 69135, 69136, 69137, 69138, 69139, 69140, 69180, 69181, 69182, 69183, 69184, 69185, 69227, 69228, 69229, 69230, 69276, 69866, 69867, 69868, 69958, 69959, 69960, 69961, 69962, 69963, 69964, 70065, 70066, 70067, 70068, 70069, 70070, 70071, 70072, 70073, 70159, 70160, 70161, 70162, 70163, 70164, 70165, 70166, 70167, 70168, 70269, 70270, 70271, 70272, 70273, 70274, 70275, 70276, 70277, 70278, 70279, 70280, 70389, 70390, 70391, 70392, 70393, 70394, 70395, 70396, 70397, 70398, 70399, 70400, 70401, 70503, 70504, 70505, 70506, 70507, 70508, 70509, 70510, 70511, 70512, 70513, 70514, 70515, 70516, 70625, 70626, 70627, 70628, 70629, 70630, 70631, 70632, 70633, 70634, 70635, 70636, 70637, 70638, 70750, 70751, 70752, 70753, 70754, 70755, 70756, 70757, 70758, 70759, 70760, 70761, 70762, 70763, 70883, 70884, 70885, 70886, 70887, 70888, 70889, 70890, 70891, 70892, 70893, 70894, 71013, 71014, 71015, 71016, 71017, 71018, 71019, 71020, 71021, 71022, 71023, 71024, 71025, 71150, 71151, 71152, 71153, 71154, 71155, 71156, 71157, 71158, 71159, 71160, 71161, 71162, 71284, 71285, 71286, 71287, 71288, 71289, 71290, 71291, 71292, 71293, 71294, 71295, 71296, 71424, 71425, 71426, 71427, 71428, 71429, 71430, 71431, 71432, 71433, 71434, 71565, 71566, 71567, 71568, 71569, 71570, 71571, 71572, 71573, 71574, 71575, 71711, 71712, 71713, 71714, 71715, 71716, 71717, 71718, 71719, 71720, 71721, 71859, 71860, 71861, 71862, 71863, 71864, 71865, 71866, 71867, 71868, 71999, 72000, 72001, 72002, 72003, 72004, 72005, 72006, 72007, 72008, 72009, 72150, 72151, 72152, 72153, 72154, 72155, 72156, 72157, 72158, 72159, 72293, 72294, 72295, 72296, 72297, 72298, 72299, 72300, 72301, 72302, 72432, 72433, 72434, 72435, 72436, 72437, 72438, 72439, 72440, 72441, 72575, 72576, 72577, 72578, 72579, 72580, 72581, 72582, 72583, 72716, 72717, 72718, 72719, 72720, 72721, 72722, 72723, 72724, 72725, 72862, 72863, 72864, 72865, 72866, 72867, 72868, 72869, 72870, 72871, 73005, 73006, 73007, 73008, 73009, 73010, 73011, 73012, 73013, 73145, 73146, 73147, 73148, 73149, 73150, 73151, 73152, 73153, 73288, 73289, 73290, 73291, 73292, 73293, 73294, 73295, 73296, 73297, 73432, 73433, 73434, 73435, 73436, 73437, 73438, 73439, 73440, 73576, 73577, 73578, 73579, 73580, 73581, 73582, 73583, 73584, 73714, 73715, 73716, 73717, 73718, 73719, 73720, 73721, 73722, 73850, 73851, 73852, 73853, 73854, 73855, 73856, 73857, 73858, 73978, 73979, 73980, 73981, 73982, 73983, 73984, 73985, 73986, 74094, 74095, 74096, 74097, 74098, 74099, 74100, 74101, 74102, 74207, 74208, 74209, 74210, 74211, 74212, 74213, 74214, 74215, 74286, 74287, 74288, 74289, 74290, 74291, 74292, 74293, 74294, 74408, 74409, 74410, 74411, 74412, 74413, 74414, 74415, 74416, 74533, 74534, 74535, 74536, 74537, 74538, 74539, 74540, 74541, 74662, 74663, 74664, 74665, 74666, 74667, 74668, 74669, 74670, 74755, 74756, 74757, 74758, 74759, 74760, 74761, 74762, 74763, 74846, 74847, 74848, 74849, 74850, 74851, 74852, 74853, 74854, 74940, 74941, 74942, 74943, 74944, 74945, 74946, 74947, 74948, 75030, 75031, 75032, 75033, 75034, 75035, 75036, 75037, 75038, 75111, 75112, 75113, 75114, 75115, 75116, 75117, 75118, 75119, 75193, 75194, 75195, 75196, 75197, 75198, 75199, 75200, 75201, 75276, 75277, 75278, 75279, 75280, 75281, 75282, 75283, 75284, 75285, 75359, 75360, 75361, 75362, 75363, 75364, 75365, 75366, 75367, 75438, 75439, 75440, 75441, 75442, 75443, 75444, 75445, 75446, 75447, 75514, 75515, 75516, 75517, 75518, 75519, 75520, 75521, 75522, 75523, 75582, 75583, 75584, 75585, 75586, 75587, 75588, 75589, 75590, 75591, 75647, 75648, 75649, 75650, 75651, 75652, 75653, 75654, 75655, 75656, 75719, 75720, 75721, 75722, 75723, 75724, 75725, 75726, 75727, 75728, 75781, 75782, 75783, 75784, 75785, 75786, 75787, 75788, 75789, 75790, 75791, 75844, 75845, 75846, 75847, 75848, 75849, 75850, 75851, 75852, 75853, 75854, 75905, 75906, 75907, 75908, 75909, 75910, 75911, 75912, 75913, 75914, 75915, 75975, 75976, 75977, 75978, 75979, 75980, 75981, 75982, 75983, 75984, 75985, 75986, 76035, 76036, 76037, 76038, 76039, 76040, 76041, 76042, 76043, 76044, 76045, 76046, 76097, 76098, 76099, 76100, 76101, 76102, 76103, 76104, 76105, 76106, 76107, 76108, 76109, 76158, 76159, 76160, 76161, 76162, 76163, 76164, 76165, 76166, 76167, 76168, 76169, 76170, 76171, 76213, 76214, 76215, 76216, 76217, 76218, 76219, 76220, 76221, 76222, 76223, 76224, 76266, 76267, 76268, 76269, 76270, 76271, 76272, 76273, 76274, 76275, 76276, 76277, 76278, 76321, 76322, 76323, 76324, 76325, 76326, 76327, 76328, 76329, 76330, 76331, 76332, 76333, 76334, 76378, 76379, 76380, 76381, 76382, 76383, 76384, 76385, 76386, 76387, 76388, 76389, 76390, 76430, 76431, 76432, 76433, 76434, 76435, 76436, 76437, 76438, 76439, 76440, 76488, 76489, 76490, 76491, 76492, 76493, 76494, 76495, 76496, 76539, 76540, 76541, 76542, 76543, 76544, 76545, 76546, 76589, 76590, 76591, 76592, 76593, 76594, 76639, 76640, 79193, 79194, 79195, 79196, 79197, 79198, 79199, 79200, 79307, 79308, 79309, 79310, 79311, 79312, 79313, 79314, 79315, 79429, 79430, 79431, 79432, 79433, 79434, 79847, 79848, 79849, 79850, 79851, 79971, 79973, 79974, 79975, 79976, 79977, 79978, 79979, 79980, 80109, 80110, 80111, 80112, 80113, 80114, 80115, 80116, 80247, 80248, 80249, 80250, 80251, 80252, 80253, 80254, 80396, 80397, 80398, 80399, 80400, 80401, 80402, 80403, 80545, 80546, 80547, 80548, 80549, 80550, 80551, 80552, 80553, 80696, 80698, 80699, 80700, 80701, 80702, 80703, 80704, 80705, 80853, 80854, 80855, 80856, 80857, 80858, 80859, 80860, 80861, 81010, 81011, 81012, 81013, 81014, 81015, 81016, 81017, 81162, 81164, 81165, 81166, 81167, 81168, 81169, 81170, 81171, 81311, 81312, 81313, 81314, 81315, 81316, 81317, 81318, 81464, 81465, 81466, 81467, 81468, 81469, 81470, 81471, 81609, 81610, 81611, 81612, 81613, 81614, 81615, 81616, 81760, 81761, 81762, 81763, 81764, 81765, 81766, 81767, 81899, 81900, 81901, 81902, 81903, 81904, 81905, 81906, 82042, 82043, 82044, 82045, 82046, 82047, 82048, 82049, 82185, 82186, 82187, 82188, 82189, 82190, 82191, 82192, 82329, 82330, 82331, 82332, 82333, 82334, 82335, 82336, 82468, 82469, 82470, 82471, 82472, 82473, 82474, 82607, 82608, 82609, 82610, 82611, 82612, 82613, 82614, 82747, 82748, 82749, 82750, 82751, 82752, 82753, 82870, 82871, 82872, 82873, 82874, 82875, 82999, 83000, 83001, 83002, 83003, 83004, 83126, 83127, 83128, 83129, 83130, 83131, 83256, 83257, 83258, 83259, 83260, 83392, 83393, 83394, 83395, 83396, 83521, 83522, 83523, 83524, 83643, 83644, 83645, 83766, 83767, 87058, 87059, 87060, 87061, 87062, 87063, 87064, 87065, 87169, 87170, 87171, 87172, 87173, 87174, 87175, 87176, 87280, 87281, 87282, 87283, 87284, 87697, 87698, 87699, 87700, 87701, 87702, 87816, 87817, 87818, 87819, 87820, 87821, 87822, 87823, 87824, 87950, 87951, 87952, 87953, 87954, 87955, 87956, 87957, 88093, 88094, 88095, 88096, 88097, 88098, 88099, 88100, 88245, 88246, 88247, 88248, 88249, 88250, 88251, 88252, 88395, 88396, 88397, 88398, 88399, 88400, 88401, 88402, 88546, 88547, 88548, 88549, 88550, 88551, 88552, 88553, 88554, 88707, 88708, 88709, 88710, 88711, 88712, 88713, 88714, 88715, 88863, 88864, 88865, 88866, 88867, 88868, 88869, 88870, 89019, 89020, 89021, 89022, 89023, 89024, 89025, 89026, 89027, 89172, 89173, 89174, 89175, 89176, 89177, 89178, 89179, 89323, 89324, 89325, 89326, 89327, 89328, 89329, 89330, 89331, 89468, 89469, 89470, 89471, 89472, 89473, 89474, 89475, 89613, 89614, 89615, 89616, 89617, 89618, 89619, 89620, 89758, 89759, 89760, 89761, 89762, 89763, 89764, 89765, 89903, 89904, 89905, 89906, 89907, 89908, 89909, 89910, 90045, 90046, 90047, 90048, 90049, 90050, 90051, 90052, 90186, 90187, 90188, 90189, 90190, 90191, 90192, 90334, 90335, 90336, 90337, 90338, 90339, 90340, 90472, 90473, 90474, 90475, 90476, 90477, 90478, 90610, 90611, 90612, 90613, 90614, 90615, 90616, 90739, 90740, 90741, 90742, 90743, 90744, 90871, 90872, 90873, 90874, 90875, 90995, 90996, 90997, 90998, 90999, 91127, 91128, 91129, 91130, 91131, 91132, 91266, 91267, 91268, 91269, 91395, 91396, 91397, 91519, 91520, 91521, 91522, 91646, 91647, 91769, 97012, 97075, 97138, 97201, 97262, 97323, 97385, 97447, 97510, 97573, 97634, 97695, 97758, 97821, 97883, 97945, 98007, 98069, 98132, 98195, 98258, 98321, 98383, 98445, 98508, 98571, 98633, 98695, 98758, 98821, 98880, 98881, 98940, 99002, 99003, 99065, 99066, 99126, 99127, 99187, 99188, 99250, 99251, 99313, 99314, 99375, 99376, 99437, 99438, 99500, 99501, 99563, 99564, 99626, 99627, 99689, 99690, 99752, 99753, 99815, 99816, 99877, 99878, 99939, 99940, 100002, 100003, 100065, 100066, 100126, 100127, 100187, 100188, 100250, 100251, 100313, 100314, 100375, 100376, 100437, 100438, 100500, 100501, 100563, 100564, 100626, 100627, 100689, 100690, 100752, 100753, 100815, 100816, 100878, 100879, 100941, 100942, 101005, 101006, 101069, 101070, 101133, 101134, 101197, 101198, 101261, 101262, 101325, 101326, 101389, 101390, 101453, 101454, 101515, 101516, 101577, 101578, 101639, 101640, 101641, 101702, 101703, 101704, 101766, 101767, 101768, 101830, 101831, 101832, 101894, 101895, 101896, 101958, 101959, 101960, 102022, 102023, 102024, 102086, 102087, 102088, 102150, 102151, 102152, 102214, 102215, 102216, 102277, 102278, 102279, 102340, 102341, 102342, 102404, 102405, 102406, 102468, 102469, 102470, 102531, 102532, 102533, 102594, 102595, 102596, 102657, 102658, 102659, 102720, 102721, 102722, 102781, 102782, 102783, 102842, 102843, 102844, 102906, 102907, 102908, 102970, 102971, 102972, 103034, 103035, 103036, 103098, 103099, 103100, 103162, 103163, 103164, 103226, 103227, 103228, 103290, 103291, 103292, 103354, 103355, 103356, 103417, 103418, 103419, 103480, 103481, 103482, 103544, 103545, 103546, 103608, 103609, 103610, 103671, 103672, 103673, 103734, 103735, 103736, 103798, 103799, 103800, 103862, 103863, 103864, 103926, 103927, 103928, 103990, 103991, 103992, 104054, 104055, 104118, 104119, 104182, 104183, 104246, 104247, 104309, 104310, 104372, 104373, 104435, 104436, 104437, 104498, 104499, 104500, 104562, 104563, 104564, 104626, 104627, 104628, 104688, 104689, 104690, 104750, 104751, 104752, 104813, 104814, 104815, 104876, 104877, 104878, 104938, 104939, 104940, 105000, 105001, 105002, 105062, 105063, 105064, 105124, 105125, 105126, 105185, 105186, 105187, 105188, 105247, 105248, 105249, 105250, 105309, 105310, 105311, 105371, 105372, 105373, 105433, 105434, 105435, 105495, 105496, 105497, 105558, 105559, 105560, 105621, 105622, 105623, 105684, 105685, 105686, 105747, 105748, 105749, 105810, 105811, 105812, 105873, 105874, 105875, 105936, 105937, 105938, 105999, 106000, 106001, 106062, 106063, 106064, 106125, 106126, 106127, 106187, 106188, 106189, 106249, 106250, 106251, 106312, 106313, 106314, 106375, 106376, 106377, 106438, 106439, 106440, 106501, 106502, 106503, 106564, 106565, 106566, 106627, 106628, 106629, 106690, 106691, 106692, 106753, 106754, 106755, 106817, 106818, 106819, 106881, 106882, 106883, 106944, 106945, 106946, 107007, 107008, 107009, 107069, 107070, 107071, 107131, 107132, 107133, 107195, 107196, 107197, 107259, 107260, 107261, 107323, 107324, 107325, 107387, 107388, 107389, 107450, 107451, 107452, 107513, 107514, 107515, 107576, 107577, 107578, 107639, 107640, 107641, 107703, 107704, 107705, 107767, 107768, 107769, 107831, 107832, 107833, 107895, 107896, 107897, 107959, 107960, 107961, 108023, 108024, 108025, 108087, 108088, 108089, 108151, 108152, 108153, 108215, 108216, 108217, 108279, 108280, 108281, 108343, 108344, 108345, 108407, 108408, 108409, 108470, 108471, 108472, 108473, 108534, 108535, 108536, 108537, 108598, 108599, 108600, 108601, 108662, 108663, 108664, 108665, 108726, 108727, 108728, 108729, 108790, 108791, 108792, 108793, 108854, 108855, 108856, 108857, 108918, 108919, 108920, 108921, 108982, 108983, 108984, 108985, 109046, 109047, 109048, 109049, 109109, 109110, 109111, 109112, 109172, 109173, 109174, 109175, 109235, 109236, 109237, 109238, 109298, 109299, 109300, 109301, 109360, 109361, 109362, 109363, 109422, 109423, 109424, 109425, 109485, 109486, 109487, 109488, 109548, 109549, 109550, 109551, 109612, 109613, 109614, 109615, 109676, 109677, 109678, 109679, 109740, 109741, 109742, 109743, 109804, 109805, 109806, 109807, 109867, 109868, 109869, 109870, 109930, 109931, 109932, 109933, 109994, 109995, 109996, 109997, 110058, 110059, 110060, 110061, 110122, 110123, 110124, 110125, 110186, 110187, 110188, 110189, 110250, 110251, 110252, 110253, 110314, 110315, 110316, 110317, 110378, 110379, 110380, 110381, 110442, 110443, 110444, 110445, 110506, 110507, 110508, 110509, 110570, 110571, 110572, 110573, 110634, 110635, 110636, 110637, 110698, 110699, 110700, 110701, 110762, 110763, 110764, 110765, 110826, 110827, 110828, 110829, 110889, 110890, 110891, 110892, 110952, 110953, 110954, 110955, 111016, 111017, 111018, 111019, 111080, 111081, 111082, 111083, 111143, 111144, 111145, 111146, 111206, 111207, 111208, 111209, 111270, 111271, 111272, 111273, 111334, 111335, 111336, 111337, 111397, 111398, 111399, 111400, 111459, 111460, 111461, 111462, 111463, 111523, 111524, 111525, 111526, 111527, 111587, 111588, 111589, 111590, 111591, 111651, 111652, 111653, 111654, 111655, 111715, 111716, 111717, 111718, 111719, 111779, 111780, 111781, 111782, 111783, 111843, 111844, 111845, 111846, 111847, 111906, 111907, 111908, 111909, 111910, 111969, 111970, 111971, 111972, 111973, 112033, 112034, 112035, 112036, 112037, 112097, 112098, 112099, 112100, 112101, 112160, 112161, 112162, 112163, 112164, 112223, 112224, 112225, 112226, 112227, 112287, 112288, 112289, 112290, 112291, 112351, 112352, 112353, 112354, 112355, 112415, 112416, 112417, 112418, 112419, 112479, 112480, 112481, 112482, 112483, 112542, 112543, 112544, 112545, 112546, 112605, 112606, 112607, 112608, 112609, 112669, 112670, 112671, 112672, 112673, 112733, 112734, 112735, 112736, 112737, 112796, 112797, 112798, 112799, 112800, 112859, 112860, 112861, 112862, 112863, 112922, 112923, 112924, 112925, 112926, 112985, 112986, 112987, 112988, 112989, 113048, 113049, 113050, 113051, 113052, 113111, 113112, 113113, 113114, 113115, 113175, 113176, 113177, 113178, 113179, 113239, 113240, 113241, 113242, 113243, 113303, 113304, 113305, 113306, 113307, 113367, 113368, 113369, 113370, 113371, 113430, 113431, 113432, 113433, 113434, 113493, 113494, 113495, 113496, 113497, 113556, 113557, 113558, 113559, 113560, 113619, 113620, 113621, 113622, 113623, 113682, 113683, 113684, 113685, 113686, 113745, 113746, 113747, 113748, 113749, 113807, 113808, 113809, 113810, 113811, 113869, 113870, 113871, 113872, 113873, 113933, 113934, 113935, 113936, 113937, 113997, 113998, 113999, 114000, 114001, 114060, 114061, 114062, 114063, 114064, 114123, 114124, 114125, 114126, 114127, 114187, 114188, 114189, 114190, 114191, 114251, 114252, 114253, 114254, 114255, 114315, 114316, 114317, 114318, 114319, 114378, 114379, 114380, 114381, 114382, 114383, 114441, 114442, 114443, 114444, 114445, 114446, 114504, 114505, 114506, 114507, 114508, 114509, 114566, 114567, 114568, 114569, 114570, 114571, 114628, 114629, 114630, 114631, 114632, 114633, 114692, 114693, 114694, 114695, 114696, 114697, 114756, 114757, 114758, 114759, 114760, 114761, 114820, 114821, 114822, 114823, 114824, 114825, 114884, 114885, 114886, 114887, 114888, 114889, 114948, 114949, 114950, 114951, 114952, 115012, 115013, 115014, 115015, 115016, 115076, 115077, 115078, 115079, 115080, 115140, 115141, 115142, 115143, 115144, 115203, 115204, 115205, 115206, 115207, 115266, 115267, 115268, 115269, 115270, 115330, 115331, 115332, 115333, 115334, 115394, 115395, 115396, 115397, 115398, 115458, 115459, 115460, 115461, 115462, 115522, 115523, 115524, 115525, 115526, 115584, 115585, 115586, 115587, 115588, 115646, 115647, 115648, 115649, 115650, 115710, 115711, 115712, 115713, 115714, 115774, 115775, 115776, 115777, 115778, 115837, 115838, 115839, 115840, 115841, 115900, 115901, 115902, 115903, 115904, 115964, 115965, 115966, 115967, 115968, 116028, 116029, 116030, 116031, 116032, 116092, 116093, 116094, 116095, 116096, 116156, 116157, 116158, 116159, 116160, 116220, 116221, 116222, 116223, 116224, 116284, 116285, 116286, 116287, 116288, 116348, 116349, 116350, 116351, 116352, 116412, 116413, 116414, 116415, 116416, 116476, 116477, 116478, 116479, 116540, 116541, 116542, 116543, 116603, 116604, 116605, 116606, 116666, 116667, 116668, 116669, 116730, 116731, 116732, 116733, 116794, 116795, 116796, 116797, 116856, 116857, 116858, 116859, 116918, 116919, 116920, 116921, 116981, 116982, 116983, 116984, 116985, 117045, 117046, 117047, 117048, 117049, 117109, 117110, 117111, 117112, 117113, 117173, 117174, 117175, 117176, 117177, 117237, 117238, 117239, 117240, 117241, 117301, 117302, 117303, 117304, 117305, 117365, 117366, 117367, 117368, 117369, 117429, 117430, 117431, 117432, 117433, 117492, 117493, 117494, 117495, 117496, 117555, 117556, 117557, 117558, 117559, 117618, 117619, 117620, 117621, 117622, 117681, 117682, 117683, 117684, 117685, 117744, 117745, 117746, 117747, 117807, 117808, 117809, 117810, 117871, 117872, 117873, 117874, 117935, 117936, 117937, 117938, 117999, 118000, 118001, 118002, 118063, 118064, 118065, 118066, 118127, 118128, 118129, 118130, 118191, 118192, 118193, 118194, 118254, 118255, 118256, 118257, 118317, 118318, 118319, 118320, 118381, 118382, 118383, 118384, 118445, 118446, 118447, 118448, 118508, 118509, 118510, 118511, 118571, 118572, 118573, 118574, 118635, 118636, 118637, 118638, 118699, 118700, 118701, 118702, 118762, 118763, 118764, 118765, 118825, 118826, 118827, 118828, 118889, 118890, 118891, 118892, 118953, 118954, 118955, 118956, 119016, 119017, 119018, 119019, 119079, 119080, 119081, 119082, 119142, 119143, 119144, 119145, 119205, 119206, 119207, 119208, 119269, 119270, 119271, 119272, 119333, 119334, 119335, 119336, 119397, 119398, 119399, 119400, 119456, 119460, 119461, 119462, 119463, 119464, 119525, 119526, 119527, 119528, 119584, 119588, 119589, 119590, 119591, 119592, 119652, 119653, 119654, 119655, 119656, 119712, 119716, 119717, 119718, 119719, 119720, 119775, 119779, 119780, 119781, 119782, 119838, 119842, 119843, 119844, 119845, 119902, 119906, 119907, 119908, 119909, 119966, 119970, 119971, 119972, 119973, 120030, 120034, 120035, 120036, 120037, 120094, 120098, 120099, 120100, 120101, 120158, 120162, 120163, 120164, 120165, 120222, 120226, 120227, 120228, 120229, 120285, 120289, 120290, 120291, 120292, 120348, 120352, 120353, 120354, 120355, 120412, 120416, 120417, 120418, 120419, 120476, 120480, 120481, 120482, 120483, 120537, 120541, 120542, 120543, 120544, 120598, 120602, 120603, 120604, 120605, 120662, 120666, 120667, 120668, 120669, 120726, 120730, 120731, 120732, 120733, 120789, 120793, 120794, 120795, 120796, 120852, 120856, 120857, 120858, 120859, 120915, 120919, 120920, 120921, 120922, 120978, 120982, 120983, 120984, 120985, 121042, 121046, 121047, 121048, 121049, 121105, 121106, 121110, 121111, 121112, 121113, 121170, 121174, 121175, 121176, 121177, 121233, 121234, 121238, 121239, 121240, 121241, 121295, 121296, 121300, 121301, 121302, 121303, 121357, 121358, 121362, 121363, 121364, 121365, 121420, 121421, 121425, 121426, 121427, 121483, 121484, 121488, 121489, 121490, 121546, 121547, 121551, 121552, 121553, 121609, 121610, 121614, 121615, 121616, 121673, 121674, 121678, 121679, 121680, 121737, 121738, 121742, 121743, 121744, 121800, 121801, 121805, 121806, 121807, 121858, 121863, 121864, 121868, 121869, 121870, 121926, 121927, 121931, 121932, 121933, 121984, 121989, 121990, 121993, 121994, 121995, 121996, 122053, 122054, 122058, 122059, 122060, 122112, 122117, 122118, 122121, 122122, 122123, 122124, 122176, 122181, 122182, 122185, 122186, 122187, 122188, 122240, 122245, 122246, 122249, 122250, 122251, 122252, 122304, 122309, 122310, 122313, 122314, 122315, 122316, 122368, 122373, 122374, 122377, 122378, 122379, 122380, 122432, 122437, 122438, 122441, 122442, 122443, 122444, 122496, 122501, 122502, 122505, 122506, 122507, 122508, 122559, 122564, 122565, 122568, 122569, 122570, 122571, 122622, 122627, 122628, 122631, 122632, 122633, 122634, 122682, 122687, 122688, 122691, 122692, 122693, 122742, 122746, 122747, 122748, 122751, 122752, 122753, 122806, 122811, 122812, 122815, 122816, 122817, 122870, 122874, 122875, 122876, 122879, 122880, 122881, 122933, 122937, 122938, 122939, 122942, 122943, 122944, 122996, 123000, 123001, 123002, 123005, 123006, 123007, 123059, 123063, 123064, 123065, 123068, 123069, 123070, 123122, 123126, 123127, 123128, 123131, 123132, 123133, 123186, 123190, 123191, 123192, 123195, 123196, 123197, 123250, 123254, 123255, 123256, 123259, 123260, 123261, 123312, 123316, 123317, 123318, 123321, 123322, 123323, 123374, 123378, 123379, 123380, 123383, 123384, 123385, 123436, 123440, 123441, 123442, 123445, 123446, 123447, 123497, 123498, 123502, 123503, 123504, 123507, 123508, 123509, 123561, 123565, 123566, 123567, 123570, 123571, 123572, 123623, 123624, 123628, 123629, 123630, 123632, 123633, 123634, 123635, 123686, 123687, 123691, 123692, 123693, 123696, 123697, 123698, 123749, 123750, 123754, 123755, 123756, 123758, 123759, 123760, 123761, 123810, 123811, 123815, 123816, 123817, 123819, 123820, 123821, 123822, 123871, 123872, 123876, 123877, 123878, 123880, 123881, 123882, 123883, 123933, 123934, 123938, 123939, 123942, 123943, 123944, 123945, 123995, 123996, 124000, 124001, 124004, 124005, 124006, 124007, 124056, 124057, 124061, 124062, 124065, 124066, 124067, 124117, 124118, 124122, 124123, 124126, 124127, 124128, 124178, 124179, 124183, 124184, 124187, 124188, 124189, 124234, 124239, 124240, 124244, 124245, 124248, 124249, 124250, 124302, 124303, 124307, 124308, 124311, 124312, 124313, 124360, 124365, 124366, 124369, 124370, 124371, 124374, 124375, 124376, 124422, 124427, 124428, 124431, 124432, 124435, 124436, 124437, 124483, 124488, 124489, 124491, 124492, 124493, 124496, 124497, 124498, 124545, 124550, 124551, 124555, 124556, 124559, 124560, 124561, 124608, 124613, 124614, 124617, 124618, 124619, 124622, 124623, 124624, 124670, 124675, 124676, 124679, 124680, 124681, 124684, 124685, 124686, 124732, 124737, 124738, 124741, 124742, 124743, 124746, 124747, 124748, 124794, 124799, 124800, 124803, 124804, 124805, 124808, 124809, 124810, 124856, 124861, 124862, 124865, 124866, 124867, 124870, 124871, 124872, 124918, 124923, 124924, 124927, 124928, 124929, 124932, 124933, 124934, 124980, 124985, 124986, 124989, 124990, 124991, 124994, 124995, 124996, 125043, 125048, 125049, 125052, 125053, 125054, 125057, 125058, 125059, 125106, 125110, 125111, 125112, 125115, 125116, 125117, 125120, 125121, 125122, 125169, 125174, 125175, 125178, 125179, 125180, 125183, 125184, 125185, 125232, 125236, 125237, 125238, 125241, 125242, 125243, 125245, 125246, 125247, 125248, 125290, 125295, 125299, 125300, 125301, 125304, 125305, 125306, 125348, 125352, 125353, 125357, 125358, 125359, 125361, 125362, 125363, 125364, 125409, 125413, 125414, 125418, 125419, 125422, 125423, 125424, 125425, 125470, 125474, 125475, 125479, 125480, 125483, 125484, 125485, 125486, 125533, 125537, 125538, 125542, 125543, 125546, 125547, 125548, 125596, 125600, 125601, 125605, 125606, 125609, 125610, 125611, 125659, 125663, 125664, 125668, 125669, 125672, 125673, 125674, 125722, 125726, 125727, 125731, 125732, 125735, 125736, 125737, 125782, 125786, 125787, 125791, 125792, 125795, 125796, 125797, 125841, 125842, 125846, 125847, 125851, 125852, 125855, 125856, 125857, 125901, 125905, 125906, 125910, 125911, 125914, 125915, 125916, 125959, 125960, 125964, 125965, 125968, 125969, 125970, 125973, 125974, 125975, 126021, 126022, 126026, 126027, 126031, 126032, 126035, 126036, 126037, 126083, 126084, 126088, 126089, 126092, 126093, 126094, 126097, 126098, 126099, 126143, 126144, 126148, 126149, 126152, 126153, 126154, 126157, 126158, 126159, 126203, 126204, 126208, 126209, 126212, 126213, 126214, 126217, 126218, 126219, 126264, 126265, 126269, 126270, 126272, 126273, 126274, 126277, 126278, 126279, 126324, 126325, 126329, 126330, 126332, 126333, 126334, 126337, 126338, 126339, 126384, 126385, 126389, 126390, 126393, 126394, 126395, 126398, 126399, 126400, 126445, 126446, 126450, 126451, 126454, 126455, 126456, 126459, 126460, 126461, 126504, 126505, 126509, 126510, 126513, 126514, 126515, 126518, 126519, 126520, 126558, 126563, 126564, 126568, 126569, 126572, 126573, 126574, 126577, 126578, 126579, 126617, 126622, 126623, 126627, 126628, 126631, 126632, 126633, 126636, 126637, 126638, 126676, 126681, 126682, 126685, 126686, 126687, 126690, 126691, 126692, 126695, 126696, 126697, 126736, 126741, 126746, 126747, 126750, 126751, 126752, 126755, 126756, 126757, 126796, 126801, 126805, 126806, 126807, 126810, 126811, 126812, 126815, 126816, 126817, 126858, 126863, 126868, 126872, 126873, 126877, 126878, 126879, 126920, 126925, 126929, 126930, 126934, 126935, 126939, 126940, 126941, 126982, 126987, 126991, 126992, 126996, 126997, 127000, 127001, 127043, 127048, 127052, 127053, 127057, 127058, 127061, 127062, 127101, 127106, 127110, 127111, 127115, 127116, 127120, 127121, 127160, 127165, 127169, 127170, 127174, 127175, 127179, 127180, 127218, 127223, 127227, 127228, 127232, 127233, 127237, 127238, 127276, 127281, 127285, 127286, 127290, 127291, 127295, 127296, 127336, 127340, 127341, 127345, 127346, 127350, 127351, 127390, 127391, 127395, 127396, 127400, 127401, 127405, 127406, 127442, 127447, 127451, 127452, 127456, 127457, 127461, 127462, 127498, 127502, 127503, 127507, 127508, 127511, 127512, 127513, 127517, 127518, 127552, 127556, 127557, 127561, 127562, 127565, 127566, 127570, 127571, 127604, 127608, 127609, 127613, 127614, 127616, 127617, 127618, 127622, 127623, 127657, 127661, 127662, 127665, 127666, 127669, 127670, 127671, 127675, 127676, 127711, 127715, 127716, 127719, 127720, 127723, 127724, 127725, 127729, 127730, 127762, 127765, 127766, 127770, 127771, 127774, 127775, 127776, 127780, 127781, 127813, 127816, 127817, 127821, 127822, 127825, 127826, 127827, 127831, 127832, 127866, 127870, 127871, 127875, 127876, 127879, 127880, 127881, 127885, 127886, 127920, 127924, 127925, 127929, 127930, 127933, 127934, 127935, 127939, 127940, 127979, 127980, 127984, 127985, 127988, 127989, 127990, 127994, 127995, 128034, 128035, 128039, 128040, 128043, 128044, 128045, 128049, 128050, 128082, 128091, 128095, 128096, 128097, 128101, 128102, 128134, 128142, 128143, 128147, 128148, 128149, 128153, 128154, 128188, 128192, 128197, 128201, 128202, 128206, 128207, 128241, 128245, 128249, 128250, 128254, 128255, 128259, 128260, 128292, 128297, 128301, 128302, 128306, 128307, 128312, 128345, 128350, 128354, 128355, 128359, 128360, 128365, 128396, 128399, 128403, 128404, 128408, 128409, 128414, 128445, 128448, 128452, 128453, 128457, 128458, 128463, 128500, 128504, 128505, 128509, 128510, 128515, 128552, 128556, 128557, 128561, 128562, 128567, 128603, 128607, 128608, 128612, 128613, 128618, 128654, 128658, 128659, 128663, 128664, 128669, 128706, 128710, 128711, 128715, 128716, 128721, 128758, 128762, 128763, 128767, 128768, 128773, 128807, 128812, 128816, 128817, 128821, 128822, 128827, 128861, 128865, 128866, 128870, 128871, 128875, 128876, 128881, 128916, 128920, 128921, 128925, 128926, 128930, 128931, 128936, 128971, 128975, 128976, 128980, 128981, 128985, 128986, 128991, 129028, 129029, 129033, 129034, 129038, 129039, 129044, 129081, 129082, 129086, 129087, 129091, 129092, 129097, 129132, 129133, 129137, 129138, 129142, 129143, 129148, 129183, 129184, 129188, 129189, 129193, 129194, 129199, 129234, 129235, 129239, 129240, 129244, 129245, 129250, 129285, 129286, 129290, 129291, 129295, 129296, 129301, 129335, 129340, 129341, 129345, 129346, 129351, 129385, 129390, 129391, 129395, 129396, 129401, 129434, 129439, 129444, 129483, 129488, 129493, 129530, 129534, 129538, 129543, 129580, 129584, 129587, 129588, 129593, 129629, 129634, 129639, 129675, 129679, 129680, 129685, 129725, 129729, 129730, 129735, 129775, 129779, 129780, 129785, 129819, 129824, 129828, 129829, 129834, 129868, 129873, 129877, 129878, 129883, 129922, 129926, 129927, 129932, 129970, 129974, 129975, 129980, 130017, 130021, 130022, 130026, 130064, 130068, 130069, 130073, 130109, 130113, 130114, 130119, 130154, 130155, 130159, 130160, 130165, 130199, 130200, 130204, 130205, 130210, 130244, 130245, 130249, 130250, 130255, 130291, 130292, 130296, 130297, 130302, 130338, 130339, 130343, 130344, 130349, 130382, 130383, 130386, 130387, 130392, 130425, 130426, 130429, 130430, 130435, 130465, 130470, 130506, 130511, 130549, 130554, 130559, 130597, 130602, 130607, 130644, 130648, 130652, 130689, 130693, 130697, 130733, 130738, 130743, 130779, 130784, 130789, 130827, 130831, 130869, 130873, 130908, 130913, 130949, 130954, 130989, 130993, 131030, 131034, 131070, 131073, 131078, 131114, 131117, 131122, 131159, 131164, 131201, 131202, 131207, 131240, 131243, 131244, 131249, 131282, 131285, 131286, 131291, 131330, 131331, 131336, 131374, 131375, 131380, 131417, 131423, 131461, 131467, 131506, 131551, 131595, 131638, 131681, 131725, 131770, 131815, 131854, 131859, 131900, 131905, 131945, 131950, 131990, 131995, 132039, 132083, 132126, 132170, 132215, 132260, 132301, 132306, 132348, 132353, 132398, 132444, 133394, 133448, 184809, 184814, 184820, 184824, 184828, 184829, 184832, 184837, 184838, 184842, 184843, 184848, 184849, 184850, 184854, 184855, 184860, 184861, 184862, 184866, 184867, 184872, 184873, 184874, 184878, 184879, 184880, 184886, 184887, 184888, 184892, 184893, 184894, 184900, 184901, 184902, 184906, 184907, 184908, 184915, 184916, 184917, 184922, 184923, 184924, 184932, 184933, 184934, 184939, 184940, 184941, 184949, 184950, 184951, 184958, 184959, 184960, 184968, 184969, 184970, 184977, 184978, 184979, 184988, 184989, 184990, 184991, 184998, 184999, 185000, 185009, 185010, 185011, 185012, 185020, 185021, 185022, 185032, 185033, 185034, 185035, 185043, 185044, 185045, 185056, 185057, 185058, 185059, 185069, 185070, 185071, 185072, 185085, 185086, 185087, 185088, 185099, 185100, 185101, 185102, 185118, 185119, 185120, 185121, 185135, 185136, 185137, 185138, 185154, 185155, 185156, 185157, 185173, 185174, 185175, 185176, 185194, 185195, 185196, 185213, 185214, 185215, 185233, 185234, 185235, 185252, 185253, 185254, 185272, 185273, 185274, 185292, 185293, 185294, 185312, 185313, 185314, 185332, 185333, 185334, 185352, 185353, 185354, 185372, 185373, 185374, 185393, 185394, 185395, 185413, 185414, 185415, 185436, 185437, 185438, 185456, 185457, 185458, 185480, 185481, 185482, 185502, 185503, 185504, 185529, 185530, 185531, 185553, 185554, 185555, 185581, 185582, 185583, 185607, 185608, 185609, 185635, 185636, 185637, 185638, 185663, 185664, 185665, 185692, 185693, 185694, 185695, 185720, 185721, 185722, 185749, 185750, 185751, 185752, 185778, 185779, 185780, 185807, 185808, 185809, 185810, 185836, 185837, 185838, 185839, 185866, 185867, 185868, 185869, 185895, 185896, 185897, 185898, 185925, 185926, 185927, 185928, 185954, 185955, 185956, 185957, 185986, 185987, 185988, 185989, 186015, 186016, 186017, 186018, 186048, 186049, 186050, 186051, 186078, 186079, 186080, 186081, 186115, 186116, 186117, 186118, 186147, 186148, 186149, 186150, 186185, 186186, 186187, 186188, 186219, 186220, 186221, 186222, 186258, 186259, 186260, 186261, 186296, 186297, 186298, 186299, 186335, 186336, 186337, 186338, 186372, 186373, 186374, 186375, 186413, 186414, 186415, 186416, 186452, 186453, 186454, 186455, 186494, 186495, 186496, 186534, 186535, 186536, 186577, 186578, 186579, 186618, 186619, 186620, 186660, 186661, 186662, 186701, 186702, 186703, 187946, 187947, 187948, 188052, 188053, 188054, 188157, 188158, 188159, 188212, 188213, 188214, 188268, 188269, 188270, 188324, 188325, 188326, 188378, 188379, 188380, 188432, 188433, 188434, 188487, 188488, 188489, 188542, 188543, 188544, 188598, 188599, 188600, 188654, 188655, 188656, 188711, 188712, 188713, 188767, 188768, 188769, 188826, 188827, 188882, 188883, 188943, 188944, 189002, 189003, 189063, 189064, 189122, 189123, 189183, 189184, 189244, 189245, 189305, 189306, 189366, 189367, 189427, 189428, 189488, 189489, 189549, 189550, 189610, 189611, 189671, 189672, 189732, 189733, 189793, 189794, 189854, 189855, 189915, 189916, 189976, 189977, 190036, 190037, 190096, 190097, 190156, 190157, 190216, 190217, 190277, 190278, 190338, 190339, 190397, 190398, 190456, 190457, 190517, 190518, 190578, 190579, 190638, 190639, 190698, 190699, 190758, 190759, 190818, 190819, 190878, 190879, 190938, 190939, 190998, 190999, 191058, 191059, 191118, 191119, 191178, 191179, 191238, 191239, 191298, 191299, 191359, 191360, 191420, 191421, 191481, 191482, 191542, 191543, 191601, 191602, 191660, 191661, 191719, 191720, 191778, 191779, 191838, 191839, 191898, 191899, 191958, 191959, 192018, 192019, 192078, 192079, 192138, 192139, 192196, 192197, 192254, 192255, 192315, 192375, 192435, 192495, 192555, 192615, 192674, 192733, 192793, 192853, 192913, 192973, 195149, 195267, 195386, 195447, 195507, 195567, 195627, 195687, 195748, 195809, 195869, 195929, 195990, 196051, 196112, 196173, 196234, 196295], "numberOfPointsInBox": 7455}, "attributes": {"trackId": "left_first_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "42116b2f-48f4-423e-9219-a8c6961ede1a", "objectId": "03a3625e-9082-41ec-8b44-24e92b7286eb", "className": "3D_track", "geometry": {"associatedPoints": [2699, 2713, 2719, 2740, 2758, 2781, 2786, 2791, 2814, 2819, 2825, 2841, 2847, 2853, 5805, 5810, 5814, 13349, 13374, 13401, 16271, 16277, 16283, 16294, 16300, 16306, 16312, 16333, 16339, 16349, 16355, 16361, 16367, 16398, 16413, 16418, 17590, 17596, 17602, 17608, 17609, 17615, 17621, 17622, 17627, 17628, 17634, 17640, 17641, 17647, 17653, 17654, 17659, 17660, 17666, 17672, 17673, 17679, 17685, 17691, 18423, 18429, 18439, 18444, 18449, 18464, 18475, 18480, 18486, 18490, 18496, 18502, 18512, 18524, 18530, 19052, 19058, 19064, 19070, 19076, 19077, 19082, 19083, 19088, 19089, 19094, 19095, 19100, 19101, 19106, 19107, 19108, 19112, 19113, 19114, 19119, 19120, 19125, 19126, 19131, 19132, 19133, 19137, 19138, 19139, 19144, 19145, 19150, 19151, 19156, 19157, 19162, 19163, 19164, 19170, 19176, 19182, 19188, 19189, 19195, 19201, 19207, 19213, 19219, 19225, 19543, 19549, 19555, 19561, 19566, 19567, 19572, 19573, 19578, 19579, 19584, 19590, 19595, 19596, 19601, 19602, 19607, 19613, 19618, 19619, 19624, 19625, 19630, 19636, 19641, 19642, 19647, 19648, 19653, 19659, 19664, 19665, 19670, 19676, 19682, 22024, 22030, 22043, 22049, 22062, 22101, 22857, 22876, 22886, 22895, 22900, 23323, 23329, 23335, 23341, 23347, 23353, 23354, 23359, 23360, 23365, 23366, 23371, 23372, 23378, 23379, 23384, 23385, 23390, 23391, 23396, 23397, 23402, 23403, 23404, 23409, 23410, 23415, 23416, 23421, 23422, 23427, 23428, 23429, 23434, 23435, 23440, 23441, 23446, 23447, 23452, 23453, 23454, 23459, 23460, 23465, 23466, 23471, 23472, 23477, 23478, 23484, 23490, 23496, 23502, 24000, 24006, 24012, 24018, 24023, 24024, 24029, 24035, 24040, 24041, 24046, 24052, 24057, 24058, 24063, 24069, 24074, 24075, 24080, 24086, 24091, 24092, 24097, 24103, 24109, 26456, 26474, 26475, 26481, 26487, 26488, 26494, 26500, 26506, 26507, 26513, 26519, 26520, 26526, 26532, 26538, 26539, 26545, 26551, 26557, 27628, 27634, 27640, 27641, 27646, 27647, 27652, 27653, 27658, 27659, 27665, 27666, 27671, 27672, 27677, 27678, 27683, 27684, 27689, 27690, 27691, 27696, 27697, 27702, 27703, 27708, 27709, 27715, 27716, 27721, 27722, 27727, 27728, 27733, 27734, 27735, 27740, 27741, 27746, 27747, 27752, 27753, 27759, 27765, 27771, 27777, 28442, 28459, 28476, 28504, 30862, 30868, 30874, 30880, 30881, 30887, 30893, 30899, 30900, 30906, 30912, 30918, 30919, 30925, 30931, 30937, 30938, 30944, 30950, 30956, 30969, 30975, 30981, 30987, 31826, 31832, 31838, 31844, 31845, 31850, 31851, 32671, 32677, 32683, 32688, 32694, 32700, 32705, 32711, 32717, 32722, 32728, 32733, 32734, 32739, 32745, 32750, 32751, 32756, 32762, 32768, 35138, 35144, 35150, 35156, 35162, 35163, 35169, 35175, 35181, 35187, 35188, 35200, 35206, 35212, 35217, 35223, 35228, 35258, 35264, 35994, 36000, 36006, 36012, 36013, 36018, 36019, 36025, 36031, 36032, 36037, 36038, 36044, 36050, 36051, 36056, 36057, 36063, 36069, 36070, 36075, 36076, 36082, 36083, 36088, 36089, 36094, 36095, 36101, 36107, 36856, 36862, 36867, 36868, 36873, 36879, 36884, 36885, 36890, 36896, 36901, 36902, 36907, 36913, 36918, 36919, 36924, 36930, 36935, 36936, 36941, 36947, 36953, 37697, 37703, 37709, 37715, 37721, 37727, 37733, 37734, 37740, 37746, 37752, 37758, 37764, 37769, 37770, 37776, 37777, 37782, 37783, 37788, 37789, 37794, 37795, 37800, 37801, 37806, 37807, 37812, 37813, 37818, 37819, 37824, 37825, 37830, 37831, 37836, 37837, 37842, 37843, 37848, 37849, 37854, 37855, 37856, 37861, 37862, 37867, 37868, 37873, 37874, 37879, 37880, 37885, 37886, 37891, 37892, 37897, 37898, 37902, 37903, 37904, 37909, 37914, 37915, 37920, 37921, 37926, 37927, 37932, 37933, 37938, 37939, 38003, 38004, 38009, 38010, 38015, 38016, 38021, 38022, 38027, 38033, 38039, 38045, 38051, 39377, 39383, 39389, 39395, 39401, 63381, 63382, 63515, 63516, 63653, 63791, 63792, 63937, 63938, 63939, 64083, 64084, 64085, 64228, 64229, 64230, 64231, 64377, 64378, 64379, 64518, 64519, 64520, 64521, 64662, 64664, 64665, 64666, 64808, 64809, 64810, 64811, 64812, 64953, 64954, 64955, 64956, 64957, 65096, 65097, 65098, 65099, 65100, 65245, 65246, 65247, 65248, 65386, 65387, 65388, 65389, 65528, 65529, 65530, 65531, 65669, 65670, 65671, 65672, 65673, 65811, 65812, 65813, 65814, 65815, 65816, 65957, 65958, 65959, 65960, 66106, 66107, 66108, 66109, 66110, 66247, 66249, 66250, 66251, 66252, 66253, 66394, 66395, 66396, 66397, 66398, 66532, 66533, 66534, 66535, 66536, 66659, 66660, 66661, 66662, 66770, 66771, 66772, 66773, 66774, 66872, 66873, 66874, 66875, 66946, 66947, 66948, 66949, 66950, 67062, 67063, 67065, 67067, 67165, 67166, 67167, 67168, 67170, 67301, 67302, 67303, 67304, 67305, 67393, 67395, 67396, 67397, 67398, 67492, 67493, 67494, 67495, 67496, 67588, 67589, 67590, 67591, 67592, 67670, 67671, 67672, 67673, 67674, 67753, 67754, 67755, 67756, 67757, 67835, 67837, 67838, 67839, 67840, 67917, 67918, 67919, 67920, 67921, 67995, 67996, 67997, 67998, 68072, 68073, 68074, 68075, 68145, 68146, 68147, 68148, 68210, 68211, 68212, 68213, 68279, 68280, 68281, 68282, 68344, 68345, 68346, 68405, 68406, 68407, 68408, 68465, 68466, 68467, 68536, 68537, 68538, 68539, 68596, 68597, 68598, 68665, 68666, 68667, 68729, 68730, 68785, 68786, 68787, 68838, 68839, 68888, 68936, 70778, 70779, 70907, 70908, 71038, 71039, 71040, 71177, 71178, 71311, 71312, 71313, 71449, 71450, 71451, 71590, 71591, 71592, 71593, 71737, 71738, 71739, 71740, 71886, 71887, 71888, 71889, 72028, 72029, 72030, 72031, 72177, 72178, 72179, 72180, 72318, 72319, 72320, 72321, 72322, 72457, 72458, 72459, 72460, 72600, 72601, 72602, 72603, 72741, 72742, 72743, 72744, 72745, 72888, 72889, 72890, 72891, 72892, 73030, 73031, 73032, 73033, 73169, 73170, 73171, 73172, 73173, 73313, 73314, 73315, 73316, 73317, 73318, 73457, 73458, 73459, 73460, 73461, 73600, 73601, 73602, 73603, 73604, 73738, 73739, 73740, 73741, 73742, 73874, 73875, 73876, 73877, 73878, 74002, 74003, 74004, 74005, 74006, 74118, 74119, 74120, 74121, 74122, 74231, 74232, 74233, 74234, 74235, 74311, 74312, 74313, 74314, 74315, 74438, 74439, 74440, 74441, 74557, 74558, 74559, 74560, 74561, 74686, 74687, 74688, 74689, 74690, 74779, 74780, 74781, 74782, 74783, 74870, 74871, 74872, 74873, 74874, 74964, 74965, 74966, 74967, 74968, 75053, 75054, 75055, 75056, 75057, 75135, 75136, 75137, 75138, 75217, 75218, 75219, 75220, 75221, 75301, 75302, 75303, 75304, 75383, 75384, 75385, 75386, 75387, 75462, 75463, 75464, 75465, 75539, 75540, 75541, 75542, 75606, 75607, 75608, 75609, 75672, 75673, 75674, 75675, 75743, 75744, 75745, 75746, 75806, 75807, 75808, 75868, 75869, 75870, 75871, 75929, 75930, 75931, 76000, 76001, 76002, 76059, 76060, 76061, 76122, 76123, 76124, 76183, 76184, 76236, 79181, 79182, 79296, 79297, 79298, 79418, 79419, 79420, 79421, 79842, 79961, 79962, 79963, 80095, 80097, 80098, 80099, 80234, 80235, 80236, 80237, 80382, 80384, 80385, 80532, 80533, 80534, 80684, 80685, 80686, 80841, 80842, 80843, 80997, 80998, 80999, 81000, 81151, 81152, 81298, 81300, 81301, 81452, 81453, 81454, 81598, 81599, 81600, 81748, 81749, 81750, 81751, 81888, 81889, 82030, 82032, 82033, 82175, 82176, 82177, 82320, 82321, 82459, 82460, 82598, 82599, 82739, 82740, 82861, 82862, 87076, 87077, 87078, 87187, 87188, 87189, 87292, 87293, 87294, 87588, 87833, 87834, 87835, 87836, 87968, 87969, 87970, 87971, 88110, 88111, 88112, 88113, 88263, 88264, 88265, 88413, 88414, 88415, 88565, 88566, 88567, 88726, 88727, 88728, 88880, 88881, 88882, 89037, 89038, 89039, 89040, 89189, 89190, 89341, 89342, 89343, 89485, 89486, 89487, 89630, 89631, 89632, 89774, 89775, 89919, 89920, 89921, 90061, 90062, 90201, 90348, 90349, 90485, 90486, 90623, 90624, 90751, 90752, 93118, 93142, 93164, 93165, 93188, 93189, 93218, 93219, 93285, 93286, 93322, 93354, 93355, 93389, 93423, 93424, 93459, 93494, 93495, 93532, 93572, 93573, 93616, 93617, 93654, 93655, 93697, 93698, 93731, 93732, 93768, 93769, 93807, 93808, 93848, 93849, 93889, 93890, 93933, 93934, 93979, 93980, 94027, 94028, 94072, 94073, 94120, 94121, 94167, 94168, 94215, 94216, 94267, 94320, 94370, 94421, 94472, 94524, 94576, 94577, 94629, 94630, 94686, 94687, 94741, 94742, 94799, 94800, 94856, 94857, 94916, 94917, 94976, 94977, 95037, 95038, 95098, 95099, 95157, 95158, 95216, 95217, 95277, 95278, 95339, 95340, 95400, 95401, 95461, 95462, 95523, 95585, 95647, 95648, 95709, 95710, 95771, 95772, 95833, 95834, 95895, 95956, 95957, 96017, 96076, 96077, 96137, 96196, 96197, 96259, 96320, 96321, 96383, 96445, 96446, 96509, 96571, 96572, 96635, 96697, 96698, 96760, 96820, 96821, 96882, 96942, 96943, 97005, 97067, 97068, 97131, 97193, 97194, 97255, 97315, 97316, 97377, 97378, 97439, 97440, 97502, 97503, 97565, 97566, 97627, 97687, 97688, 97750, 97751, 97813, 97814, 97875, 97876, 97937, 97938, 97999, 98000, 98061, 98062, 98124, 98125, 98187, 98188, 98250, 98251, 98313, 98314, 98375, 98376, 98437, 98438, 98500, 98501, 98560, 98563, 98564, 98625, 98626, 98684, 98687, 98688, 98750, 98751, 98810, 98813, 98814, 98872, 98873, 98929, 98931, 98932, 98933, 98994, 98995, 99054, 99056, 99057, 99058, 99118, 99119, 99176, 99178, 99179, 99180, 99242, 99243, 99302, 99304, 99305, 99306, 99367, 99368, 99427, 99428, 99429, 99430, 99492, 99493, 99552, 99554, 99555, 99556, 99618, 99619, 99678, 99680, 99681, 99682, 99744, 99745, 99804, 99806, 99807, 99808, 99869, 99870, 99928, 99930, 99931, 99932, 99991, 99994, 99995, 100054, 100056, 100057, 100058, 100115, 100118, 100119, 100176, 100178, 100179, 100180, 100239, 100242, 100243, 100302, 100304, 100305, 100306, 100364, 100367, 100368, 100426, 100428, 100429, 100430, 100489, 100491, 100492, 100493, 100552, 100554, 100555, 100556, 100615, 100617, 100618, 100619, 100678, 100680, 100681, 100682, 100741, 100743, 100744, 100745, 100804, 100806, 100807, 100808, 100868, 100869, 100870, 100871, 100931, 100932, 100933, 100934, 100994, 100996, 100997, 100998, 101057, 101058, 101060, 101061, 101062, 101122, 101124, 101125, 101126, 101185, 101186, 101188, 101189, 101190, 101250, 101252, 101253, 101254, 101313, 101314, 101316, 101317, 101318, 101378, 101380, 101381, 101382, 101441, 101442, 101444, 101445, 101446, 101505, 101507, 101508, 101509, 101566, 101567, 101569, 101570, 101571, 101629, 101631, 101632, 101633, 101691, 101692, 101694, 101695, 101696, 101756, 101758, 101759, 101760, 101819, 101820, 101821, 101822, 101823, 101824, 101884, 101886, 101887, 101888, 101947, 101948, 101949, 101950, 101951, 101952, 102012, 102014, 102015, 102016, 102075, 102076, 102077, 102078, 102079, 102080, 102140, 102142, 102143, 102144, 102203, 102204, 102205, 102206, 102207, 102208, 102267, 102269, 102270, 102271, 102329, 102330, 102331, 102332, 102333, 102334, 102393, 102394, 102396, 102397, 102398, 102457, 102458, 102459, 102460, 102461, 102462, 102520, 102521, 102523, 102524, 102525, 102583, 102584, 102585, 102586, 102587, 102588, 102646, 102647, 102649, 102650, 102651, 102709, 102710, 102711, 102712, 102713, 102714, 102770, 102771, 102773, 102774, 102775, 102831, 102832, 102833, 102834, 102835, 102836, 102895, 102896, 102898, 102899, 102900, 102959, 102960, 102961, 102962, 102963, 102964, 103023, 103024, 103026, 103027, 103028, 103087, 103088, 103089, 103090, 103091, 103092, 103151, 103152, 103153, 103154, 103155, 103156, 103215, 103216, 103217, 103218, 103219, 103220, 103279, 103280, 103281, 103282, 103283, 103284, 103343, 103344, 103345, 103346, 103347, 103348, 103406, 103407, 103408, 103409, 103410, 103411, 103469, 103470, 103471, 103472, 103473, 103474, 103533, 103534, 103535, 103536, 103537, 103597, 103598, 103599, 103600, 103601, 103660, 103661, 103662, 103663, 103664, 103665, 103722, 103723, 103724, 103725, 103726, 103727, 103728, 103787, 103788, 103789, 103790, 103791, 103792, 103850, 103851, 103852, 103853, 103854, 103855, 103856, 103915, 103916, 103917, 103918, 103919, 103920, 103978, 103979, 103980, 103981, 103982, 103983, 103984, 104043, 104044, 104045, 104046, 104047, 104106, 104107, 104108, 104109, 104110, 104111, 104171, 104172, 104173, 104174, 104175, 104234, 104235, 104236, 104237, 104238, 104239, 104298, 104299, 104300, 104301, 104302, 104360, 104361, 104362, 104363, 104364, 104365, 104423, 104424, 104425, 104426, 104427, 104428, 104486, 104487, 104488, 104489, 104490, 104491, 104550, 104551, 104552, 104553, 104554, 104555, 104614, 104615, 104616, 104617, 104618, 104619, 104677, 104678, 104679, 104680, 104681, 104738, 104739, 104740, 104741, 104742, 104743, 104802, 104803, 104804, 104805, 104806, 104864, 104865, 104866, 104867, 104868, 104869, 104927, 104928, 104929, 104930, 104931, 104988, 104989, 104990, 104991, 104992, 104993, 105050, 105051, 105052, 105053, 105054, 105055, 105112, 105113, 105114, 105115, 105116, 105117, 105174, 105175, 105176, 105177, 105178, 105179, 105236, 105237, 105238, 105239, 105240, 105241, 105298, 105299, 105300, 105301, 105302, 105303, 105360, 105361, 105362, 105363, 105364, 105365, 105422, 105423, 105424, 105425, 105426, 105427, 105484, 105485, 105486, 105487, 105488, 105489, 105547, 105548, 105549, 105550, 105551, 105552, 105610, 105611, 105612, 105613, 105614, 105615, 105673, 105674, 105675, 105676, 105677, 105678, 105736, 105737, 105738, 105739, 105740, 105741, 105799, 105800, 105801, 105802, 105803, 105804, 105862, 105863, 105864, 105865, 105866, 105867, 105925, 105926, 105927, 105928, 105929, 105930, 105988, 105989, 105990, 105991, 105992, 105993, 106051, 106052, 106053, 106054, 106055, 106056, 106114, 106115, 106116, 106117, 106118, 106119, 106176, 106177, 106178, 106179, 106180, 106181, 106237, 106238, 106239, 106240, 106241, 106242, 106243, 106301, 106302, 106303, 106304, 106305, 106306, 106364, 106365, 106366, 106367, 106368, 106369, 106427, 106428, 106429, 106430, 106431, 106432, 106489, 106490, 106491, 106492, 106493, 106494, 106495, 106553, 106554, 106555, 106556, 106557, 106558, 106615, 106616, 106617, 106618, 106619, 106620, 106621, 106679, 106680, 106681, 106682, 106683, 106684, 106741, 106742, 106743, 106744, 106745, 106746, 106747, 106806, 106807, 106808, 106809, 106810, 106811, 106869, 106870, 106871, 106872, 106873, 106874, 106875, 106933, 106934, 106935, 106936, 106937, 106938, 106992, 106995, 106996, 106997, 106998, 106999, 107000, 107001, 107058, 107059, 107060, 107061, 107062, 107063, 107116, 107119, 107120, 107121, 107122, 107123, 107124, 107125, 107184, 107185, 107186, 107187, 107188, 107189, 107244, 107247, 107248, 107249, 107250, 107251, 107252, 107253, 107312, 107313, 107314, 107315, 107316, 107317, 107372, 107375, 107376, 107377, 107378, 107379, 107380, 107381, 107438, 107439, 107440, 107441, 107442, 107443, 107444, 107498, 107501, 107502, 107503, 107504, 107505, 107506, 107507, 107564, 107565, 107566, 107567, 107568, 107569, 107570, 107624, 107627, 107628, 107629, 107630, 107631, 107632, 107633, 107691, 107692, 107693, 107694, 107695, 107696, 107697, 107752, 107755, 107756, 107757, 107758, 107759, 107760, 107761, 107819, 107820, 107821, 107822, 107823, 107824, 107825, 107880, 107883, 107884, 107885, 107886, 107887, 107888, 107889, 107947, 107948, 107949, 107950, 107951, 107952, 107953, 108008, 108011, 108012, 108013, 108014, 108015, 108016, 108017, 108072, 108075, 108076, 108077, 108078, 108079, 108080, 108081, 108136, 108139, 108140, 108141, 108142, 108143, 108144, 108145, 108200, 108203, 108204, 108205, 108206, 108207, 108208, 108209, 108264, 108267, 108268, 108269, 108270, 108271, 108272, 108273, 108328, 108331, 108332, 108333, 108334, 108335, 108336, 108337, 108392, 108395, 108396, 108397, 108398, 108399, 108400, 108401, 108456, 108459, 108460, 108461, 108462, 108463, 108464, 108465, 108520, 108523, 108524, 108525, 108526, 108527, 108528, 108529, 108584, 108587, 108588, 108589, 108590, 108591, 108592, 108593, 108648, 108650, 108651, 108652, 108653, 108654, 108655, 108656, 108657, 108712, 108715, 108716, 108717, 108719, 108720, 108721, 108776, 108778, 108779, 108780, 108781, 108783, 108784, 108785, 108840, 108843, 108844, 108845, 108846, 108847, 108848, 108849, 108904, 108906, 108907, 108908, 108909, 108910, 108911, 108912, 108913, 108968, 108971, 108972, 108973, 108974, 108975, 108976, 108977, 109032, 109034, 109035, 109036, 109037, 109038, 109039, 109040, 109041, 109095, 109098, 109099, 109100, 109101, 109102, 109103, 109104, 109158, 109160, 109161, 109162, 109163, 109164, 109165, 109166, 109167, 109221, 109224, 109225, 109226, 109228, 109229, 109230, 109283, 109284, 109286, 109287, 109288, 109289, 109291, 109292, 109293, 109346, 109349, 109350, 109351, 109353, 109354, 109355, 109408, 109410, 109411, 109412, 109413, 109415, 109416, 109417, 109471, 109474, 109475, 109476, 109478, 109479, 109480, 109533, 109534, 109536, 109537, 109538, 109539, 109541, 109542, 109543, 109598, 109601, 109602, 109603, 109605, 109606, 109607, 109661, 109662, 109664, 109665, 109666, 109667, 109669, 109670, 109671, 109726, 109728, 109729, 109730, 109731, 109733, 109734, 109735, 109789, 109790, 109792, 109793, 109794, 109795, 109797, 109798, 109799, 109853, 109855, 109856, 109857, 109858, 109860, 109861, 109915, 109916, 109918, 109919, 109920, 109921, 109923, 109924, 109980, 109982, 109983, 109984, 109985, 109987, 109988, 110043, 110044, 110046, 110047, 110048, 110049, 110051, 110052, 110108, 110110, 110111, 110112, 110113, 110115, 110116, 110171, 110172, 110174, 110175, 110176, 110177, 110179, 110180, 110236, 110238, 110239, 110240, 110241, 110243, 110244, 110299, 110300, 110302, 110303, 110304, 110305, 110307, 110308, 110363, 110364, 110366, 110367, 110368, 110369, 110371, 110372, 110427, 110428, 110430, 110431, 110432, 110433, 110435, 110436, 110491, 110492, 110494, 110495, 110496, 110497, 110499, 110500, 110555, 110556, 110558, 110559, 110560, 110561, 110563, 110564, 110619, 110620, 110622, 110623, 110624, 110625, 110627, 110628, 110683, 110684, 110686, 110687, 110688, 110689, 110691, 110692, 110747, 110748, 110750, 110751, 110752, 110755, 110756, 110811, 110812, 110814, 110815, 110816, 110819, 110820, 110874, 110875, 110877, 110878, 110879, 110882, 110883, 110937, 110938, 110940, 110941, 110942, 110945, 110946, 111001, 111002, 111004, 111005, 111006, 111007, 111009, 111010, 111065, 111066, 111068, 111069, 111070, 111071, 111073, 111074, 111128, 111129, 111131, 111132, 111133, 111134, 111136, 111137, 111191, 111192, 111194, 111195, 111196, 111197, 111199, 111200, 111255, 111256, 111258, 111259, 111260, 111261, 111263, 111264, 111319, 111320, 111322, 111323, 111324, 111325, 111327, 111328, 111382, 111383, 111385, 111386, 111387, 111390, 111445, 111446, 111447, 111448, 111449, 111450, 111453, 111509, 111510, 111512, 111513, 111514, 111517, 111518, 111573, 111574, 111575, 111576, 111577, 111578, 111581, 111582, 111637, 111638, 111640, 111641, 111642, 111645, 111646, 111701, 111702, 111703, 111704, 111705, 111706, 111709, 111710, 111765, 111766, 111768, 111769, 111770, 111773, 111774, 111829, 111830, 111831, 111832, 111833, 111834, 111837, 111838, 111892, 111893, 111895, 111896, 111897, 111900, 111901, 111954, 111955, 111956, 111957, 111958, 111959, 111960, 111963, 111964, 112019, 112020, 112022, 112023, 112024, 112027, 112082, 112083, 112084, 112085, 112086, 112087, 112088, 112091, 112146, 112147, 112148, 112149, 112150, 112151, 112154, 112208, 112209, 112210, 112211, 112212, 112213, 112214, 112217, 112273, 112274, 112275, 112276, 112277, 112278, 112281, 112336, 112337, 112338, 112339, 112340, 112341, 112342, 112345, 112401, 112402, 112403, 112404, 112405, 112406, 112409, 112464, 112465, 112466, 112467, 112468, 112469, 112470, 112473, 112528, 112529, 112530, 112531, 112532, 112533, 112536, 112590, 112591, 112592, 112593, 112594, 112595, 112596, 112599, 112655, 112656, 112657, 112658, 112659, 112660, 112663, 112718, 112719, 112720, 112721, 112722, 112723, 112724, 112727, 112781, 112782, 112783, 112784, 112785, 112786, 112787, 112790, 112844, 112845, 112846, 112847, 112848, 112849, 112850, 112853, 112907, 112908, 112909, 112910, 112911, 112912, 112916, 112970, 112971, 112972, 112973, 112974, 112975, 112979, 113033, 113034, 113035, 113036, 113037, 113038, 113039, 113042, 113096, 113097, 113098, 113099, 113100, 113101, 113102, 113105, 113160, 113161, 113162, 113163, 113164, 113165, 113166, 113169, 113224, 113225, 113226, 113227, 113228, 113229, 113230, 113233, 113288, 113289, 113290, 113291, 113292, 113293, 113297, 113352, 113353, 113354, 113355, 113356, 113357, 113361, 113415, 113416, 113417, 113418, 113419, 113420, 113424, 113478, 113479, 113480, 113481, 113482, 113483, 113487, 113541, 113542, 113543, 113544, 113545, 113546, 113604, 113605, 113606, 113607, 113608, 113609, 113667, 113668, 113669, 113670, 113671, 113672, 113730, 113731, 113732, 113733, 113734, 113735, 113792, 113793, 113794, 113795, 113796, 113797, 113801, 113854, 113855, 113856, 113857, 113858, 113859, 113863, 113918, 113919, 113920, 113921, 113922, 113923, 113927, 113982, 113983, 113984, 113985, 113986, 113987, 113991, 114045, 114046, 114047, 114048, 114049, 114050, 114054, 114108, 114109, 114110, 114111, 114112, 114113, 114117, 114172, 114173, 114174, 114175, 114176, 114177, 114181, 114236, 114237, 114238, 114239, 114240, 114241, 114245, 114300, 114301, 114303, 114304, 114305, 114360, 114363, 114364, 114365, 114367, 114368, 114369, 114428, 114429, 114431, 114432, 114433, 114487, 114490, 114491, 114492, 114494, 114495, 114496, 114552, 114553, 114555, 114556, 114557, 114610, 114613, 114614, 114615, 114617, 114618, 114619, 114678, 114679, 114681, 114682, 114683, 114738, 114741, 114742, 114743, 114745, 114746, 114747, 114806, 114807, 114809, 114810, 114811, 114866, 114869, 114870, 114871, 114873, 114874, 114875, 114934, 114935, 114937, 114938, 114939, 114994, 114997, 114998, 114999, 115001, 115002, 115003, 115058, 115061, 115062, 115063, 115065, 115066, 115067, 115122, 115125, 115126, 115127, 115129, 115130, 115131, 115185, 115188, 115189, 115190, 115192, 115193, 115248, 115251, 115252, 115253, 115255, 115256, 115312, 115315, 115316, 115317, 115319, 115320, 115321, 115376, 115379, 115380, 115381, 115383, 115384, 115385, 115440, 115443, 115444, 115445, 115447, 115448, 115504, 115507, 115508, 115509, 115511, 115512, 115566, 115569, 115570, 115571, 115573, 115574, 115628, 115631, 115632, 115633, 115635, 115636, 115692, 115695, 115696, 115697, 115699, 115700, 115756, 115759, 115760, 115761, 115763, 115764, 115819, 115822, 115823, 115824, 115826, 115827, 115882, 115885, 115886, 115887, 115889, 115890, 115946, 115949, 115950, 115951, 115953, 115954, 116010, 116013, 116014, 116015, 116017, 116018, 116074, 116077, 116078, 116081, 116082, 116138, 116141, 116142, 116145, 116146, 116202, 116205, 116206, 116207, 116209, 116210, 116266, 116269, 116270, 116271, 116273, 116274, 116330, 116333, 116334, 116335, 116337, 116338, 116394, 116397, 116398, 116399, 116401, 116402, 116458, 116461, 116462, 116465, 116466, 116521, 116522, 116524, 116525, 116526, 116529, 116530, 116585, 116588, 116589, 116592, 116593, 116647, 116648, 116650, 116651, 116652, 116655, 116656, 116712, 116715, 116716, 116719, 116720, 116775, 116776, 116778, 116779, 116780, 116783, 116784, 116838, 116841, 116842, 116845, 116846, 116899, 116900, 116902, 116903, 116904, 116907, 116908, 116964, 116967, 116968, 116971, 116972, 117027, 117028, 117030, 117031, 117032, 117035, 117036, 117092, 117094, 117095, 117096, 117099, 117100, 117155, 117156, 117158, 117159, 117160, 117163, 117164, 117220, 117222, 117223, 117224, 117227, 117228, 117283, 117284, 117286, 117287, 117288, 117291, 117292, 117348, 117350, 117351, 117352, 117355, 117411, 117412, 117414, 117415, 117416, 117419, 117474, 117475, 117477, 117478, 117479, 117482, 117537, 117538, 117540, 117541, 117542, 117545, 117600, 117601, 117603, 117604, 117605, 117608, 117663, 117664, 117666, 117667, 117668, 117671, 117726, 117727, 117729, 117730, 117731, 117734, 117789, 117790, 117792, 117793, 117794, 117797, 117853, 117854, 117856, 117857, 117858, 117861, 117917, 117918, 117920, 117921, 117922, 117925, 117981, 117982, 117984, 117985, 117986, 117989, 118045, 118046, 118048, 118049, 118050, 118053, 118109, 118110, 118112, 118113, 118114, 118117, 118173, 118174, 118176, 118177, 118178, 118181, 118237, 118238, 118239, 118240, 118244, 118300, 118301, 118302, 118303, 118307, 118363, 118364, 118366, 118367, 118371, 118427, 118428, 118430, 118431, 118435, 118490, 118491, 118493, 118494, 118498, 118553, 118554, 118556, 118557, 118561, 118617, 118618, 118620, 118621, 118625, 118681, 118682, 118683, 118684, 118685, 118689, 118744, 118745, 118747, 118748, 118752, 118807, 118808, 118809, 118810, 118811, 118815, 118871, 118874, 118875, 118879, 118934, 118935, 118937, 118938, 118939, 118943, 118998, 119001, 119002, 119006, 119060, 119061, 119063, 119064, 119065, 119069, 119125, 119127, 119128, 119132, 119187, 119188, 119189, 119190, 119191, 119195, 119251, 119254, 119255, 119314, 119315, 119317, 119318, 119319, 119379, 119382, 119383, 119442, 119443, 119445, 119446, 119447, 119506, 119507, 119509, 119510, 119511, 119570, 119571, 119573, 119574, 119575, 119634, 119635, 119637, 119638, 119639, 119698, 119699, 119701, 119702, 119703, 119761, 119762, 119764, 119765, 119766, 119824, 119825, 119827, 119828, 119829, 119888, 119889, 119891, 119892, 119893, 119952, 119953, 119955, 119956, 119957, 120016, 120017, 120019, 120020, 120080, 120081, 120083, 120084, 120144, 120145, 120147, 120148, 120149, 120208, 120209, 120211, 120212, 120213, 120271, 120272, 120274, 120275, 120334, 120335, 120337, 120338, 120398, 120399, 120401, 120402, 120462, 120463, 120465, 120466, 120523, 120524, 120526, 120527, 120584, 120585, 120587, 120588, 120648, 120649, 120651, 120652, 120708, 120712, 120713, 120715, 120716, 120771, 120775, 120776, 120778, 120779, 120834, 120838, 120839, 120841, 120842, 120901, 120904, 120905, 120960, 120964, 120967, 120968, 121028, 121031, 121032, 121088, 121091, 121092, 121095, 121096, 121156, 121159, 121160, 121216, 121219, 121220, 121223, 121224, 121282, 121285, 121286, 121340, 121343, 121344, 121347, 121348, 121403, 121407, 121410, 121411, 121466, 121469, 121470, 121473, 121474, 121529, 121533, 121536, 121537, 121592, 121595, 121596, 121599, 121600, 121656, 121660, 121663, 121664, 121720, 121723, 121724, 121727, 121728, 121783, 121786, 121787, 121790, 121791, 121846, 121849, 121850, 121853, 121854, 121909, 121912, 121913, 121916, 121917, 121972, 121975, 121976, 121979, 121980, 122036, 122039, 122040, 122043, 122100, 122103, 122104, 122107, 122164, 122167, 122168, 122171, 122228, 122231, 122232, 122235, 122292, 122295, 122296, 122299, 122359, 122360, 122363, 122423, 122424, 122427, 122487, 122488, 122491, 122550, 122551, 122554, 122613, 122614, 122617, 122673, 122674, 122677, 122733, 122734, 122737, 122797, 122801, 122861, 122865, 122924, 122928, 122983, 122987, 122991, 123047, 123050, 123054, 123109, 123110, 123113, 123117, 123174, 123177, 123181, 123237, 123238, 123240, 123241, 123245, 123300, 123303, 123307, 123361, 123362, 123364, 123365, 123369, 123423, 123427, 123431, 123485, 123488, 123489, 123493, 123548, 123552, 123556, 123611, 123614, 123615, 123619, 123674, 123678, 123682, 123740, 123741, 123745, 123801, 123802, 123862, 123863, 123924, 123925, 123929, 123986, 123987, 123991, 124047, 124048, 124108, 124109, 124169, 124230, 124293, 124356, 124416, 124418, 124477, 124479, 124538, 124541, 124601, 124604, 124663, 124666, 124725, 124728, 124787, 124790, 124791, 124849, 124852, 124911, 124914, 124973, 124976, 125036, 125039, 125099, 125102, 125162, 125165, 125225, 125227, 125228, 125286, 125343, 125344, 125405, 125465, 125466, 125591, 125651, 125714, 125717, 125774, 125777, 125778, 125834, 125837, 125893, 125896, 125897, 125952, 125955, 125956, 126014, 126017, 126018, 126076, 126079, 126136, 126139, 126140, 126196, 126199, 126257, 126260, 126261, 126317, 126320, 126377, 126380, 126381, 126438, 126441, 126497, 126500, 126556, 126559, 126615, 126618, 126674, 126677, 126734, 126737, 126794, 126856, 126859, 126980, 126983, 127102, 127157, 127161, 127219, 127273, 127277, 127329, 127332, 127384, 127387, 127443, 127495, 127499, 127553, 127602, 127605, 127654, 127658, 127708, 127712, 127759, 127763, 127810, 127814, 127864, 127867, 127918, 127921, 127973, 127976, 128031, 128080, 128186, 128290, 128343, 128394, 128443, 128494, 128546, 128597, 128648, 128968, 129075, 129125, 129379, 129429, 129478, 129528, 129578, 129624, 129670, 129719, 129769, 129817, 129866, 129916, 129964, 130011, 130058, 130595, 130687, 130777, 130821, 130863, 130903, 130944, 130984, 131025, 131068, 131155, 131238, 131280, 131326, 184772, 184775, 184777, 184779, 184781, 184783, 184784, 184786, 184788, 184789, 184790, 184791, 184792, 184793, 184794, 184795, 184796, 184797, 184798, 184799, 184800, 184801, 184802, 184803, 184804, 184805, 184806, 184807, 184808, 184810, 184811, 184812, 184813, 184815, 184816, 184818, 184819, 184821, 184822, 184823, 184825, 184826, 184827, 184830, 184831, 184833, 184835, 184836, 184840, 184844, 184846, 184847, 184852, 184856, 184858, 184864, 184868, 184870, 184876, 184883, 184884, 184890, 184897, 184898, 184904, 184911, 184912, 184913, 184919, 184927, 184928, 184936, 184944, 184945, 184946, 184953, 184954, 184963, 184964, 184965, 184972, 184973, 184974, 184982, 184983, 184984, 184993, 184995, 185003, 185005, 185014, 185016, 185026, 185028, 185037, 185039, 185050, 185052, 185063, 185065, 185078, 185080, 185093, 185095, 185111, 185112, 185113, 185129, 185130, 185131, 185147, 185148, 185149, 185166, 185168, 185185, 185186, 185187, 185188, 185205, 185207, 185224, 185225, 185226, 185227, 185244, 185246, 185263, 185264, 185265, 185266, 185283, 185284, 185285, 185286, 185303, 185304, 185305, 185306, 185323, 185325, 185326, 185343, 185345, 185346, 185363, 185365, 185366, 185384, 185386, 185387, 185404, 185406, 185407, 185427, 185429, 185430, 185447, 185449, 185450, 185471, 185473, 185474, 185493, 185495, 185496, 185519, 185520, 185521, 185522, 185544, 185545, 185546, 185547, 185571, 185572, 185573, 185574, 185597, 185598, 185599, 185600, 185625, 185626, 185627, 185628, 185653, 185654, 185655, 185656, 185681, 185682, 185683, 185684, 185685, 185710, 185711, 185712, 185713, 185738, 185739, 185740, 185741, 185742, 185767, 185768, 185769, 185770, 185771, 185796, 185797, 185798, 185799, 185800, 185825, 185826, 185827, 185828, 185829, 185855, 185856, 185857, 185858, 185859, 185884, 185886, 185887, 185888, 185914, 185915, 185916, 185917, 185918, 185943, 185945, 185946, 185947, 185975, 185977, 185978, 185979, 186004, 186006, 186007, 186008, 186037, 186039, 186040, 186041, 186067, 186069, 186070, 186071, 186103, 186104, 186105, 186106, 186107, 186136, 186138, 186139, 186140, 186173, 186174, 186175, 186176, 186177, 186208, 186210, 186211, 186212, 186246, 186247, 186248, 186249, 186250, 186284, 186286, 186287, 186288, 186322, 186323, 186324, 186325, 186326, 186327, 186360, 186361, 186362, 186363, 186364, 186400, 186401, 186402, 186403, 186404, 186405, 186440, 186441, 186442, 186443, 186444, 186480, 186481, 186482, 186483, 186484, 186485, 186488, 186520, 186521, 186522, 186523, 186524, 186525, 186563, 186564, 186565, 186566, 186567, 186568, 186571, 186604, 186605, 186606, 186607, 186608, 186609, 186612, 186646, 186647, 186648, 186649, 186650, 186651, 186687, 186688, 186689, 186690, 186691, 186692, 186732, 186733, 186734, 186735, 186736, 186737, 186740, 186769, 186771, 186772, 186773, 186774, 186777, 186808, 186810, 186811, 186812, 186813, 186816, 186844, 186846, 186847, 186848, 186849, 186852, 186886, 186888, 186889, 186890, 186891, 186894, 186926, 186928, 186929, 186930, 186931, 186934, 186969, 186971, 186972, 186973, 186974, 186977, 187012, 187014, 187015, 187016, 187017, 187020, 187056, 187058, 187059, 187060, 187061, 187064, 187099, 187101, 187102, 187103, 187104, 187107, 187142, 187144, 187145, 187146, 187147, 187149, 187150, 187185, 187187, 187188, 187189, 187190, 187193, 187228, 187229, 187230, 187231, 187232, 187233, 187234, 187235, 187236, 187271, 187273, 187274, 187275, 187276, 187277, 187279, 187313, 187314, 187315, 187316, 187317, 187318, 187319, 187320, 187321, 187355, 187356, 187357, 187358, 187359, 187360, 187361, 187363, 187397, 187398, 187399, 187400, 187401, 187402, 187404, 187405, 187439, 187440, 187441, 187442, 187443, 187444, 187446, 187447, 187482, 187483, 187484, 187485, 187486, 187487, 187488, 187489, 187490, 187525, 187526, 187527, 187528, 187529, 187530, 187532, 187533, 187568, 187569, 187570, 187571, 187572, 187573, 187574, 187575, 187576, 187612, 187613, 187614, 187615, 187616, 187618, 187619, 187657, 187658, 187659, 187660, 187661, 187663, 187664, 187700, 187701, 187702, 187703, 187704, 187706, 187707, 187746, 187747, 187748, 187749, 187750, 187751, 187752, 187753, 187790, 187791, 187792, 187793, 187794, 187796, 187797, 187837, 187838, 187839, 187840, 187841, 187842, 187844, 187845, 187884, 187885, 187886, 187887, 187888, 187890, 187891, 187933, 187934, 187935, 187936, 187937, 187939, 187940, 187989, 187990, 187991, 187992, 187993, 187995, 187996, 188039, 188040, 188041, 188042, 188043, 188045, 188046, 188095, 188096, 188097, 188098, 188099, 188101, 188102, 188144, 188145, 188146, 188147, 188148, 188149, 188150, 188151, 188199, 188200, 188201, 188202, 188203, 188205, 188206, 188255, 188256, 188257, 188258, 188259, 188260, 188261, 188262, 188311, 188312, 188313, 188314, 188315, 188317, 188318, 188364, 188365, 188366, 188367, 188368, 188369, 188370, 188371, 188372, 188418, 188419, 188420, 188421, 188422, 188423, 188424, 188425, 188426, 188473, 188474, 188475, 188476, 188477, 188478, 188479, 188480, 188481, 188528, 188529, 188530, 188531, 188532, 188533, 188534, 188535, 188536, 188584, 188585, 188586, 188587, 188588, 188590, 188591, 188592, 188640, 188641, 188642, 188643, 188644, 188645, 188646, 188647, 188648, 188697, 188698, 188699, 188700, 188703, 188704, 188705, 188753, 188754, 188755, 188756, 188757, 188758, 188759, 188760, 188761, 188811, 188812, 188813, 188817, 188818, 188819, 188867, 188868, 188869, 188870, 188871, 188872, 188873, 188874, 188875, 188928, 188929, 188934, 188935, 188936, 188987, 188988, 188989, 188990, 188991, 188992, 188993, 188994, 188995, 189048, 189049, 189054, 189055, 189056, 189107, 189108, 189109, 189110, 189111, 189113, 189114, 189115, 189168, 189170, 189174, 189175, 189176, 189229, 189230, 189231, 189232, 189233, 189235, 189236, 189237, 189290, 189292, 189296, 189297, 189298, 189351, 189352, 189353, 189354, 189355, 189357, 189358, 189359, 189412, 189418, 189419, 189420, 189473, 189474, 189475, 189476, 189477, 189478, 189479, 189480, 189481, 189540, 189541, 189542, 189595, 189596, 189597, 189598, 189599, 189600, 189601, 189602, 189603, 189662, 189663, 189664, 189717, 189718, 189719, 189721, 189722, 189723, 189724, 189725, 189784, 189785, 189786, 189839, 189840, 189841, 189842, 189843, 189844, 189845, 189846, 189847, 189906, 189907, 189908, 189961, 189962, 189963, 189964, 189965, 189966, 189967, 189968, 189969, 190027, 190028, 190029, 190081, 190082, 190087, 190088, 190089, 190147, 190148, 190149, 190201, 190202, 190207, 190208, 190209, 190268, 190269, 190270, 190323, 190329, 190330, 190331, 190388, 190389, 190390, 190441, 190447, 190448, 190449, 190508, 190509, 190510, 190569, 190570, 190571, 190629, 190630, 190631, 190689, 190690, 190691, 190749, 190750, 190751, 190809, 190810, 190811, 190869, 190870, 190871, 190929, 190930, 190931, 190989, 190990, 190991, 191049, 191050, 191051, 191109, 191110, 191111, 191169, 191170, 191171, 191229, 191230, 191231, 191289, 191290, 191291, 191350, 191351, 191352, 191411, 191412, 191413, 191472, 191473, 191474, 191475, 191533, 191534, 191535, 191536, 191592, 191593, 191594, 191651, 191652, 191653, 191710, 191711, 191712, 191713, 191769, 191770, 191771, 191772, 191829, 191830, 191831, 191832, 191889, 191890, 191891, 191892, 191949, 191950, 191951, 191952, 192009, 192010, 192011, 192012, 192069, 192070, 192071, 192072, 192129, 192130, 192131, 192132, 192187, 192188, 192189, 192245, 192246, 192247, 192305, 192306, 192307, 192308, 192365, 192366, 192367, 192425, 192426, 192427, 192428, 192485, 192486, 192487, 192488, 192545, 192546, 192547, 192548, 192605, 192606, 192607, 192608, 192664, 192665, 192666, 192667, 192723, 192724, 192725, 192783, 192784, 192785, 192843, 192844, 192903, 192904, 192905, 192963, 192964, 193022, 193023, 193025, 193080, 193081, 193083, 193138, 193139, 193140, 193141, 193196, 193197, 193198, 193199, 193255, 193256, 193257, 193258, 193314, 193315, 193316, 193317, 193373, 193374, 193375, 193376, 193432, 193433, 193434, 193435, 193491, 193492, 193493, 193494, 193550, 193551, 193552, 193553, 193609, 193610, 193611, 193612, 193668, 193669, 193670, 193671, 193727, 193728, 193729, 193730, 193786, 193787, 193788, 193789, 193844, 193845, 193846, 193847, 193902, 193903, 193904, 193905, 193961, 193962, 193963, 193964, 194020, 194022, 194023, 194079, 194081, 194082, 194138, 194140, 194141, 194197, 194198, 194199, 194200, 194256, 194258, 194259, 194315, 194316, 194317, 194318, 194374, 194375, 194376, 194377, 194432, 194433, 194434, 194435, 194490, 194491, 194492, 194493, 194549, 194550, 194551, 194552, 194608, 194609, 194610, 194667, 194668, 194669, 194726, 194727, 194728, 194785, 194786, 194787, 194844, 194845, 194846, 194903, 194904, 194905, 194906, 194962, 194963, 194964, 194965, 195021, 195022, 195023, 195024, 195080, 195081, 195082, 195083, 195139, 195140, 195141, 195142, 195200, 195201, 195202, 195258, 195259, 195260, 195317, 195318, 195319, 195377, 195378, 195379, 195438, 195439, 195440, 195498, 195499, 195500, 195501, 195558, 195559, 195560, 195561, 195618, 195619, 195620, 195621, 195678, 195679, 195680, 195681, 195738, 195739, 195740, 195741, 195799, 195800, 195801, 195802, 195859, 195860, 195861, 195862, 195919, 195920, 195921, 195922, 195980, 195981, 195982, 195983, 196041, 196042, 196043, 196044, 196102, 196103, 196104, 196105, 196163, 196164, 196165, 196166, 196224, 196225, 196226, 196227, 196285, 196286, 196287, 196288, 196345, 196346, 196347, 196348, 196405, 196406, 196407, 196408, 196465, 196466, 196467, 196468, 196525, 196526, 196527, 196528, 196586, 196587, 196588, 196589, 196647, 196648, 196649, 196650, 196705, 196706, 196707, 196708, 196763, 196764, 196765, 196766, 196823, 196824, 196825, 196826, 196882, 196883, 196884, 196885, 196940, 196941, 196942, 196943, 196999, 197000, 197001, 197056, 197057, 197058, 197059, 197113, 197114, 197115, 197168, 197169, 197170, 197171, 197224, 197225, 197226, 197280, 197281, 197282, 197283, 197336, 197337, 197338, 197391, 197392, 197393, 197394, 197447, 197448, 197449, 197450, 197503, 197504, 197505, 197506, 197559, 197560, 197561, 197613, 197614, 197615, 197616, 197668, 197669, 197670, 197722, 197723, 197724, 197725, 197777, 197778, 197779, 197780, 197832, 197833, 197834, 197885, 197886, 197887, 197888, 197939, 197940, 197989, 197990, 197991, 197992, 198041, 198042, 198089, 198090, 198091, 198092, 198143, 198144, 198193, 198194, 198195, 198196, 198241, 198242, 198286, 198287, 198288, 198289, 198334, 198335, 198380, 198381, 198382, 198383, 198424, 198425, 198426, 198467, 198468, 198469, 198470, 198514, 198515, 198558, 198560, 198561, 198602, 198603, 198604, 198645, 198646, 198647, 198688, 198689, 198690, 198731, 198732, 198733, 198776, 198817, 198819, 198820, 198859, 198896, 198897, 198898, 198899, 198937, 198973, 198974, 198975, 198976, 199012, 199047, 199048, 199084, 199120, 199153, 199154, 199184, 199186, 199187, 199219, 199220, 199253, 199254, 199285, 199286, 199317, 199318, 199350, 199351, 199384, 199385, 199411, 199412, 199439, 199440, 199465, 199466, 199492, 199493, 199515, 199516, 199539, 199540, 199558, 199559, 199578, 199579, 199597, 199598, 199617, 199618, 199635, 199636, 199654, 199655, 199672, 199673, 199689, 199690, 199709, 199729, 199745, 199746, 199761, 199762, 199778, 199779, 199795, 199796], "numberOfPointsInBox": 5119}, "attributes": {"trackId": "left_second_track"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}, {"id": "cff15e77-6e63-4d8c-b67b-ac45c96cd684", "objectId": "e4366b9f-a350-42a9-8ac2-61eeeee5513c", "className": "3D_signal_pole", "geometry": {"associatedPoints": [159, 5760, 5761, 13438, 16540, 16541, 16853, 16855, 16856, 16857, 16858, 27163, 27164, 31412, 31415, 31416], "numberOfPointsInBox": 16}, "attributes": {"structure": "solid"}, "sensor": {"type": "LIDAR", "uri": "lidar_merged/000_1632321880.132833000.pcd", "timestamp": "1632321880.132833000"}}]}}]} diff --git a/tests/__test_assets__/understand_ai_real_life.json.license b/tests/__test_assets__/understand_ai_real_life.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/tests/__test_assets__/understand_ai_real_life.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/tests/__test_assets__/understand_ai_t4_short.json b/tests/__test_assets__/understand_ai_t4_short.json deleted file mode 100644 index c22a41e..0000000 --- a/tests/__test_assets__/understand_ai_t4_short.json +++ /dev/null @@ -1,823 +0,0 @@ -{ - "metadata": { - "clip_id": "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - "external_clip_id": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "project_id": "trains_4", - "export_time": "2023-04-20 01:38 UTC", - "exporter_version": "1.0.0", - "coordinate_system_3d": "FLU", - "coordinate_system_reference": "SENSOR", - "folder_name": "2021-09-22-14-28-01_2021-09-22-14-44-03" - }, - "coordinateSystems": [ - { - "coordinate_system_id": "rgb_middle_rect", - "topic": "/S1206063/image", - "frame_id": "S1206063", - "position": [ - 0, - 1, - 2 - ], - "rotation_quaternion": [ - 0.97518507, - -0.18529384, - -0.05469746, - -0.10811315 - ], - "rotation_matrix": [ - 0.9253488, - -0.3732186, - -0.0666149, - -0.3495646, - -0.9079555, - 0.2311309, - -0.1467457, - -0.1905905, - -0.9706395 - ], - "angle_axis_rotation": [ - 0.9809347, - -0.1863863, - -0.05502 - ], - "homogeneous_transform": [ - 0.9999680667369587, - -0.005432933661727504, - -0.005860779656024867, - 0.06694577073389593, - 0.005410226430795589, - 0.9999778242364062, - -0.003883360064516633, - -0.0009111521949818156, - 0.005881747726375861, - 0.003851527911158113, - 0.9999752850828029, - 2.054322280217477, - 0, - 0, - 0, - 1 - ], - "measured_position": [ - 0, - 0, - 0 - ], - "camera_matrix": [ - 0.48, - 0, - 0.81, - 0, - 0.16, - 0.83, - 0, - 0, - 1 - ], - "dist_coeffs": [ - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ] - }, - { - "coordinate_system_id": "ir_middle", - "topic": "/A0001781/image", - "frame_id": "A0001781", - "position": [ - 0, - 2, - 1 - ], - "rotation_quaternion": [ - -0.64984101, - -0.72563166, - 0.18784818, - -0.12600959 - ], - "rotation_matrix": [ - -0.1236565, - 0.9904318, - -0.0612698, - 0.8957491, - 0.0848394, - -0.4363896, - -0.427016, - -0.1088448, - -0.8976693 - ], - "angle_axis_rotation": [ - -0.6550625, - -0.7314621, - 0.1893575 - ], - "homogeneous_transform": [ - 0.998808097970439, - -0.003596303890419965, - 0.04867699689794021, - 0.09334241552161804, - 0.0039172823670546, - 0.9999712003585413, - -0.006500257868352383, - 0.2252408177142321, - -0.04865221811522157, - 0.006683191739435895, - 0.9987934203931477, - 3.517100000000001, - 0, - 0, - 0, - 1 - ], - "measured_position": [ - 0, - 0, - 0 - ], - "camera_matrix": [ - 0.47, - 0, - 0.85, - 0, - 0.15, - 0.23, - 0, - 0, - 1 - ], - "dist_coeffs": [ - 0.19, - 0.66, - 0.31, - 0.21, - 0.99 - ] - }, - { - "coordinate_system_id": "lidar_merged", - "topic": "/lidar_merged", - "frame_id": "lidar_merged", - "position": [ - 0, - 0, - 0 - ], - "rotation_quaternion": [ - 0, - 0, - 0, - 1 - ], - "rotation_matrix": [ - 1, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 1 - ], - "angle_axis_rotation": [ - 0, - 0, - 0 - ], - "homogeneous_transform": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ] - }, - { - "coordinate_system_id": "radar", - "rotation_around_z_in_degrees": 1.22869, - "topic": "/talker1/Nvt/Cartesian", - "frame_id": "navtech", - "position": [ - 2, - 0, - 1 - ], - "rotation_quaternion": [ - -0.64984101, - 0.18784818, - -0.72563166, - -0.12600959 - ], - "rotation_matrix": [ -0.1236565, -0.4270160, 0.8957491, - -0.0612698, -0.8976693, -0.4363896, - 0.9904318, -0.1088448, 0.0848394 ], - "angle_axis_rotation": [ - -0.6550625, 0.1893575, -0.7314621 - ], - "homogeneous_transform": [ - 0.9997700727912402, - -0.02144298372425017, - 0, - -0.002940099322541904, - 0.02144298372425017, - 0.9997700727912402, - 0, - 0.3296990382394244, - 0, - 0, - 1, - 1.5638, - 0, - 0, - 0, - 1 - ] - } - ], - "frames": [ - { - "frameId": "000", - "timestamp": "1632321743.134149", - "annotations": { - "2D_BOUNDING_BOX": [ - { - "id": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "2D_person", - "geometry": { - "xMin": -1.0, - "yMin": -0.5, - "xMax": 1, - "yMax": 2.5 - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "rgb_middle_rect", - "uri": "S1206063/rgb_test0.png", - "timestamp": "1632321743.100000072" - } - }, - { - "id": "68b4e02c-40c8-4de0-89ad-bc00ed05a043", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "2D_person", - "geometry": { - "xMin": 2.5, - "yMin": 2.0, - "xMax": 3.5, - "yMax": 2.0 - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "ir_middle", - "timestamp": "1632321743.106000004", - "uri": "A0001781/ir_test0.png" - } - } - ], - "2D_POLYLINE": [ - { - "id": "bebfbae4-61a2-4758-993c-efa846b050a5", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "2D_person", - "geometry": { - "points": [ - [ - 1.0, - 2.0 - ], - [ - 3.0, - 4.0 - ] - ] - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 2, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "rgb_middle_rect", - "uri": "S1206063/rgb_test0.png", - "timestamp": "1632321743.100000072" - } - } - ], - "2D_POLYGON": [ - { - "id": "3f63201c-fb33-4487-aff6-ae0aa5fa976c", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "2D_person", - "geometry": { - "points": [ - [ - 7.0, - 6.0 - ], - [ - 5.0, - 4.0 - ], - [ - 3.0, - 2.0 - ], - [ - 1.0, - 0.0 - ] - ] - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "ir_middle", - "timestamp": "1632321743.106000004", - "uri": "A0001781/ir_test0.png" - } - } - ], - "3D_BOUNDING_BOX": [ - { - "id": "dc2be700-8ee4-45c4-9256-920b5d55c917", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "3D_person", - "geometry": { - "center": { - "x": 0.49, - "y": 0.04, - "z": 0.73 - }, - "quaternion": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - "w": 0.0 - }, - "size": { - "width": 0.75, - "length": 0.01, - "height": 0.1 - } - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.134149", - "uri": "lidar_merged/lidar_test0.pcd" - } - }, - { - "id": "450ceb81-9778-4e63-bf89-42f3ed9f6747", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "3D_person", - "geometry": { - "center": { - "x": 0.0, - "y": 1.0, - "z": 2.0 - }, - "quaternion": { - "x": 3.0, - "y": 4.0, - "z": 5.0, - "w": 6.0 - }, - "size": { - "width": 7.0, - "length": 8.0, - "height": 9.0 - } - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.134149", - "uri": "lidar_merged/lidar_test0.pcd" - } - } - ], - "3D_SEGMENTATION": [ - { - "id": "c1087f1d-7271-4dee-83ad-519a4e3b78a8", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "3D_person", - "geometry": { - "associatedPoints": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ], - "numberOfPointsInBox": 10 - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.134149", - "uri": "lidar_merged/lidar_test0.pcd" - } - }, - { - "id": "50be7fe3-1f43-47ca-b65a-930e6cfacfeb", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "3D_person", - "geometry": { - "associatedPoints": [ - 9, - 8, - 7, - 6, - 5, - 4, - 3, - 2, - 1, - 0 - ], - "numberOfPointsInBox": 10 - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.134149", - "uri": "lidar_merged/lidar_test0.pcd" - } - } - ] - } - }, - { - "frameId": "001", - "timestamp": "1632321743.233263", - "annotations": { - "2D_BOUNDING_BOX": [ - { - "id": "6ba42cbc-484e-4b8d-a022-b23c2bb6643c", - "objectId": "6fe55546-0dd7-4e40-b6b4-bb7ea3445772", - "className": "2D_person", - "geometry": { - "xMin": -1.0, - "yMin": -0.5, - "xMax": 1, - "yMax": 2.5 - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "rgb_middle_rect", - "uri": "S1206063/rgb_test1.png", - "timestamp": "1632321743.2" - } - }, - { - "id": "5f28fa18-8f2a-4a40-a0b6-c0bbedc00f2e", - "objectId": "6fe55546-0dd7-4e40-b6b4-bb7ea3445772", - "className": "2D_person", - "geometry": { - "xMin": 2.5, - "yMin": 2.0, - "xMax": 3.5, - "yMax": 2.0 - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "ir_middle", - "timestamp": "1632321743.208000004", - "uri": "A0001781/ir_test1.png" - } - } - ], - "2D_POLYLINE": [ - { - "id": "e2503c5d-9fe4-4666-b510-ef644c5a766b", - "objectId": "6fe55546-0dd7-4e40-b6b4-bb7ea3445772", - "className": "2D_person", - "geometry": { - "points": [ - [ - 1.0, - 2.0 - ], - [ - 3.0, - 4.0 - ] - ] - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "rgb_middle_rect", - "uri": "S1206063/rgb_test1.png", - "timestamp": "1632321743.2" - } - } - ], - "2D_POLYGON": [], - "3D_BOUNDING_BOX": [ - { - "id": "536ac83a-32c8-4fce-8499-ef32716c64a6", - "objectId": "22dedd49-6dcb-413b-87ef-00ccfb532e98", - "className": "3D_train", - "geometry": { - "center": { - "x": 0.0, - "y": 1.0, - "z": 2.0 - }, - "quaternion": { - "x": 3.0, - "y": 4.0, - "z": 5.0, - "w": 6.0 - }, - "size": { - "width": 7.0, - "length": 8.0, - "height": 9.0 - } - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.233263", - "uri": "lidar_merged/lidar_test1.pcd" - } - }, - { - "id": "e53bd5e3-980a-4fa7-a0f9-5a2e59ba663c", - "objectId": "22dedd49-6dcb-413b-87ef-00ccfb532e98", - "className": "3D_train", - "geometry": { - "center": { - "x": 0.0, - "y": 1.0, - "z": 2.0 - }, - "quaternion": { - "x": 3.0, - "y": 4.0, - "z": 5.0, - "w": 6.0 - }, - "size": { - "width": 7.0, - "length": 8.0, - "height": 9.0 - } - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.233263", - "uri": "lidar_merged/lidar_test1.pcd" - } - } - ], - "3D_SEGMENTATION": [ - { - "id": "550df2c3-0e66-483e-bcc6-f3013b7e581b", - "objectId": "22dedd49-6dcb-413b-87ef-00ccfb532e98", - "className": "3D_train", - "geometry": { - "associatedPoints": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ], - "numberOfPointsInBox": 10 - }, - "attributes": { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - "test_num_attr0": 0, - "test_num_attr1": 1, - "test_bool_attr0": true, - "test_vec_attr0": [ - "0", - "1" - ], - "test_vec_attr1": [ - 0, - 1, - 2 - ], - "test_vec_attr2": [ - "a", - "b", - "c" - ] - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.233263", - "uri": "lidar_merged/lidar_test1.pcd" - } - }, - { - "id": "12b21c52-06ea-4269-9805-e7167e7a74ed", - "objectId": "22dedd49-6dcb-413b-87ef-00ccfb532e98", - "className": "3D_train", - "geometry": { - "associatedPoints": [ - 9, - 8, - 7, - 6, - 5, - 4, - 3, - 2, - 1, - 0 - ], - "numberOfPointsInBox": 10 - }, - "attributes": { - "test_bool_attr0": false - }, - "sensor": { - "type": "lidar_merged", - "timestamp": "1632321743.233263", - "uri": "lidar_merged/lidar_test1.pcd" - } - } - ] - } - } - ] -} diff --git a/tests/__test_assets__/understand_ai_t4_short.json.license b/tests/__test_assets__/understand_ai_t4_short.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/tests/__test_assets__/understand_ai_t4_short.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/tests/test_raillabel/format/understand_ai/conftest.py b/tests/test_raillabel/format/understand_ai/conftest.py deleted file mode 100644 index 3ec078f..0000000 --- a/tests/test_raillabel/format/understand_ai/conftest.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from test_uai_attributes import attributes_raillabel_dict, attributes_uai, attributes_uai_dict -from test_uai_bounding_box_2d import ( - bounding_box_2d_raillabel_dict, - bounding_box_2d_uai, - bounding_box_2d_uai_dict, -) -from test_uai_bounding_box_3d import ( - bounding_box_3d_raillabel_dict, - bounding_box_3d_uai, - bounding_box_3d_uai_dict, -) -from test_uai_coordinate_system import ( - coordinate_system_camera_raillabel_dict, - coordinate_system_camera_translated_uid, - coordinate_system_camera_uai, - coordinate_system_camera_uai_dict, - coordinate_system_lidar_raillabel_dict, - coordinate_system_lidar_translated_uid, - coordinate_system_lidar_uai, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_raillabel_dict, - coordinate_system_radar_translated_uid, - coordinate_system_radar_uai, - coordinate_system_radar_uai_dict, -) -from test_uai_frame import frame_raillabel_dict, frame_uai, frame_uai_dict -from test_uai_metadata import metadata_raillabel_dict, metadata_uai, metadata_uai_dict -from test_uai_point_3d import point_3d_uai, point_3d_uai_dict, point_3d_vec -from test_uai_polygon_2d import polygon_2d_raillabel_dict, polygon_2d_uai, polygon_2d_uai_dict -from test_uai_polyline_2d import polyline_2d_raillabel_dict, polyline_2d_uai, polyline_2d_uai_dict -from test_uai_quaternion import quaternion_uai, quaternion_uai_dict, quaternion_vec -from test_uai_scene import scene_raillabel_dict, scene_uai, scene_uai_dict -from test_uai_segmentation_3d import ( - segmentation_3d_raillabel_dict, - segmentation_3d_uai, - segmentation_3d_uai_dict, -) -from test_uai_sensor_reference import ( - sensor_camera_raillabel_dict, - sensor_camera_uai, - sensor_camera_uai_dict, - sensor_lidar_raillabel_dict, - sensor_lidar_uai, - sensor_lidar_uai_dict, -) -from test_uai_size_3d import size_3d_uai, size_3d_uai_dict, size_3d_vec diff --git a/tests/test_raillabel/format/understand_ai/test_uai_attributes.py b/tests/test_raillabel/format/understand_ai/test_uai_attributes.py deleted file mode 100644 index 23ecf4c..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_attributes.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import pytest - -# == Fixtures ========================= - -@pytest.fixture -def attributes_uai_dict() -> dict: - return { - "isDummy": False, - "carries": "nothing", - "connectedTo": [], - "pose": "upright" - } - -@pytest.fixture -def attributes_uai() -> dict: - return { - "isDummy": False, - "carries": "nothing", - "connectedTo": [], - "pose": "upright" - } - -@pytest.fixture -def attributes_raillabel_dict() -> dict: - return { - "text": [ - { - "name": "carries", - "val": "nothing" - }, - { - "name": "pose", - "val": "upright" - } - ], - "boolean": [ - { - "name": "isDummy", - "val": False - } - ], - "vec": [ - { - "name": "connectedTo", - "val": [] - } - ] - } diff --git a/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_2d.py b/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_2d.py deleted file mode 100644 index 59c89cb..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_2d.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id - -# == Fixtures ========================= - -@pytest.fixture -def bounding_box_2d_uai_dict(sensor_camera_uai_dict, attributes_uai_dict) -> dict: - return { - "id": "2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965", - "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", - "className": "test_class", - "geometry": { - "xMin": 1, - "yMin": 2, - "xMax": 3, - "yMax": 4 - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - -@pytest.fixture -def bounding_box_2d_uai(attributes_uai, sensor_camera_uai) -> dict: - return uai_format.BoundingBox2d( - id=UUID("2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965"), - object_id=UUID("48c988bd-76f1-423f-b46d-7e7acb859f31"), - class_name="test_class", - x_min=1, - y_min=2, - x_max=3, - y_max=4, - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - -@pytest.fixture -def bounding_box_2d_raillabel_dict(attributes_raillabel_dict, coordinate_system_camera_translated_uid) -> dict: - return { - "name": "2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965", - "val": [ - 2.0, - 3.0, - 2.0, - 2.0 - ], - "coordinate_system": coordinate_system_camera_translated_uid, - "attributes": attributes_raillabel_dict, - } - - -# == Tests ============================ - -def test_fromdict( - attributes_uai_dict, attributes_uai, - sensor_camera_uai_dict, sensor_camera_uai -): - bounding_box_2d = uai_format.BoundingBox2d.fromdict( - { - "id": "2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965", - "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", - "className": "test_class", - "geometry": { - "xMin": 1, - "yMin": 2, - "xMax": 3, - "yMax": 4 - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - ) - - assert bounding_box_2d.id == UUID("2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965") - assert bounding_box_2d.object_id == UUID("48c988bd-76f1-423f-b46d-7e7acb859f31") - assert bounding_box_2d.class_name == "test_class" - assert bounding_box_2d.x_min == 1 - assert bounding_box_2d.y_min == 2 - assert bounding_box_2d.x_max == 3 - assert bounding_box_2d.y_max == 4 - assert bounding_box_2d.attributes == attributes_uai - assert bounding_box_2d.sensor == sensor_camera_uai - - -def test_to_raillabel( - attributes_uai, attributes_raillabel_dict, - sensor_camera_uai, sensor_camera_raillabel_dict, coordinate_system_camera_translated_uid, -): - bounding_box_2d = uai_format.BoundingBox2d( - id=UUID("2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965"), - object_id=UUID("48c988bd-76f1-423f-b46d-7e7acb859f31"), - class_name="test_class", - x_min=1, - y_min=2, - x_max=3, - y_max=4, - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - - data_dict, object_id, translated_class_id, sensor_reference = bounding_box_2d.to_raillabel() - - assert data_dict == { - "name": "2f2a1d7f-56d1-435c-a3ec-d6b8fdaaa965", - "val": [ - 2.0, - 3.0, - 2.0, - 2.0 - ], - "coordinate_system": coordinate_system_camera_translated_uid, - "attributes": attributes_raillabel_dict, - } - assert object_id == str(bounding_box_2d.object_id) - assert translated_class_id == translate_class_id(bounding_box_2d.class_name) - assert sensor_reference == sensor_camera_raillabel_dict - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_3d.py b/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_3d.py deleted file mode 100644 index 3e82bcf..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_bounding_box_3d.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id - -# == Fixtures ========================= - -@pytest.fixture -def bounding_box_3d_uai_dict( - point_3d_uai_dict, size_3d_uai_dict, quaternion_uai_dict, - sensor_lidar_uai_dict, - attributes_uai_dict -) -> dict: - return { - "id": "910399ec-da3e-4d7e-be42-ef8e53e38ca6", - "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", - "className": "test_class", - "geometry": { - "size": size_3d_uai_dict, - "center": point_3d_uai_dict, - "quaternion": quaternion_uai_dict - }, - "attributes": attributes_uai_dict, - "sensor": sensor_lidar_uai_dict, - } - - -@pytest.fixture -def bounding_box_3d_uai( - point_3d_uai, size_3d_uai, quaternion_uai, - attributes_uai, - sensor_lidar_uai -): - return uai_format.BoundingBox3d( - id=UUID("910399ec-da3e-4d7e-be42-ef8e53e38ca6"), - object_id=UUID("48c988bd-76f1-423f-b46d-7e7acb859f31"), - class_name="test_class", - size=size_3d_uai, - center=point_3d_uai, - quaternion=quaternion_uai, - attributes=attributes_uai, - sensor=sensor_lidar_uai, - ) - -@pytest.fixture -def bounding_box_3d_raillabel_dict( - point_3d_vec, size_3d_vec, quaternion_vec, - coordinate_system_lidar_translated_uid, attributes_raillabel_dict -) -> dict: - return { - "name": "910399ec-da3e-4d7e-be42-ef8e53e38ca6", - "val": point_3d_vec + quaternion_vec + size_3d_vec, - "coordinate_system": coordinate_system_lidar_translated_uid, - "attributes": attributes_raillabel_dict - } - - -# == Tests ============================ - -def test_fromdict( - size_3d_uai_dict, point_3d_uai_dict, quaternion_uai_dict, - size_3d_uai, point_3d_uai, quaternion_uai, - sensor_lidar_uai_dict, sensor_lidar_uai, - attributes_uai_dict, attributes_uai, -): - bounding_box_3d = uai_format.BoundingBox3d.fromdict( - { - "id": "910399ec-da3e-4d7e-be42-ef8e53e38ca6", - "objectId": "48c988bd-76f1-423f-b46d-7e7acb859f31", - "className": "test_class", - "geometry": { - "size": size_3d_uai_dict, - "center": point_3d_uai_dict, - "quaternion": quaternion_uai_dict - }, - "attributes": attributes_uai_dict, - "sensor": sensor_lidar_uai_dict, - } - ) - - assert bounding_box_3d.object_id == UUID("48c988bd-76f1-423f-b46d-7e7acb859f31") - assert bounding_box_3d.id == UUID("910399ec-da3e-4d7e-be42-ef8e53e38ca6") - assert bounding_box_3d.class_name == "test_class" - assert bounding_box_3d.size == size_3d_uai - assert bounding_box_3d.center == point_3d_uai - assert bounding_box_3d.quaternion == quaternion_uai - assert bounding_box_3d.attributes == attributes_uai - assert bounding_box_3d.sensor == sensor_lidar_uai - - -def test_to_raillabel( - size_3d_uai, point_3d_uai, quaternion_uai, - point_3d_vec, quaternion_vec, size_3d_vec, - attributes_uai, attributes_raillabel_dict, - sensor_lidar_uai, coordinate_system_lidar_translated_uid, sensor_lidar_raillabel_dict, -): - bounding_box_3d = uai_format.BoundingBox3d( - id=UUID("910399ec-da3e-4d7e-be42-ef8e53e38ca6"), - object_id=UUID("48c988bd-76f1-423f-b46d-7e7acb859f31"), - class_name="test_class", - size=size_3d_uai, - center=point_3d_uai, - quaternion=quaternion_uai, - attributes=attributes_uai, - sensor=sensor_lidar_uai, - ) - - data_dict, object_id, translated_class_id, sensor_reference = bounding_box_3d.to_raillabel() - - assert data_dict == { - "name": "910399ec-da3e-4d7e-be42-ef8e53e38ca6", - "val": point_3d_vec + quaternion_vec + size_3d_vec, - "coordinate_system": coordinate_system_lidar_translated_uid, - "attributes": attributes_raillabel_dict - } - assert object_id == str(bounding_box_3d.object_id) - assert translated_class_id == translate_class_id(bounding_box_3d.class_name) - assert sensor_reference == sensor_lidar_raillabel_dict - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_coordinate_system.py b/tests/test_raillabel/format/understand_ai/test_uai_coordinate_system.py deleted file mode 100644 index 4fe0a80..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_coordinate_system.py +++ /dev/null @@ -1,341 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def coordinate_system_camera_uai_dict(point_3d_vec, quaternion_vec) -> dict: - return { - "coordinate_system_id": "ir_middle", - "topic": "/A0001781/image", - "frame_id": " A0001781", - "position": point_3d_vec, - "rotation_quaternion": quaternion_vec, - "rotation_matrix": [0] * 9, - "angle_axis_rotation": [0] * 3, - "homogeneous_transform": [0] * 16, - "measured_position": [0, 0, 0], - "camera_matrix": [ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ], - "dist_coeffs": [0, 1, 2, 3, 4] - } - -@pytest.fixture -def coordinate_system_camera_uai(point_3d_vec, quaternion_vec): - return uai_format.CoordinateSystem( - uid="ir_middle", - topic="/A0001781/image", - frame_id=" A0001781", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - measured_position=[0, 0, 0], - camera_matrix=[ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ], - dist_coeffs=[0, 1, 2, 3, 4] - ) - -@pytest.fixture -def coordinate_system_camera_raillabel_dict(point_3d_vec, quaternion_vec) -> dict: - return ( - { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": { - "translation": point_3d_vec, - "quaternion": quaternion_vec - } - }, - { - "type": "camera", - "uri": "/A0001781/image", - "stream_properties": { - "intrinsics_pinhole": { - "camera_matrix": [ - 3535, 0, 319.5, 0, - 0, 3535, 239.5, 0, - 0, 0, 1 , 0, - ], - "distortion_coeffs": [0, 1, 2, 3, 4], - "width_px": 640, - "height_px": 480, - } - } - } - ) - -@pytest.fixture -def coordinate_system_camera_translated_uid() -> dict: - return "ir_middle" - - -@pytest.fixture -def coordinate_system_lidar_uai_dict(point_3d_vec, quaternion_vec) -> dict: - return { - "coordinate_system_id": "LIDAR", - "topic": "/lidar_merged", - "frame_id": "lidar_merged", - "position": point_3d_vec, - "rotation_quaternion": quaternion_vec, - "rotation_matrix": [0] * 9, - "angle_axis_rotation": [0] * 3, - "homogeneous_transform": [0] * 16, - } - -@pytest.fixture -def coordinate_system_lidar_uai(point_3d_vec, quaternion_vec): - return uai_format.CoordinateSystem( - uid="LIDAR", - topic="/lidar_merged", - frame_id="lidar_merged", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - ) - -@pytest.fixture -def coordinate_system_lidar_raillabel_dict(point_3d_vec, quaternion_vec) -> dict: - return ( - { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": { - "translation": point_3d_vec, - "quaternion": quaternion_vec - } - }, - { - "type": "lidar", - "uri": "/lidar_merged", - } - ) - -@pytest.fixture -def coordinate_system_lidar_translated_uid() -> dict: - return "lidar" - - -@pytest.fixture -def coordinate_system_radar_uai_dict(point_3d_vec, quaternion_vec) -> dict: - return { - "coordinate_system_id": "radar", - "rotation_around_z_in_degrees": 1.22869, - "topic": "/talker1/Nvt/Cartesian", - "frame_id": "navtech", - "position": point_3d_vec, - "rotation_quaternion": quaternion_vec, - "rotation_matrix": [0] * 9, - "angle_axis_rotation": [0] * 3, - "homogeneous_transform": [0] * 16, - } - -@pytest.fixture -def coordinate_system_radar_uai(point_3d_vec, quaternion_vec): - return uai_format.CoordinateSystem( - uid="radar", - topic="/talker1/Nvt/Cartesian", - frame_id="navtech", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - ) - -@pytest.fixture -def coordinate_system_radar_raillabel_dict(point_3d_vec, quaternion_vec) -> dict: - return ( - { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": { - "translation": point_3d_vec, - "quaternion": quaternion_vec - } - }, - { - "type": "radar", - "uri": "/talker1/Nvt/Cartesian", - "stream_properties": { - "intrinsics_radar": { - "resolution_px_per_m": 2.856, - "width_px": 2856, - "height_px": 1428 - } - } - } - ) - -@pytest.fixture -def coordinate_system_radar_translated_uid() -> dict: - return "radar" - -# == Tests ============================ - -def test_fromdict(point_3d_vec, quaternion_vec): - coordinate_system = uai_format.CoordinateSystem.fromdict( - { - "coordinate_system_id": "ir_middle", - "topic": "/A0001781/image", - "frame_id": " A0001781", - "position": point_3d_vec, - "rotation_quaternion": quaternion_vec, - "rotation_matrix": [0] * 9, - "angle_axis_rotation": [0] * 3, - "homogeneous_transform": [0] * 16, - "measured_position": [0, 0, 0], - "camera_matrix": [ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ], - "dist_coeffs": [0, 1, 2, 3, 4] - } - ) - - assert coordinate_system.uid == "ir_middle" - assert coordinate_system.topic == "/A0001781/image" - assert coordinate_system.frame_id == " A0001781" - assert coordinate_system.position == point_3d_vec - assert coordinate_system.rotation_quaternion == quaternion_vec - assert coordinate_system.rotation_matrix == [0] * 9 - assert coordinate_system.angle_axis_rotation == [0] * 3 - assert coordinate_system.homogeneous_transform == [0] * 16 - assert coordinate_system.measured_position == [0, 0, 0] - assert coordinate_system.camera_matrix == [ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ] - assert coordinate_system.dist_coeffs == [0, 1, 2, 3, 4] - - -def test_to_raillabel__coordinate_system(point_3d_vec, quaternion_vec): - coordinate_system = uai_format.CoordinateSystem( - uid="ir_middle", - topic="/A0001781/image", - frame_id=" A0001781", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - measured_position=[0, 0, 0], - camera_matrix=[ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ], - dist_coeffs=[0, 1, 2, 3, 4] - ) - - assert coordinate_system.to_raillabel()[0] == { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": { - "translation": point_3d_vec, - "quaternion": quaternion_vec - } - } - -def test_to_raillabel__stream__camera(point_3d_vec, quaternion_vec): - coordinate_system = uai_format.CoordinateSystem( - uid="ir_middle", - topic="/A0001781/image", - frame_id=" A0001781", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - measured_position=[0, 0, 0], - camera_matrix=[ - 3535, 0, 319.5, - 0, 3535, 239.5, - 0, 0, 1 , - ], - dist_coeffs=[0, 1, 2, 3, 4] - ) - - assert coordinate_system.to_raillabel()[1] == { - "type": "camera", - "uri": "/A0001781/image", - "stream_properties": { - "intrinsics_pinhole": { - "camera_matrix": [ - 3535, 0, 319.5, 0, - 0, 3535, 239.5, 0, - 0, 0, 1 , 0, - ], - "distortion_coeffs": [0, 1, 2, 3, 4], - "width_px": 640, - "height_px": 480, - } - } - } - -def test_to_raillabel__stream__lidar(point_3d_vec, quaternion_vec): - coordinate_system = uai_format.CoordinateSystem( - uid="LIDAR", - topic="/lidar_merged", - frame_id="lidar_merged", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - ) - - assert coordinate_system.to_raillabel()[1] == { - "type": "lidar", - "uri": "/lidar_merged", - } - -def test_to_raillabel__stream__radar(point_3d_vec, quaternion_vec): - coordinate_system = uai_format.CoordinateSystem( - uid="radar", - topic="/talker1/Nvt/Cartesian", - frame_id="navtech", - position=point_3d_vec, - rotation_quaternion=quaternion_vec, - rotation_matrix=[0] * 9, - angle_axis_rotation=[0] * 3, - homogeneous_transform=[0] * 16, - ) - - assert coordinate_system.to_raillabel()[1] == { - "type": "radar", - "uri": "/talker1/Nvt/Cartesian", - "stream_properties": { - "intrinsics_radar": { - "resolution_px_per_m": 2.856, - "width_px": 2856, - "height_px": 1428 - } - } - } - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_frame.py b/tests/test_raillabel/format/understand_ai/test_uai_frame.py deleted file mode 100644 index 0e1496a..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_frame.py +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel._util._warning import _WarningsLogger - -# == Fixtures ========================= - -@pytest.fixture -def frame_uai_dict( - bounding_box_2d_uai_dict, - bounding_box_3d_uai_dict, - polygon_2d_uai_dict, - polyline_2d_uai_dict, - segmentation_3d_uai_dict, - sensor_lidar_uai_dict, -) -> dict: - return { - "frameId": "000", - "timestamp": sensor_lidar_uai_dict["timestamp"], - "annotations": { - "2D_BOUNDING_BOX": [bounding_box_2d_uai_dict], - "2D_POLYLINE": [polyline_2d_uai_dict], - "2D_POLYGON": [polygon_2d_uai_dict], - "3D_BOUNDING_BOX": [bounding_box_3d_uai_dict], - "3D_SEGMENTATION": [segmentation_3d_uai_dict], - } - } - -@pytest.fixture -def frame_uai( - bounding_box_2d_uai, - bounding_box_3d_uai, - polygon_2d_uai, - polyline_2d_uai, - segmentation_3d_uai, - sensor_lidar_uai, -): - return uai_format.Frame( - id=0, - timestamp=sensor_lidar_uai.timestamp, - bounding_box_2ds={str(bounding_box_2d_uai.id): bounding_box_2d_uai}, - bounding_box_3ds={str(bounding_box_3d_uai.id): bounding_box_3d_uai}, - polygon_2ds={str(polygon_2d_uai.id): polygon_2d_uai}, - polyline_2ds={str(polyline_2d_uai.id): polyline_2d_uai}, - segmentation_3ds={str(segmentation_3d_uai.id): segmentation_3d_uai}, - ) - -@pytest.fixture -def frame_raillabel_dict( - bounding_box_2d_uai, bounding_box_2d_raillabel_dict, - bounding_box_3d_raillabel_dict, - polygon_2d_uai, polygon_2d_raillabel_dict, - polyline_2d_uai, polyline_2d_raillabel_dict, - segmentation_3d_uai, segmentation_3d_raillabel_dict, - sensor_lidar_uai, sensor_lidar_raillabel_dict, coordinate_system_lidar_translated_uid, - sensor_camera_raillabel_dict, coordinate_system_camera_translated_uid, -) -> dict: - return { - "frame_properties": { - "timestamp": str(sensor_lidar_uai.timestamp), - "streams": { - coordinate_system_camera_translated_uid: sensor_camera_raillabel_dict, - coordinate_system_lidar_translated_uid: sensor_lidar_raillabel_dict, - } - }, - "objects": { - str(bounding_box_2d_uai.object_id): { - "object_data": { - "bbox": [bounding_box_2d_raillabel_dict], - "cuboid": [bounding_box_3d_raillabel_dict], - } - }, - str(polygon_2d_uai.object_id): { - "object_data": { - "poly2d": [polygon_2d_raillabel_dict] - } - }, - str(polyline_2d_uai.object_id): { - "object_data": { - "poly2d": [polyline_2d_raillabel_dict] - } - }, - str(segmentation_3d_uai.object_id): { - "object_data": { - "vec": [segmentation_3d_raillabel_dict] - } - }, - } - } - -# == Tests ============================ - -def test_fromdict( - bounding_box_2d_uai_dict, bounding_box_2d_uai, - bounding_box_3d_uai_dict, bounding_box_3d_uai, - polygon_2d_uai_dict, polygon_2d_uai, - polyline_2d_uai_dict, polyline_2d_uai, - segmentation_3d_uai_dict, segmentation_3d_uai, - sensor_lidar_uai_dict, sensor_lidar_uai, -): - frame = uai_format.Frame.fromdict( - { - "frameId": "000", - "timestamp": sensor_lidar_uai_dict["timestamp"], - "annotations": { - "2D_BOUNDING_BOX": [bounding_box_2d_uai_dict], - "2D_POLYLINE": [polyline_2d_uai_dict], - "2D_POLYGON": [polygon_2d_uai_dict], - "3D_BOUNDING_BOX": [bounding_box_3d_uai_dict], - "3D_SEGMENTATION": [segmentation_3d_uai_dict], - } - } - ) - - assert frame.id == 0 - assert frame.timestamp == sensor_lidar_uai.timestamp - assert frame.bounding_box_2ds == {str(bounding_box_2d_uai.id): bounding_box_2d_uai} - assert frame.bounding_box_3ds == {str(bounding_box_3d_uai.id): bounding_box_3d_uai} - assert frame.polygon_2ds == {str(polygon_2d_uai.id): polygon_2d_uai} - assert frame.polyline_2ds == {str(polyline_2d_uai.id): polyline_2d_uai} - assert frame.segmentation_3ds == {str(segmentation_3d_uai.id): segmentation_3d_uai} - - -def test_to_raillabel( - bounding_box_2d_uai, bounding_box_2d_raillabel_dict, - bounding_box_3d_uai, bounding_box_3d_raillabel_dict, - polygon_2d_uai, polygon_2d_raillabel_dict, - polyline_2d_uai, polyline_2d_raillabel_dict, - segmentation_3d_uai, segmentation_3d_raillabel_dict, - sensor_lidar_uai, sensor_lidar_raillabel_dict, coordinate_system_lidar_translated_uid, - sensor_camera_raillabel_dict, coordinate_system_camera_translated_uid, -): - frame = uai_format.Frame( - id=0, - timestamp=sensor_lidar_uai.timestamp, - bounding_box_2ds={str(bounding_box_2d_uai.id): bounding_box_2d_uai}, - bounding_box_3ds={str(bounding_box_3d_uai.id): bounding_box_3d_uai}, - polygon_2ds={str(polygon_2d_uai.id): polygon_2d_uai}, - polyline_2ds={str(polyline_2d_uai.id): polyline_2d_uai}, - segmentation_3ds={str(segmentation_3d_uai.id): segmentation_3d_uai}, - ) - - assert frame.to_raillabel() == { - "frame_properties": { - "timestamp": str(sensor_lidar_uai.timestamp), - "streams": { - coordinate_system_camera_translated_uid: sensor_camera_raillabel_dict, - coordinate_system_lidar_translated_uid: sensor_lidar_raillabel_dict, - } - }, - "objects": { - str(bounding_box_2d_uai.object_id): { - "object_data": { - "bbox": [bounding_box_2d_raillabel_dict], - "cuboid": [bounding_box_3d_raillabel_dict], - } - }, - str(polygon_2d_uai.object_id): { - "object_data": { - "poly2d": [polygon_2d_raillabel_dict] - } - }, - str(polyline_2d_uai.object_id): { - "object_data": { - "poly2d": [polyline_2d_raillabel_dict] - } - }, - str(segmentation_3d_uai.object_id): { - "object_data": { - "vec": [segmentation_3d_raillabel_dict] - } - }, - } - } - - -def test_warning_duplicate_annotation_id( - bounding_box_2d_uai_dict, polyline_2d_uai_dict, - sensor_lidar_uai_dict -): - polyline_2d_uai_dict["id"] = bounding_box_2d_uai_dict["id"] - - frame_dict = { - "frameId": "000", - "timestamp": sensor_lidar_uai_dict["timestamp"], - "annotations": { - "2D_BOUNDING_BOX": [bounding_box_2d_uai_dict], - "2D_POLYLINE": [polyline_2d_uai_dict], - "2D_POLYGON": [], - "3D_BOUNDING_BOX": [], - "3D_SEGMENTATION": [], - } - } - - with _WarningsLogger() as logger: - uai_format.Frame.fromdict(frame_dict) - - assert len(logger.warnings) == 1 - assert bounding_box_2d_uai_dict["id"] in logger.warnings[0] - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_metadata.py b/tests/test_raillabel/format/understand_ai/test_uai_metadata.py deleted file mode 100644 index d890874..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_metadata.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def metadata_uai_dict() -> dict: - return { - "clip_id": "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - "external_clip_id": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "project_id": "trains_4", - "export_time": "2023-04-20 01:38 UTC", - "exporter_version": "1.0.0", - "coordinate_system_3d": "FLU", - "coordinate_system_reference": "SENSOR", - "folder_name": "2021-09-22-14-28-01_2021-09-22-14-44-03", - } - -@pytest.fixture -def metadata_uai(): - return uai_format.Metadata( - clip_id="db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - external_clip_id="2021-09-22-14-28-01_2021-09-22-14-44-03", - project_id="trains_4", - export_time="2023-04-20 01:38 UTC", - exporter_version="1.0.0", - coordinate_system_3d="FLU", - coordinate_system_reference="SENSOR", - folder_name="2021-09-22-14-28-01_2021-09-22-14-44-03", - ) - -@pytest.fixture -def metadata_raillabel_dict(json_data) -> dict: - return { - "annotator": "understandAI GmbH", - "schema_version": "1.0.0", - "name": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "subschema_version": json_data["raillabel_schema"]["version"], - "tagged_file": "2021-09-22-14-28-01_2021-09-22-14-44-03" - } - - -# == Tests ============================ - -def test_fromdict(): - metadata = uai_format.Metadata.fromdict( - { - "clip_id": "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - "external_clip_id": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "project_id": "trains_4", - "export_time": "2023-04-20 01:38 UTC", - "exporter_version": "1.0.0", - "coordinate_system_3d": "FLU", - "coordinate_system_reference": "SENSOR", - "folder_name": "2021-09-22-14-28-01_2021-09-22-14-44-03", - } - ) - - assert metadata.clip_id == "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03" - assert metadata.external_clip_id == "2021-09-22-14-28-01_2021-09-22-14-44-03" - assert metadata.project_id == "trains_4" - assert metadata.export_time == "2023-04-20 01:38 UTC" - assert metadata.exporter_version == "1.0.0" - assert metadata.coordinate_system_3d == "FLU" - assert metadata.coordinate_system_reference == "SENSOR" - assert metadata.folder_name == "2021-09-22-14-28-01_2021-09-22-14-44-03" - - -def test_to_raillabel(json_data): - metadata = uai_format.Metadata( - clip_id="db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - external_clip_id="2021-09-22-14-28-01_2021-09-22-14-44-03", - project_id="trains_4", - export_time="2023-04-20 01:38 UTC", - exporter_version="1.0.0", - coordinate_system_3d="FLU", - coordinate_system_reference="SENSOR", - folder_name="2021-09-22-14-28-01_2021-09-22-14-44-03", - ) - - assert metadata.to_raillabel() == { - "annotator": "understandAI GmbH", - "schema_version": "1.0.0", - "name": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "subschema_version": json_data["raillabel_schema"]["version"], - "tagged_file": "2021-09-22-14-28-01_2021-09-22-14-44-03" - } - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_point_3d.py b/tests/test_raillabel/format/understand_ai/test_uai_point_3d.py deleted file mode 100644 index 341eacb..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_point_3d.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def point_3d_uai_dict() -> dict: - return { - "x": 0, - "y": 1, - "z": 2, - } - -@pytest.fixture -def point_3d_uai() -> dict: - return uai_format.Point3d( - x=0, - y=1, - z=2, - ) - -@pytest.fixture -def point_3d_vec() -> dict: - return [0, 1, 2] - -# == Tests ============================ - -def test_fromdict(): - point_3d = uai_format.Point3d.fromdict( - { - "x": 0, - "y": 1, - "z": 2, - } - ) - - assert point_3d.x == 0 - assert point_3d.y == 1 - assert point_3d.z == 2 - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_polygon_2d.py b/tests/test_raillabel/format/understand_ai/test_uai_polygon_2d.py deleted file mode 100644 index 3b7ef99..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_polygon_2d.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id - -# == Fixtures ========================= - -@pytest.fixture -def polygon_2d_uai_dict(sensor_camera_uai_dict, attributes_uai_dict) -> dict: - return { - "id": "0f90cffa-2b6b-4e09-8fc2-527769a94e0a", - "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", - "className": "test_class", - "geometry": { - "points": [ - [127.71153737657284, -0.3861000079676791], - [127.4762636010818, 328.04436391207815], - [115.77703250958459, 334.4789410124016], - [115.01063176442402, 411.0810690770479], - ] - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - - -@pytest.fixture -def polygon_2d_uai(attributes_uai, sensor_camera_uai) -> dict: - return uai_format.Polygon2d( - id=UUID("0f90cffa-2b6b-4e09-8fc2-527769a94e0a"), - object_id=UUID("58e7edd8-a7ee-4775-a837-e6dd375e8150"), - class_name="test_class", - points=[ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ], - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - -@pytest.fixture -def polygon_2d_raillabel_dict(attributes_raillabel_dict, coordinate_system_camera_translated_uid) -> dict: - return { - "name": "0f90cffa-2b6b-4e09-8fc2-527769a94e0a", - "val": [ - 127.71153737657284, -0.3861000079676791, - 127.4762636010818, 328.04436391207815, - 115.77703250958459, 334.4789410124016, - 115.01063176442402, 411.0810690770479, - ], - "mode": "MODE_POLY2D_ABSOLUTE", - "closed": True, - "coordinate_system": coordinate_system_camera_translated_uid, - "attributes": attributes_raillabel_dict, - } - - -# == Tests ============================ - -def test_fromdict( - attributes_uai_dict, attributes_uai, - sensor_camera_uai_dict, sensor_camera_uai -): - polygon_2d = uai_format.Polygon2d.fromdict( - { - "id": "0f90cffa-2b6b-4e09-8fc2-527769a94e0a", - "objectId": "58e7edd8-a7ee-4775-a837-e6dd375e8150", - "className": "test_class", - "geometry": { - "points": [ - [127.71153737657284, -0.3861000079676791], - [127.4762636010818, 328.04436391207815], - [115.77703250958459, 334.4789410124016], - [115.01063176442402, 411.0810690770479], - ] - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - ) - - assert polygon_2d.id == UUID("0f90cffa-2b6b-4e09-8fc2-527769a94e0a") - assert polygon_2d.object_id == UUID("58e7edd8-a7ee-4775-a837-e6dd375e8150") - assert polygon_2d.class_name == "test_class" - assert polygon_2d.points == [ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ] - assert polygon_2d.attributes == attributes_uai - assert polygon_2d.sensor == sensor_camera_uai - - -def test_to_raillabel( - attributes_uai, attributes_raillabel_dict, - sensor_camera_uai, sensor_camera_raillabel_dict, coordinate_system_camera_translated_uid, -): - polygon_2d = uai_format.Polygon2d( - id=UUID("0f90cffa-2b6b-4e09-8fc2-527769a94e0a"), - object_id=UUID("58e7edd8-a7ee-4775-a837-e6dd375e8150"), - class_name="test_class", - points=[ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ], - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - - data_dict, object_id, translated_class_id, sensor_reference = polygon_2d.to_raillabel() - - assert data_dict == { - "name": "0f90cffa-2b6b-4e09-8fc2-527769a94e0a", - "val": [ - 127.71153737657284, -0.3861000079676791, - 127.4762636010818, 328.04436391207815, - 115.77703250958459, 334.4789410124016, - 115.01063176442402, 411.0810690770479, - ], - "mode": "MODE_POLY2D_ABSOLUTE", - "coordinate_system": coordinate_system_camera_translated_uid, - "closed": True, - "attributes": attributes_raillabel_dict, - } - assert object_id == str(polygon_2d.object_id) - assert translated_class_id == translate_class_id(polygon_2d.class_name) - assert sensor_reference == sensor_camera_raillabel_dict - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_polyline_2d.py b/tests/test_raillabel/format/understand_ai/test_uai_polyline_2d.py deleted file mode 100644 index 5230c07..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_polyline_2d.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id - -# == Fixtures ========================= - -@pytest.fixture -def polyline_2d_uai_dict(sensor_camera_uai_dict, attributes_uai_dict) -> dict: - return { - "id": "7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2", - "objectId": "4d8eca35-6c1d-4159-8062-21c2f2c051df", - "className": "test_class", - "geometry": { - "points": [ - [127.71153737657284, -0.3861000079676791], - [127.4762636010818, 328.04436391207815], - [115.77703250958459, 334.4789410124016], - [115.01063176442402, 411.0810690770479], - ] - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - - -@pytest.fixture -def polyline_2d_uai(attributes_uai, sensor_camera_uai) -> dict: - return uai_format.Polyline2d( - id=UUID("7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2"), - object_id=UUID("4d8eca35-6c1d-4159-8062-21c2f2c051df"), - class_name="test_class", - points=[ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ], - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - -@pytest.fixture -def polyline_2d_raillabel_dict(attributes_raillabel_dict, coordinate_system_camera_translated_uid) -> dict: - return { - "name": "7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2", - "val": [ - 127.71153737657284, -0.3861000079676791, - 127.4762636010818, 328.04436391207815, - 115.77703250958459, 334.4789410124016, - 115.01063176442402, 411.0810690770479, - ], - "mode": "MODE_POLY2D_ABSOLUTE", - "closed": False, - "coordinate_system": coordinate_system_camera_translated_uid, - "attributes": attributes_raillabel_dict, - } - - -# == Tests ============================ - -def test_fromdict( - attributes_uai_dict, attributes_uai, - sensor_camera_uai_dict, sensor_camera_uai -): - polyline_2d = uai_format.Polyline2d.fromdict( - { - "id": "7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2", - "objectId": "4d8eca35-6c1d-4159-8062-21c2f2c051df", - "className": "test_class", - "geometry": { - "points": [ - [127.71153737657284, -0.3861000079676791], - [127.4762636010818, 328.04436391207815], - [115.77703250958459, 334.4789410124016], - [115.01063176442402, 411.0810690770479], - ] - }, - "attributes": attributes_uai_dict, - "sensor": sensor_camera_uai_dict - } - ) - - assert polyline_2d.id == UUID("7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2") - assert polyline_2d.object_id == UUID("4d8eca35-6c1d-4159-8062-21c2f2c051df") - assert polyline_2d.class_name == "test_class" - assert polyline_2d.points == [ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ] - assert polyline_2d.attributes == attributes_uai - assert polyline_2d.sensor == sensor_camera_uai - - -def test_to_raillabel( - attributes_uai, attributes_raillabel_dict, - sensor_camera_uai, sensor_camera_raillabel_dict, coordinate_system_camera_translated_uid, -): - polyline_2d = uai_format.Polyline2d( - id=UUID("7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2"), - object_id=UUID("4d8eca35-6c1d-4159-8062-21c2f2c051df"), - class_name="test_class", - points=[ - (127.71153737657284, -0.3861000079676791), - (127.4762636010818, 328.04436391207815), - (115.77703250958459, 334.4789410124016), - (115.01063176442402, 411.0810690770479), - ], - attributes=attributes_uai, - sensor=sensor_camera_uai, - ) - - data_dict, object_id, translated_class_id, sensor_reference = polyline_2d.to_raillabel() - - assert data_dict == { - "name": "7f2b99b7-61e4-4f9f-96e9-d3e9f583d7c2", - "val": [ - 127.71153737657284, -0.3861000079676791, - 127.4762636010818, 328.04436391207815, - 115.77703250958459, 334.4789410124016, - 115.01063176442402, 411.0810690770479, - ], - "mode": "MODE_POLY2D_ABSOLUTE", - "coordinate_system": coordinate_system_camera_translated_uid, - "closed": False, - "attributes": attributes_raillabel_dict, - } - assert object_id == str(polyline_2d.object_id) - assert translated_class_id == translate_class_id(polyline_2d.class_name) - assert sensor_reference == sensor_camera_raillabel_dict - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_quaternion.py b/tests/test_raillabel/format/understand_ai/test_uai_quaternion.py deleted file mode 100644 index 5364632..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_quaternion.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def quaternion_uai_dict() -> dict: - return { - "x": 0.75318325, - "y": -0.10270147, - "z": 0.21430262, - "w": -0.61338551, - } - -@pytest.fixture -def quaternion_uai() -> dict: - return uai_format.Quaternion( - x=0.75318325, - y=-0.10270147, - z=0.21430262, - w=-0.61338551, - ) - -@pytest.fixture -def quaternion_vec() -> dict: - return [0.75318325, -0.10270147, 0.21430262, -0.61338551] - -# == Tests ============================ - -def test_fromdict(): - quaternion = uai_format.Quaternion.fromdict( - { - "x": 0.75318325, - "y": -0.10270147, - "z": 0.21430262, - "w": -0.61338551, - } - ) - - assert quaternion.x == 0.75318325 - assert quaternion.y == -0.10270147 - assert quaternion.z == 0.21430262 - assert quaternion.w == -0.61338551 - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_scene.py b/tests/test_raillabel/format/understand_ai/test_uai_scene.py deleted file mode 100644 index e5f619e..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_scene.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel._util._warning import _WarningsLogger - -# == Fixtures ========================= - -@pytest.fixture -def scene_uai_dict( - metadata_uai_dict, - coordinate_system_camera_uai_dict, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_uai_dict, - frame_uai_dict, -) -> dict: - return { - "metadata": metadata_uai_dict, - "coordinateSystems": [ - coordinate_system_camera_uai_dict, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_uai_dict, - ], - "frames": [frame_uai_dict] - } - -@pytest.fixture -def scene_uai( - metadata_uai, - coordinate_system_camera_uai, - coordinate_system_lidar_uai, - coordinate_system_radar_uai, - frame_uai, -): - return uai_format.Scene( - metadata=metadata_uai, - coordinate_systems={ - coordinate_system_camera_uai.uid: coordinate_system_camera_uai, - coordinate_system_lidar_uai.uid: coordinate_system_lidar_uai, - coordinate_system_radar_uai.uid: coordinate_system_radar_uai, - }, - frames={ - frame_uai.id: frame_uai - }, - ) - -@pytest.fixture -def scene_raillabel_dict( - metadata_raillabel_dict, - coordinate_system_camera_uai, coordinate_system_camera_raillabel_dict, - coordinate_system_lidar_uai, coordinate_system_lidar_raillabel_dict, - coordinate_system_radar_uai, coordinate_system_radar_raillabel_dict, -) -> dict: - return - -# == Tests ============================ - -def test_fromdict( - metadata_uai_dict, metadata_uai, - coordinate_system_camera_uai_dict, coordinate_system_camera_uai, - coordinate_system_lidar_uai_dict, coordinate_system_lidar_uai, - coordinate_system_radar_uai_dict, coordinate_system_radar_uai, - frame_uai_dict, frame_uai, -): - scene = uai_format.Scene.fromdict( - { - "metadata": metadata_uai_dict, - "coordinateSystems": [ - coordinate_system_camera_uai_dict, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_uai_dict, - ], - "frames": [frame_uai_dict] - } - ) - - assert scene.metadata == metadata_uai - assert scene.coordinate_systems == { - coordinate_system_camera_uai.uid: coordinate_system_camera_uai, - coordinate_system_lidar_uai.uid: coordinate_system_lidar_uai, - coordinate_system_radar_uai.uid: coordinate_system_radar_uai, - } - assert scene.frames == { - frame_uai.id: frame_uai - } - -def test_fromdict_duplicate_frame_id_warning( - metadata_uai_dict, - coordinate_system_camera_uai_dict, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_uai_dict, - frame_uai_dict, -): - with _WarningsLogger() as logger: - scene = uai_format.Scene.fromdict( - { - "metadata": metadata_uai_dict, - "coordinateSystems": [ - coordinate_system_camera_uai_dict, - coordinate_system_lidar_uai_dict, - coordinate_system_radar_uai_dict, - ], - "frames": [frame_uai_dict, frame_uai_dict] - } - ) - - assert len(logger.warnings) == 1 - assert "0" in logger.warnings[0] - assert len(scene.frames) == 1 - - -def test_to_raillabel__metadata(metadata_uai, metadata_raillabel_dict): - scene = uai_format.Scene( - metadata=metadata_uai, - coordinate_systems={}, - frames={}, - ) - - assert scene.to_raillabel()["openlabel"]["metadata"] == metadata_raillabel_dict - -def test_to_raillabel__sensors( - metadata_uai, - coordinate_system_camera_uai, coordinate_system_lidar_uai, - coordinate_system_camera_raillabel_dict, coordinate_system_lidar_raillabel_dict, - coordinate_system_camera_translated_uid, coordinate_system_lidar_translated_uid, -): - scene = uai_format.Scene( - metadata=metadata_uai, - coordinate_systems={ - coordinate_system_camera_uai.uid: coordinate_system_camera_uai, - coordinate_system_lidar_uai.uid: coordinate_system_lidar_uai, - }, - frames={}, - ) - - assert scene.to_raillabel()["openlabel"]["coordinate_systems"] == { - "base": { - "type": "local", - "parent": "", - "children": [ - coordinate_system_camera_translated_uid, - coordinate_system_lidar_translated_uid - ] - }, - coordinate_system_camera_translated_uid: coordinate_system_camera_raillabel_dict[0], - coordinate_system_lidar_translated_uid: coordinate_system_lidar_raillabel_dict[0], - } - assert scene.to_raillabel()["openlabel"]["streams"] == { - coordinate_system_camera_translated_uid: coordinate_system_camera_raillabel_dict[1], - coordinate_system_lidar_translated_uid: coordinate_system_lidar_raillabel_dict[1], - } - -def test_to_raillabel__frames( - metadata_uai, - coordinate_system_camera_uai, coordinate_system_lidar_uai, - frame_uai, frame_raillabel_dict -): - scene = uai_format.Scene( - metadata=metadata_uai, - coordinate_systems={ - coordinate_system_camera_uai.uid: coordinate_system_camera_uai, - coordinate_system_lidar_uai.uid: coordinate_system_lidar_uai, - }, - frames={ - frame_uai.id: frame_uai - }, - ) - - assert scene.to_raillabel()["openlabel"]["frames"] == { - str(frame_uai.id): frame_raillabel_dict - } - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_segmentation_3d.py b/tests/test_raillabel/format/understand_ai/test_uai_segmentation_3d.py deleted file mode 100644 index 6462506..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_segmentation_3d.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path -from uuid import UUID - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id - -# == Fixtures ========================= - -@pytest.fixture -def segmentation_3d_uai_dict(sensor_lidar_uai_dict, attributes_uai_dict) -> dict: - return { - "id": "13478f94-d556-4f64-a72b-47662e94988e", - "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", - "className": "test_class", - "geometry": { - "associatedPoints": [39814, 39815, 39816, 39817, 39818], - "numberOfPointsInBox": 5 - }, - "attributes": attributes_uai_dict, - "sensor": sensor_lidar_uai_dict, - } - -@pytest.fixture -def segmentation_3d_uai(attributes_uai, sensor_lidar_uai) -> dict: - return uai_format.Segmentation3d( - id=UUID("13478f94-d556-4f64-a72b-47662e94988e"), - object_id=UUID("05a7e7a7-91e1-49ef-a172-780f2461f013"), - class_name="test_class", - associated_points=[39814, 39815, 39816, 39817, 39818], - number_of_points=5, - attributes=attributes_uai, - sensor=sensor_lidar_uai, - ) - -@pytest.fixture -def segmentation_3d_raillabel_dict(attributes_raillabel_dict, coordinate_system_lidar_translated_uid) -> dict: - return { - "name": "13478f94-d556-4f64-a72b-47662e94988e", - "val": [39814, 39815, 39816, 39817, 39818], - "coordinate_system": coordinate_system_lidar_translated_uid, - "attributes": attributes_raillabel_dict - } - -# == Tests ============================ - -def test_fromdict( - attributes_uai_dict, attributes_uai, - sensor_lidar_uai_dict, sensor_lidar_uai -): - segmentation_3d = uai_format.Segmentation3d.fromdict( - { - "id": "13478f94-d556-4f64-a72b-47662e94988e", - "objectId": "05a7e7a7-91e1-49ef-a172-780f2461f013", - "className": "test_class", - "geometry": { - "associatedPoints": [39814, 39815, 39816, 39817, 39818], - "numberOfPointsInBox": 5 - }, - "attributes": attributes_uai_dict, - "sensor": sensor_lidar_uai_dict, - } - ) - - assert segmentation_3d.id == UUID("13478f94-d556-4f64-a72b-47662e94988e") - assert segmentation_3d.object_id == UUID("05a7e7a7-91e1-49ef-a172-780f2461f013") - assert segmentation_3d.class_name == "test_class" - assert segmentation_3d.associated_points == [39814, 39815, 39816, 39817, 39818] - assert segmentation_3d.number_of_points == 5 - assert segmentation_3d.attributes == attributes_uai - assert segmentation_3d.sensor == sensor_lidar_uai - - -def test_to_raillabel( - attributes_uai, attributes_raillabel_dict, - sensor_lidar_uai, sensor_lidar_raillabel_dict, coordinate_system_lidar_translated_uid, -): - segmentation_3d = uai_format.Segmentation3d( - id=UUID("13478f94-d556-4f64-a72b-47662e94988e"), - object_id=UUID("05a7e7a7-91e1-49ef-a172-780f2461f013"), - class_name="test_class", - associated_points=[39814, 39815, 39816, 39817, 39818], - number_of_points=5, - attributes=attributes_uai, - sensor=sensor_lidar_uai, - ) - - data_dict, object_id, translated_class_id, sensor_reference = segmentation_3d.to_raillabel() - - assert data_dict == { - "name": "13478f94-d556-4f64-a72b-47662e94988e", - "val": [39814, 39815, 39816, 39817, 39818], - "coordinate_system": coordinate_system_lidar_translated_uid, - "attributes": attributes_raillabel_dict, - } - assert object_id == str(segmentation_3d.object_id) - assert translated_class_id == translate_class_id(segmentation_3d.class_name) - assert sensor_reference == sensor_lidar_raillabel_dict - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_sensor_reference.py b/tests/test_raillabel/format/understand_ai/test_uai_sensor_reference.py deleted file mode 100644 index 0fd1f54..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_sensor_reference.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from decimal import Decimal -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def sensor_camera_uai_dict() -> dict: - return { - "type": "ir_middle", - "uri": "A0001781_image/000_1632321843.100464760.png", - "timestamp": "1632321843.100464760" - } - -@pytest.fixture -def sensor_camera_uai() -> dict: - return uai_format.SensorReference( - type="ir_middle", - uri="A0001781_image/000_1632321843.100464760.png", - timestamp=Decimal("1632321843.100464760"), - ) - -@pytest.fixture -def sensor_camera_raillabel_dict() -> dict: - return { - "stream_properties": { - "sync": { - "timestamp": "1632321843.100464760" - } - }, - "uri": "000_1632321843.100464760.png" - } - - -@pytest.fixture -def sensor_lidar_uai_dict() -> dict: - return { - "type": "LIDAR", - "uri": "lidar_merged/000_1632321880.132833000.pcd", - "timestamp": "1632321880.132833000" - } - -@pytest.fixture -def sensor_lidar_uai() -> dict: - return uai_format.SensorReference( - type="LIDAR", - uri="lidar_merged/000_1632321880.132833000.pcd", - timestamp=Decimal("1632321880.132833000"), - ) - -@pytest.fixture -def sensor_lidar_raillabel_dict() -> dict: - return { - "stream_properties": { - "sync": { - "timestamp": "1632321880.132833000" - } - }, - "uri": "000_1632321880.132833000.pcd" - } - -# == Tests ============================ - -def test_fromdict(): - sensor_reference = uai_format.SensorReference.fromdict( - { - "type": "ir_middle", - "uri": "A0001781_image/000_1632321843.100464760.png", - "timestamp": "1632321843.100464760" - } - ) - - assert sensor_reference.type == "ir_middle" - assert sensor_reference.uri == "A0001781_image/000_1632321843.100464760.png" - assert sensor_reference.timestamp == Decimal("1632321843.100464760") - - -def test_to_raillabel(): - sensor_reference = uai_format.SensorReference( - type="ir_middle", - uri="A0001781_image/000_1632321843.100464760.png", - timestamp=Decimal("1632321843.100464760"), - ) - - assert sensor_reference.to_raillabel()[0] == "ir_middle" - assert sensor_reference.to_raillabel()[1] == { - "stream_properties": { - "sync": { - "timestamp": "1632321843.100464760" - } - }, - "uri": "000_1632321843.100464760.png" - } - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) diff --git a/tests/test_raillabel/format/understand_ai/test_uai_size_3d.py b/tests/test_raillabel/format/understand_ai/test_uai_size_3d.py deleted file mode 100644 index 0734b03..0000000 --- a/tests/test_raillabel/format/understand_ai/test_uai_size_3d.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel.format.understand_ai as uai_format - -# == Fixtures ========================= - -@pytest.fixture -def size_3d_uai_dict() -> dict: - return { - "width": 3, - "length": 4, - "height": 5, - } - -@pytest.fixture -def size_3d_uai() -> dict: - return uai_format.Size3d( - width=3, - length=4, - height=5, - ) - -@pytest.fixture -def size_3d_vec() -> dict: - return [3, 4, 5] - -# == Tests ============================ - -def test_fromdict(): - size_3d = uai_format.Size3d.fromdict( - { - "width": 3, - "length": 4, - "height": 5, - } - ) - - assert size_3d.width == 3 - assert size_3d.length == 4 - assert size_3d.height == 5 - - -if __name__ == "__main__": - import os - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/load_/loader_classes/test_loader_understand_ai.py b/tests/test_raillabel/load_/loader_classes/test_loader_understand_ai.py deleted file mode 100644 index 293e5f3..0000000 --- a/tests/test_raillabel/load_/loader_classes/test_loader_understand_ai.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel - - -@pytest.fixture -def loader(): - return raillabel.load_.loader_classes.LoaderUnderstandAi() - - -def test_supports_true(json_data, loader): - assert loader.supports(json_data["understand_ai_real_life"]) - - -def test_supports_false(json_data, loader): - data = json_data["understand_ai_real_life"] - del data["metadata"]["project_id"] - assert not loader.supports(data) - - -def test_load(json_data, loader): - input_data_raillabel = remove_non_parsed_fields(json_data["openlabel_v1_short"]) - input_data_uai = json_data["understand_ai_t4_short"] - - scene_ground_truth = raillabel.load_.loader_classes.LoaderRailLabel().load(input_data_raillabel, validate=False) - scene = loader.load(input_data_uai, validate=False) - - scene.metadata = scene_ground_truth.metadata - - assert scene.asdict() == scene_ground_truth.asdict() - -def remove_non_parsed_fields(raillabel_data: dict) -> dict: - """Return RailLabel file with frame_data and poly3ds removed.""" - - for frame in raillabel_data["openlabel"]["frames"].values(): - - if "frame_data" in frame["frame_properties"]: - del frame["frame_properties"]["frame_data"] - - for object_id, object in list(frame["objects"].items()): - if "poly3d" not in object["object_data"]: - continue - - del object["object_data"]["poly3d"] - if len(object["object_data"]) == 0: - del frame["objects"][object_id] - - return raillabel_data - - -def test_raillabel_loader_warnings(loader): - scene_dict = { - "metadata": { - "clip_id": "db_3_2021-09-22-14-28-01_2021-09-22-14-44-03", - "external_clip_id": "2021-09-22-14-28-01_2021-09-22-14-44-03", - "project_id": "trains_4", - "export_time": "2023-04-20 01:38 UTC", - "exporter_version": "1.0.0", - "coordinate_system_3d": "FLU", - "coordinate_system_reference": "SENSOR", - "folder_name": "2021-09-22-14-28-01_2021-09-22-14-44-03" - }, - "coordinateSystems": [], - "frames": [ - { - "frameId": "000", - "timestamp": "1632321743.134149", - "annotations": { - "2D_BOUNDING_BOX": [ - { - "id": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "objectId": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "className": "2D_person", - "geometry": { - "xMin": -1.0, - "yMin": -0.5, - "xMax": 1, - "yMax": 2.5 - }, - "attributes": {}, - "sensor": { - "type": "NON_EXISTENT_SENSOR", # <-- relevant line - "uri": "S1206063/rgb_test0.png", - "timestamp": "1632321743.100000072" - } - } - ], - "2D_POLYLINE": [], - "2D_POLYGON": [], - "3D_BOUNDING_BOX": [], - "3D_SEGMENTATION": [] - } - } - ] - } - - loader.load(scene_dict, validate=True) - - assert len(loader.warnings) == 2 - - assert "NON_EXISTENT_SENSOR" in loader.warnings[0] - assert "frame 0" in loader.warnings[0] - - assert "NON_EXISTENT_SENSOR" in loader.warnings[1] - assert "78f0ad89-2750-4a30-9d66-44c9da73a714" in loader.warnings[1] - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) diff --git a/tests/test_raillabel/load_/test_load.py b/tests/test_raillabel/load_/test_load.py index f929565..7466792 100644 --- a/tests/test_raillabel/load_/test_load.py +++ b/tests/test_raillabel/load_/test_load.py @@ -18,12 +18,6 @@ def test_load_raillabel(json_paths): assert len(scene.frames) != 0 -def test_load_uai(json_paths): - data_path = json_paths["understand_ai_t4_short"] - scene = raillabel.load(data_path) - assert len(scene.frames) != 0 - - # Executes the test if the file is called if __name__ == "__main__": os.system("clear") diff --git a/tests/test_raillabel/validate/schemas/test_understand_ai_t4_schema.py b/tests/test_raillabel/validate/schemas/test_understand_ai_t4_schema.py deleted file mode 100644 index 17af7dc..0000000 --- a/tests/test_raillabel/validate/schemas/test_understand_ai_t4_schema.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os - -import jsonschema -import pytest - - -def test_metaschema_validation(json_data): - assert jsonschema.validate( - json_data["understand_ai_t4_schema"], - json_data["metaschema"] - ) is None - - -def test_sample_data_validation_subschema(json_data): - assert jsonschema.validate( - json_data["understand_ai_real_life"], - json_data["understand_ai_t4_schema"] - ) is None - - assert jsonschema.validate( - json_data["understand_ai_t4_short"], - json_data["understand_ai_t4_schema"] - ) is None - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) From 8270c705183884b0dfcd93e67346d102ba1b8c25 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:07:53 +0100 Subject: [PATCH 003/190] refactor: remove loader classes --- raillabel/load_/load.py | 65 +------ raillabel/load_/loader_classes/__init__.py | 21 --- raillabel/load_/loader_classes/_loader_abc.py | 103 ----------- .../load_/loader_classes/loader_raillabel.py | 170 ------------------ .../__test_assets__/openlabel_v1_short.json5 | 3 +- tests/test_raillabel/filter/test_filter.py | 48 +++-- .../format/raillabel/test_scene.py | 10 +- .../loader_classes/test_loader_raillabel.py | 106 ----------- tests/test_raillabel/load_/test_load.py | 13 +- tests/test_raillabel/save/test_save.py | 9 +- 10 files changed, 46 insertions(+), 502 deletions(-) delete mode 100644 raillabel/load_/loader_classes/__init__.py delete mode 100644 raillabel/load_/loader_classes/_loader_abc.py delete mode 100644 raillabel/load_/loader_classes/loader_raillabel.py delete mode 100644 tests/test_raillabel/load_/loader_classes/test_loader_raillabel.py diff --git a/raillabel/load_/load.py b/raillabel/load_/load.py index 1f74990..c8f4d4d 100644 --- a/raillabel/load_/load.py +++ b/raillabel/load_/load.py @@ -3,10 +3,7 @@ import json -from ..exceptions import UnsupportedFormatError from ..format import Scene -from . import loader_classes as loader_classes_pkg -from .loader_classes._loader_abc import LoaderABC def load(path: str, validate: bool = False, show_warnings: bool = True) -> Scene: @@ -16,69 +13,13 @@ def load(path: str, validate: bool = False, show_warnings: bool = True) -> Scene ---------- path: str Path to the annotation file. - validate: bool, optional - If True, the annotation data is validated via the respective schema. This is highly - recommended, as not validating the data may lead to errors during loading or while handling - the scene. However, validating may increase the loading time. Default is False. - show_warnings: bool, optional - If True, any non-critical inconsistencies in the data are output as a warning. Default is - True. Returns ------- scene: raillabel.Scene Scene with the loaded data. - - Raises - ------ - raillabel.UnsupportedFormatError - if the annotation file does not match any loaders. - raillabel.exceptions.SchemaError - if during the validation, errors in the annotation file are found. """ + with path.open() as scene_file: + raw_scene = json.load(scene_file) - # To expand the supported formats in a simple manner, load() automatically fetches all classes - # in the loader_classes directory and checks if they are suitable as loaders. To avoid errors - # caused by potential other files and classes in the directory, only classes, which inherite - # from LoaderABC are considered. - - loader_classes = [] - for cls in loader_classes_pkg.__dict__.values(): - if isinstance(cls, type) and issubclass(cls, LoaderABC) and cls != LoaderABC: - loader_classes.append(cls) - - # Checks for the supported file type - if not str(path).lower().endswith(".json"): - raise UnsupportedFormatError(f"{path} is not in a supported file format.") - - # Loads the JSON data - with open(path) as data_file: - data = json.load(data_file) - - # Iterates over the loader classes to find a suitable one - for loader_class in loader_classes: - - loader = loader_class() - - # Checks if the loader supports the data - if not loader.supports(data): - continue - - # Loads the scene - scene = loader.load(data, validate=validate) - - # Outputs the warnings - if show_warnings: - - if len(loader.warnings) > 0: - print(f"During the loading of {path} warnings have occurred:") - - for warning in loader.warnings: - print(" - " + warning) - - return scene - - # This part of the code is only reached if no suitable loader has been found or the data is - # not in a supported file format. Therefore an exception is raised. - - raise UnsupportedFormatError(f"{path} is not in a supported file format.") + return Scene.fromdict(raw_scene) diff --git a/raillabel/load_/loader_classes/__init__.py b/raillabel/load_/loader_classes/__init__.py deleted file mode 100644 index 190e8a8..0000000 --- a/raillabel/load_/loader_classes/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Package containing the loader classes for all supported formats.""" - -from importlib import import_module -from inspect import isclass -from pathlib import Path -from pkgutil import iter_modules - -# iterate through the modules in the current package -package_dir = str(Path(__file__).resolve().parent) -for _, module_name, _ in iter_modules([package_dir]): - - # import the module and iterate through its attributes - module = import_module(f"{__name__}.{module_name}") - for attribute_name in dir(module): - attribute = getattr(module, attribute_name) - - if isclass(attribute): - # Add the class to this package's variables - globals()[attribute_name] = attribute diff --git a/raillabel/load_/loader_classes/_loader_abc.py b/raillabel/load_/loader_classes/_loader_abc.py deleted file mode 100644 index 446bfc4..0000000 --- a/raillabel/load_/loader_classes/_loader_abc.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from abc import ABC, abstractmethod -from pathlib import Path - -from ... import format -from ...exceptions import SchemaError -from ...validate.validate import validate as global_validate - - -class LoaderABC(ABC): - """Abstract base class of the annotation file loaders. - - For every annotation format, that can be loaded via raillabel, a loader class should exists, - that inherites from this class. - - Attributes - ---------- - scene: raillabel.Scene - Loaded raillabel.Scene with the data. - warnings: t.List[str] - List of warning strings, that have been found during the execution of load(). - SCHEMA_PATH: Path - Absolute path to the JSON schema. - """ - - scene: format.Scene - warnings: t.List[str] - SCHEMA_PATH: Path - - @abstractmethod - def load(self, data: dict, validate: bool = True) -> format.Scene: - """Load JSON-data into a raillabel.Scene. - - Any non-critical errors are stored in the warnings-property. - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - validate: bool - If True, the annotation data is validated via the respective schema. This is highly - recommended, as not validating the data may lead to Errors during loading or while - handling the scene. However, validating may increase the loading time. Default is True. - - Returns - ------- - scene: raillabel.Scene - The loaded scene with the data. - - Raises - ------ - raillabel.exceptions.SchemaError - if validate is True and the data does not validate against the schema. - """ - raise NotImplementedError - - @abstractmethod - def supports(self, data: dict) -> bool: - """Determine if the loader class is suitable for the data. - - This is performed based on hints in the data structure and can therefore be done - efficiently. - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - - Returns - ------- - bool: - If True, the Loader class is suitable for the data. - """ - raise NotImplementedError - - def validate(self, data: dict) -> t.Tuple[bool, t.List[str]]: - """Validate JSON-data with the corresponding schema. - - Parameters - ---------- - data: dict - JSON data to be validated. - - Raises - ------ - raillabel.exceptions.SchemaError - if the schema is not valid. - """ - - is_data_valid, schema_errors = global_validate(data, str(self.SCHEMA_PATH)) - if not is_data_valid: - - error_msg = ( - "The loaded data does not validate against the schema. Errors in the schema:\n" - ) - - for err in schema_errors: - error_msg += " - " + err + "\n" - - raise SchemaError(error_msg) diff --git a/raillabel/load_/loader_classes/loader_raillabel.py b/raillabel/load_/loader_classes/loader_raillabel.py deleted file mode 100644 index 399089c..0000000 --- a/raillabel/load_/loader_classes/loader_raillabel.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import json -import typing as t -from pathlib import Path - -from ... import exceptions, format -from ..._util._warning import _WarningsLogger -from ._loader_abc import LoaderABC - - -class LoaderRailLabel(LoaderABC): - """Loader class for the OpenLabel v1 annotation format. - - Attributes - ---------- - scene: raillabel.Scene - Loaded raillabel.Scene with the data. - warnings: t.List[str] - List of warning strings, that have been found during the execution of load(). - """ - - scene: format.Scene - warnings: t.List[str] - - SCHEMA_PATH: Path = ( - Path(__file__).parent.parent.parent / "validate" / "schemas" / "raillabel_schema.json" - ) - - @property - def subschema_version(self): - """Return subschema version.""" - with self.SCHEMA_PATH.open() as schema_file: - subschema_version = json.load(schema_file)["version"] - return subschema_version - - def load(self, data: dict, validate: bool = False) -> format.Scene: - """Load the data into a raillabel.Scene and return it. - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - validate: bool, optional - If True, the annotation data is validated via the respective schema. This is - recommended, if you are working with a modified or non-official file. Setting this - option will increase loading time. Default is False. - - Returns - ------- - scene: raillabel.Scene - The loaded scene with the data. - - Raises - ------ - raillabel.exceptions.SchemaError - if validate is True and the data does not validate against the schema. - """ - - if validate: - self.validate(data) - - with _WarningsLogger() as logger: - self.scene = format.Scene.fromdict(data, self.subschema_version) - - self.warnings = logger.warnings - - return self.scene - - def supports(self, data: dict) -> bool: - """Determine if the loader is suitable for the data (lightweight). - - Parameters - ---------- - data: dict - A dictionary loaded from a JSON-file. - - Returns - ------- - bool: - If True, the Loader class is suitable for the data. - """ - - if "openlabel" not in data or "metadata" not in data["openlabel"]: - return False - - if "subschema_version" in data["openlabel"]["metadata"]: - return ( - "openlabel" in data - and "metadata" in data["openlabel"] - and "schema_version" in data["openlabel"]["metadata"] - and data["openlabel"]["metadata"]["subschema_version"].split(".")[0] in ["2", "3"] - ) - - else: - return ( - "openlabel" in data - and "metadata" in data["openlabel"] - and "schema_version" in data["openlabel"]["metadata"] - ) - - def _prepare_data(self, data: dict) -> dict: - """Add optional fields to dict to simplify interaction. - - Parameters - ---------- - data : dict - JSON data. - - Returns - ------- - dict - Enhanced JSON data. - """ - - if "coordinate_systems" not in data["openlabel"]: - data["openlabel"]["coordinate_systems"] = {} - - if "streams" not in data["openlabel"]: - data["openlabel"]["streams"] = {} - - if "objects" not in data["openlabel"]: - data["openlabel"]["objects"] = {} - - if "frames" not in data["openlabel"]: - data["openlabel"]["frames"] = {} - - return data["openlabel"] - - def _check_sensor_completeness(self, cs_data: dict, stream_data: dict): - """Check for corresponding cs and stream completeness. - - Parameters - ---------- - cs_data : dict - Coordinate system data in the RailLabel format. - stream_data : dict - Stream data in the RailLabel format. - - Raises - ------ - raillabel.exceptions.MissingCoordinateSystemError - if a stream has no corresponding coordinate system. - raillabel.exceptions.MissingStreamError - if a coordinate system has no corresponding stream. - raillabel.exceptions.UnsupportedParentError - if a coordinate system has no corresponding stream. - """ - - for stream_uid in stream_data: - if stream_uid not in cs_data: - raise exceptions.MissingCoordinateSystemError( - f"Stream {stream_uid} has no corresponding coordinate system." - ) - - for cs_uid in cs_data: - if cs_uid == "base": - continue - - if cs_data[cs_uid]["parent"] != "base": - raise exceptions.UnsupportedParentError( - f"Only 'base' is permitted as a parent for coordinate system {cs_uid}, " - + f"not {cs_data[cs_uid]['parent']}." - ) - - if cs_uid not in stream_data: - raise exceptions.MissingStreamError( - f"Coordinate sytem {cs_uid} has no corresponding stream." - ) diff --git a/tests/__test_assets__/openlabel_v1_short.json5 b/tests/__test_assets__/openlabel_v1_short.json5 index 7592e33..a8f4186 100644 --- a/tests/__test_assets__/openlabel_v1_short.json5 +++ b/tests/__test_assets__/openlabel_v1_short.json5 @@ -5,8 +5,7 @@ "schema_version": "1.0.0", "comment": "test_comment", "name": "test_project", - "subschema_version": "3.0.0", - "tagged_file": "test_folder" + "tagged_file": "test_folder", }, "streams": { "rgb_middle": { diff --git a/tests/test_raillabel/filter/test_filter.py b/tests/test_raillabel/filter/test_filter.py index 2ddf213..1299b7f 100644 --- a/tests/test_raillabel/filter/test_filter.py +++ b/tests/test_raillabel/filter/test_filter.py @@ -29,10 +29,6 @@ def delete_sensor_from_data(data: dict, sensor_id: str) -> dict: return data -@pytest.fixture -def loader(): - return raillabel.load_.loader_classes.LoaderRailLabel() - def test_filter_unexpected_kwarg(json_paths): # Loads scene @@ -50,7 +46,7 @@ def test_mutual_exclusivity(json_paths): raillabel.filter(scene, include_frames=[0], exclude_frames=[1, 2]) -def test_filter_frames(json_paths, json_data, loader): +def test_filter_frames(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -62,7 +58,7 @@ def test_filter_frames(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter(scene, include_frames=[0]) @@ -73,7 +69,7 @@ def test_filter_frames(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_start(json_paths, json_data, loader): +def test_filter_start(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -85,7 +81,7 @@ def test_filter_start(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for frame filter scene_filtered = raillabel.filter(scene, start_frame=1) @@ -96,7 +92,7 @@ def test_filter_start(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_end(json_paths, json_data, loader): +def test_filter_end(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -108,7 +104,7 @@ def test_filter_end(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for frame filter scene_filtered = raillabel.filter(scene, end_frame=0) @@ -119,7 +115,7 @@ def test_filter_end(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_object_ids(json_paths, json_data, loader): +def test_filter_object_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -136,7 +132,7 @@ def test_filter_object_ids(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter( @@ -156,7 +152,7 @@ def test_filter_object_ids(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_object_types(json_paths, json_data, loader): +def test_filter_object_types(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -173,7 +169,7 @@ def test_filter_object_types(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter(scene, include_object_types=["person"]) @@ -184,7 +180,7 @@ def test_filter_object_types(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_annotation_ids(json_paths, json_data, loader): +def test_filter_annotation_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -201,7 +197,7 @@ def test_filter_annotation_ids(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter( @@ -237,7 +233,7 @@ def test_filter_annotation_ids(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_annotation_types(json_paths, json_data, loader): +def test_filter_annotation_types(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -262,7 +258,7 @@ def test_filter_annotation_types(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter(scene, include_annotation_types=["bbox", "poly2d", "Num"]) @@ -274,7 +270,7 @@ def test_filter_annotation_types(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_sensors(json_paths, json_data, loader): +def test_filter_sensors(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -307,7 +303,7 @@ def test_filter_sensors(json_paths, json_data, loader): ] # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter(scene, include_sensors=["rgb_middle", "ir_middle"]) @@ -319,7 +315,7 @@ def test_filter_sensors(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_include_attribute_ids(json_paths, json_data, loader): +def test_filter_include_attribute_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -356,7 +352,7 @@ def test_filter_include_attribute_ids(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for include filter scene_filtered = raillabel.filter(scene, include_attributes={"test_text_attr0": None}) @@ -364,7 +360,7 @@ def test_filter_include_attribute_ids(json_paths, json_data, loader): assert scene_filtered == scene_filtered_ground_truth -def test_filter_exclude_attribute_ids(json_paths, json_data, loader): +def test_filter_exclude_attribute_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -399,14 +395,14 @@ def test_filter_exclude_attribute_ids(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for exclude filter scene_filtered = raillabel.filter(scene, exclude_attributes={"test_text_attr0": None}) assert scene_filtered == scene_filtered_ground_truth -def test_filter_exclude_attribute_values(json_paths, json_data, loader): +def test_filter_exclude_attribute_values(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene @@ -419,7 +415,7 @@ def test_filter_exclude_attribute_values(json_paths, json_data, loader): data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data - scene_filtered_ground_truth = loader.load(data) + scene_filtered_ground_truth = raillabel.Scene.fromdict(data) # Tests for exclude filter scene_filtered = raillabel.filter(scene, exclude_attributes={"test_num_attr0": 2}) diff --git a/tests/test_raillabel/format/raillabel/test_scene.py b/tests/test_raillabel/format/raillabel/test_scene.py index 3854449..1a822bf 100644 --- a/tests/test_raillabel/format/raillabel/test_scene.py +++ b/tests/test_raillabel/format/raillabel/test_scene.py @@ -405,7 +405,15 @@ def test_frame_intervals(metadata_minimal): FrameInterval(8, 8), ] +def test_integration(json_data): + scene_dict = json_data["openlabel_v1_short"] + + actual = Scene.fromdict(scene_dict).asdict() + + del actual["openlabel"]["metadata"]["exporter_version"] + assert actual == scene_dict + if __name__ == "__main__": os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) diff --git a/tests/test_raillabel/load_/loader_classes/test_loader_raillabel.py b/tests/test_raillabel/load_/loader_classes/test_loader_raillabel.py deleted file mode 100644 index 01423a4..0000000 --- a/tests/test_raillabel/load_/loader_classes/test_loader_raillabel.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel - - -@pytest.fixture -def loader(): - return raillabel.load_.loader_classes.LoaderRailLabel() - - -def test_supports_true(json_data, loader): - assert loader.supports(json_data["openlabel_v1_short"]) - - -def test_supports_false(json_data, loader): - data = json_data["openlabel_v1_short"] - data["openlabel"]["metadata"]["subschema_version"] = "4.0.0" - assert not loader.supports(data) - - -# Tests the warnings and errors -def test_no_warnings(json_data, loader): - loader.load(json_data["openlabel_v1_short"], validate=False) - assert len(loader.warnings) == 0 - - -def test_warnings_sync(json_data, loader): - data = json_data["openlabel_v1_short"] - - data["openlabel"]["frames"]["0"]["frame_properties"]["streams"]["non_existing_stream"] = { - "stream_properties": { - "sync": { - "timestamp": "1632321743.100000072" - } - } - } - - loader.load(data, validate=False) - assert len(loader.warnings) == 1 - - # Tests for keywords in the warning that can help the user identify the source - assert "frame" in loader.warnings[0] - assert "0" in loader.warnings[0] - assert "sync" in loader.warnings[0] - assert "non_existing_stream" in loader.warnings[0] - - -def test_warnings_stream_sync_field(json_data, loader): - data = json_data["openlabel_v1_short"] - - data["openlabel"]["frames"]["0"]["frame_properties"]["streams"][ - "rgb_middle" - ]["stream_properties"]["stream_sync"] = data["openlabel"]["frames"]["0"][ - "frame_properties" - ][ - "streams" - ][ - "rgb_middle" - ][ - "stream_properties" - ][ - "sync" - ] - del data["openlabel"]["frames"]["0"]["frame_properties"]["streams"][ - "rgb_middle" - ]["stream_properties"]["sync"] - - loader.load(data, validate=False) - assert len(loader.warnings) == 1 - - # Tests for keywords in the warning that can help the user identify the source - assert "stream_sync" in loader.warnings[0] - assert "deprecated" in loader.warnings[0].lower() - assert "save()" in loader.warnings[0] - - -def test_identify_of_references(json_data, loader): - data = json_data["openlabel_v1_short"] - - scene = loader.load(data, validate=False) - - for frame in scene.frames.values(): - - for sensor_reference in frame.sensors.values(): - assert sensor_reference.sensor is scene.sensors[sensor_reference.sensor.uid] - - for frame_data in frame.frame_data.values(): - assert frame_data.sensor is scene.sensors[frame_data.sensor.uid] - - for annotation in frame.annotations.values(): - assert annotation.sensor is scene.sensors[annotation.sensor.uid] - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) diff --git a/tests/test_raillabel/load_/test_load.py b/tests/test_raillabel/load_/test_load.py index 7466792..7c95c19 100644 --- a/tests/test_raillabel/load_/test_load.py +++ b/tests/test_raillabel/load_/test_load.py @@ -2,20 +2,17 @@ # SPDX-License-Identifier: Apache-2.0 import os -import sys -from pathlib import Path import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - import raillabel -def test_load_raillabel(json_paths): - data_path = json_paths["openlabel_v1_short"] - scene = raillabel.load(data_path) - assert len(scene.frames) != 0 +def test_load__contains_all_elements(json_paths): + actual = raillabel.load(json_paths["openlabel_v1_short"]) + assert len(actual.sensors) == 4 + assert len(actual.objects) == 3 + assert len(actual.frames) == 2 # Executes the test if the file is called diff --git a/tests/test_raillabel/save/test_save.py b/tests/test_raillabel/save/test_save.py index 93bb32c..235bf17 100644 --- a/tests/test_raillabel/save/test_save.py +++ b/tests/test_raillabel/save/test_save.py @@ -45,10 +45,13 @@ def test_save_json(json_data): with (Path(temp_dir) / "test_save_file.json").open() as f: saved_and_loaded_data = json.load(f) - # Removes the exporter version from the generated file as these are hard to test for + # Removes the exporter version and subschema version from the generated file as these are hard to test for if "exporter_version" in saved_and_loaded_data["openlabel"]["metadata"]: del saved_and_loaded_data["openlabel"]["metadata"]["exporter_version"] + if "subschema_version" in saved_and_loaded_data["openlabel"]["metadata"]: + del saved_and_loaded_data["openlabel"]["metadata"]["subschema_version"] + assert saved_and_loaded_data == json_data["openlabel_v1_short"] @@ -67,7 +70,7 @@ def test_frame_intervals(): } } - scene = raillabel.load_.loader_classes.LoaderRailLabel().load(data) + scene = raillabel.Scene.fromdict(data) dict_repr = scene.asdict()["openlabel"] assert "frame_intervals" in dict_repr @@ -81,4 +84,4 @@ def test_frame_intervals(): # Executes the test if the file is called if __name__ == "__main__": os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) From bb0171639bdaf06beeb94c05d9f7d0eb444018af Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:09:23 +0100 Subject: [PATCH 004/190] refactor: remove unused exceptions --- raillabel/exceptions.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/raillabel/exceptions.py b/raillabel/exceptions.py index 5be884b..99515bd 100644 --- a/raillabel/exceptions.py +++ b/raillabel/exceptions.py @@ -2,27 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 -class UnsupportedFormatError(Exception): - """Raised when a loaded annotation file is not in a supported format.""" - - __module__ = "raillabel" - - -class SchemaError(Exception): - """Raised when the data does not validate against a given schema.""" - - __module__ = "raillabel" - - -class AmbiguousSchemaNameError(Exception): - """Raised when a schema key applies to more than one schema files in. - - /schemas. - """ - - __module__ = "raillabel" - - class MissingStreamError(Exception): """Raised when a coordinate system has no corresponding stream.""" From 4675701265c70ee80e5d58e084fd56b350cd980f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:10:44 +0100 Subject: [PATCH 005/190] refactor: load package --- raillabel/__init__.py | 2 +- raillabel/{load_ => load}/load.py | 0 raillabel/load_/__init__.py | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) rename raillabel/{load_ => load}/load.py (100%) delete mode 100644 raillabel/load_/__init__.py diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 7b613eb..8058cd0 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -7,7 +7,7 @@ from .exceptions import * from .filter.filter import filter from .format import Scene -from .load_.load import load +from .load.load import load from .save.save import save from .validate.validate import validate diff --git a/raillabel/load_/load.py b/raillabel/load/load.py similarity index 100% rename from raillabel/load_/load.py rename to raillabel/load/load.py diff --git a/raillabel/load_/__init__.py b/raillabel/load_/__init__.py deleted file mode 100644 index cb4a105..0000000 --- a/raillabel/load_/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Package containing all necessary modules for loading.""" From b7200353b2f52264cd0d1d0451271cd54e72961f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:13:53 +0100 Subject: [PATCH 006/190] refactor: move _clean_dict() to scene --- raillabel/_util/_clean_dict.py | 19 ------------ raillabel/format/raillabel/scene.py | 19 +++++++++++- tests/test_raillabel/_util/test_clean_dict.py | 29 ------------------- .../format/raillabel/test_scene.py | 13 +++++++++ 4 files changed, 31 insertions(+), 49 deletions(-) delete mode 100644 raillabel/_util/_clean_dict.py delete mode 100644 tests/test_raillabel/_util/test_clean_dict.py diff --git a/raillabel/_util/_clean_dict.py b/raillabel/_util/_clean_dict.py deleted file mode 100644 index 77d5444..0000000 --- a/raillabel/_util/_clean_dict.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - - -def _clean_dict(input_dict: dict) -> dict: - """Remove all fields from a dict that are None or have a length of 0.""" - - empty_keys = [] - for key, value in input_dict.items(): - - is_field_empty = value is None or (hasattr(value, "__len__") and len(value) == 0) - - if is_field_empty: - empty_keys.append(key) - - for key in empty_keys: - del input_dict[key] - - return input_dict diff --git a/raillabel/format/raillabel/scene.py b/raillabel/format/raillabel/scene.py index d3c7fcb..a184f47 100644 --- a/raillabel/format/raillabel/scene.py +++ b/raillabel/format/raillabel/scene.py @@ -5,7 +5,6 @@ from dataclasses import dataclass, field from ... import exceptions -from ..._util._clean_dict import _clean_dict from ..._util._warning import _warning from .frame import Frame from .frame_interval import FrameInterval @@ -244,3 +243,21 @@ def _frames_asdict(self, frames: t.Dict[int, Frame]) -> dict: def _frame_intervals_asdict(self, frame_intervals: t.List[FrameInterval]) -> t.List[dict]: return [fi.asdict() for fi in frame_intervals] + + + +def _clean_dict(input_dict: dict) -> dict: + """Remove all fields from a dict that are None or have a length of 0.""" + + empty_keys = [] + for key, value in input_dict.items(): + + is_field_empty = value is None or (hasattr(value, "__len__") and len(value) == 0) + + if is_field_empty: + empty_keys.append(key) + + for key in empty_keys: + del input_dict[key] + + return input_dict \ No newline at end of file diff --git a/tests/test_raillabel/_util/test_clean_dict.py b/tests/test_raillabel/_util/test_clean_dict.py deleted file mode 100644 index cd13563..0000000 --- a/tests/test_raillabel/_util/test_clean_dict.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - -from raillabel._util._clean_dict import _clean_dict - - -def test_clean_dict(): - input_dict = { - "non_empty_field": "non_empty_value", - "none_field": None, - "field_with_len_0": [] - } - - assert _clean_dict(input_dict) == { - "non_empty_field": "non_empty_value", - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/raillabel/test_scene.py b/tests/test_raillabel/format/raillabel/test_scene.py index 1a822bf..c552302 100644 --- a/tests/test_raillabel/format/raillabel/test_scene.py +++ b/tests/test_raillabel/format/raillabel/test_scene.py @@ -12,6 +12,7 @@ from raillabel import exceptions from raillabel._util._warning import _WarningsLogger from raillabel.format import Frame, FrameInterval, Scene +from raillabel.format.raillabel.scene import _clean_dict # == Fixtures ========================= @@ -412,6 +413,18 @@ def test_integration(json_data): del actual["openlabel"]["metadata"]["exporter_version"] assert actual == scene_dict + + +def test_clean_dict(): + input_dict = { + "non_empty_field": "non_empty_value", + "none_field": None, + "field_with_len_0": [] + } + + assert _clean_dict(input_dict) == { + "non_empty_field": "non_empty_value", + } if __name__ == "__main__": From 20c2cabc7fbfc45e72ec7374722501f7089f3f02 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:36:31 +0100 Subject: [PATCH 007/190] refactor: remove scene loader warnings --- raillabel/_util/_warning.py | 37 ---------- .../format/raillabel/_object_annotation.py | 8 --- raillabel/format/raillabel/frame.py | 65 ----------------- raillabel/format/raillabel/num.py | 8 --- raillabel/format/raillabel/scene.py | 12 +--- .../format/raillabel/sensor_reference.py | 9 --- raillabel/load/load.py | 2 +- tests/test_raillabel/_util/test_warning.py | 40 ----------- tests/test_raillabel/filter/test_filter.py | 26 +++---- .../format/raillabel/test_bbox.py | 26 ------- .../format/raillabel/test_frame.py | 69 ------------------- .../format/raillabel/test_scene.py | 19 ----- tests/test_raillabel/save/test_save.py | 6 +- 13 files changed, 18 insertions(+), 309 deletions(-) delete mode 100644 raillabel/_util/_warning.py delete mode 100644 tests/test_raillabel/_util/test_warning.py diff --git a/raillabel/_util/_warning.py b/raillabel/_util/_warning.py deleted file mode 100644 index 828b1b3..0000000 --- a/raillabel/_util/_warning.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import logging -import typing as t -from io import StringIO - - -class _WarningsLogger: - - warnings: t.List[str] = [] - - def __enter__(self) -> "_WarningsLogger": - logger = logging.getLogger("loader_warnings") - warnings_stream = StringIO() - handler = logging.StreamHandler(warnings_stream) - handler.setLevel(logging.WARNING) - logger.addHandler(handler) - - return self - - def __exit__(self, exc_type, exc_value, traceback): - logger = logging.getLogger("loader_warnings") - stream = logger.handlers[-1].stream - stream.seek(0) - - warnings_list = stream.getvalue().split("\n") - - if len(warnings_list) > 0: - warnings_list = warnings_list[:-1] - - self.warnings = warnings_list - - -def _warning(message: str) -> logging.Logger: - """Create a loader warning.""" - logging.getLogger("loader_warnings").warning(message) diff --git a/raillabel/format/raillabel/_object_annotation.py b/raillabel/format/raillabel/_object_annotation.py index ec49797..941ebdd 100644 --- a/raillabel/format/raillabel/_object_annotation.py +++ b/raillabel/format/raillabel/_object_annotation.py @@ -10,7 +10,6 @@ from pkgutil import iter_modules from ..._util._attribute_type import AttributeType -from ..._util._warning import _warning from .object import Object from .sensor import Sensor @@ -105,13 +104,6 @@ def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Option if not is_coordinate_system_in_data: return None - if data_dict["coordinate_system"] not in sensors: - _warning( - f"'{data_dict['coordinate_system']}' does not exist as a sensor, " - + f"but is referenced for the annotation {data_dict['uid']}." - ) - return None - return sensors[data_dict["coordinate_system"]] @classmethod diff --git a/raillabel/format/raillabel/frame.py b/raillabel/format/raillabel/frame.py index 575dd1f..ebf0bee 100644 --- a/raillabel/format/raillabel/frame.py +++ b/raillabel/format/raillabel/frame.py @@ -6,7 +6,6 @@ import uuid from dataclasses import dataclass, field -from ..._util._warning import _warning from ._object_annotation import _ObjectAnnotation, annotation_classes from .num import Num from .object import Object @@ -93,7 +92,6 @@ def fromdict( frame: raillabel.format.Frame Converted Frame object. """ - frame = Frame( uid=int(uid), timestamp=cls._timestamp_fromdict(data_dict), @@ -101,8 +99,6 @@ def fromdict( frame_data=cls._frame_data_fromdict(data_dict, sensors), annotations=cls._objects_fromdict(data_dict, int(uid), objects, sensors), ) - - frame = cls._fix_sensor_uri_attribute(frame) return frame def asdict(self) -> dict: @@ -161,13 +157,6 @@ def _sensors_fromdict( sensors = {} for sensor_id, sensor_dict in data_dict["frame_properties"]["streams"].items(): - if sensor_id not in scene_sensors: - _warning( - f"{sensor_id} does not exist as a stream, but is referenced in the " - + f"sync of frame {frame_uid}." - ) - continue - sensors[sensor_id] = SensorReference.fromdict( data_dict=sensor_dict, sensor=scene_sensors[sensor_id] ) @@ -204,14 +193,6 @@ def _objects_fromdict( annotations = {} for obj_id, obj_ann in data_dict["objects"].items(): - - if obj_id not in objects: - _warning( - f"{obj_id} does not exist as an object, but is referenced in the object" - + f" annotation of frame {frame_id}." - ) - continue - object_annotations = cls._object_annotations_fromdict( data_dict=obj_ann["object_data"], object=objects[obj_id], @@ -219,10 +200,6 @@ def _objects_fromdict( ) for annotation in object_annotations: - if annotation.uid in annotations: - cls._issue_duplicate_annotation_uid_warning(annotation.uid, frame_id) - annotation.uid = str(uuid.uuid4()) - annotations[annotation.uid] = annotation return annotations @@ -237,50 +214,8 @@ def _object_annotations_fromdict( for ann_type, annotations_raw in data_dict.items(): for ann_raw in annotations_raw: - - ann_raw = cls._fix_deprecated_annotation_name(ann_raw, ann_type, object.type) - yield annotation_classes()[ann_type].fromdict(ann_raw, sensors, object) - @classmethod - def _fix_sensor_uri_attribute(cls, frame: "Frame") -> "Frame": - - for ann_id, ann in list(frame.annotations.items()): - for attr_name, attr_val in ann.attributes.items(): - - if attr_name != "uri": - continue - - _warning( - f"Deprecated attribute 'uri' detected in annotation {ann_id}. The error has" - + " been fixed. Please update the file via 'raillabel.save()'." - ) - - frame.sensors[ann.sensor.uid].uri = attr_val - del frame.annotations[ann_id].attributes[attr_name] - break - - return frame - - @classmethod - def _fix_deprecated_annotation_name(cls, ann_raw: dict, ann_type: str, obj_type: str) -> dict: - - if "uid" not in ann_raw: - try: - ann_raw["uid"] = str(uuid.UUID(ann_raw["name"])) - except ValueError: - ann_raw["uid"] = str(uuid.uuid4()) - - ann_raw["name"] = f"{ann_raw['coordinate_system']}__{ann_type}__{obj_type}" - - return ann_raw - - @classmethod - def _issue_duplicate_annotation_uid_warning(cls, ann_uid: str, frame_id: int): - _warning( - f"Annotation UID '{ann_uid}' is contained more than once in frame {frame_id}. " - + "A new uid will be assigned." - ) def _annotations_asdict(self) -> dict: annotations_dict = {} diff --git a/raillabel/format/raillabel/num.py b/raillabel/format/raillabel/num.py index 509d938..c9a5dc4 100644 --- a/raillabel/format/raillabel/num.py +++ b/raillabel/format/raillabel/num.py @@ -4,7 +4,6 @@ import typing as t from dataclasses import dataclass -from ..._util._warning import _warning from .sensor import Sensor @@ -87,11 +86,4 @@ def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Option if not is_coordinate_system_in_data: return None - if data_dict["coordinate_system"] not in sensors: - _warning( - f"'{data_dict['coordinate_system']}' does not exist as a sensor, " - + f"but is referenced for the annotation {data_dict['uid']}." - ) - return None - return sensors[data_dict["coordinate_system"]] diff --git a/raillabel/format/raillabel/scene.py b/raillabel/format/raillabel/scene.py index a184f47..8402230 100644 --- a/raillabel/format/raillabel/scene.py +++ b/raillabel/format/raillabel/scene.py @@ -5,7 +5,6 @@ from dataclasses import dataclass, field from ... import exceptions -from ..._util._warning import _warning from .frame import Frame from .frame_interval import FrameInterval from .metadata import Metadata @@ -200,16 +199,7 @@ def _frames_fromdict( frames = {} for frame_uid, frame_dict in frames_dict.items(): - frame_uid = int(frame_uid) - - if frame_uid in frames: - _warning( - f"Frame UID {frame_uid} is contained more than once in the scene. " - + "The duplicate frame will be omitted." - ) - continue - - frames[frame_uid] = Frame.fromdict(frame_uid, frame_dict, objects, sensors) + frames[int(frame_uid)] = Frame.fromdict(frame_uid, frame_dict, objects, sensors) return frames diff --git a/raillabel/format/raillabel/sensor_reference.py b/raillabel/format/raillabel/sensor_reference.py index 6f7ce70..da693e3 100644 --- a/raillabel/format/raillabel/sensor_reference.py +++ b/raillabel/format/raillabel/sensor_reference.py @@ -5,7 +5,6 @@ import typing as t from dataclasses import dataclass -from ..._util._warning import _warning from .sensor import Sensor @@ -75,12 +74,4 @@ def asdict(self) -> dict: @classmethod def _timestamp_fromdict(cls, data_dict: dict) -> decimal.Decimal: - - if "stream_sync" in data_dict: - _warning( - "Deprecated field 'stream_sync' identified. " - + "Please update file with raillabel.save()." - ) - return decimal.Decimal(data_dict["stream_sync"]["timestamp"]) - return decimal.Decimal(data_dict["sync"]["timestamp"]) diff --git a/raillabel/load/load.py b/raillabel/load/load.py index c8f4d4d..96b574c 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -6,7 +6,7 @@ from ..format import Scene -def load(path: str, validate: bool = False, show_warnings: bool = True) -> Scene: +def load(path: str) -> Scene: """Load an annotation file of any supported type. Parameters diff --git a/tests/test_raillabel/_util/test_warning.py b/tests/test_raillabel/_util/test_warning.py deleted file mode 100644 index 2fadae1..0000000 --- a/tests/test_raillabel/_util/test_warning.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - -from raillabel._util._warning import _warning, _WarningsLogger - - -def test_issue_warning(): - with _WarningsLogger() as logger: - _warning("lorem ipsum") - - assert logger.warnings == [ - "lorem ipsum" - ] - -def test_handover_exception(): - with pytest.raises(RuntimeError) as error: - with _WarningsLogger() as logger: - raise RuntimeError("weewoo something went wrong") - -def test_clear_warnings(): - with _WarningsLogger() as logger1: - _warning("lorem ipsum") - - with _WarningsLogger() as logger2: - pass - - assert len(logger2.warnings) == 0 - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/filter/test_filter.py b/tests/test_raillabel/filter/test_filter.py index 1299b7f..91e1eb6 100644 --- a/tests/test_raillabel/filter/test_filter.py +++ b/tests/test_raillabel/filter/test_filter.py @@ -32,7 +32,7 @@ def delete_sensor_from_data(data: dict, sensor_id: str) -> dict: def test_filter_unexpected_kwarg(json_paths): # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) with pytest.raises(TypeError): raillabel.filter(scene, unsupported_kwarg=[]) @@ -40,7 +40,7 @@ def test_filter_unexpected_kwarg(json_paths): def test_mutual_exclusivity(json_paths): # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) with pytest.raises(ValueError): raillabel.filter(scene, include_frames=[0], exclude_frames=[1, 2]) @@ -50,7 +50,7 @@ def test_filter_frames(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["1"] @@ -73,7 +73,7 @@ def test_filter_start(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["0"] @@ -96,7 +96,7 @@ def test_filter_end(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["1"] @@ -119,7 +119,7 @@ def test_filter_object_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] @@ -156,7 +156,7 @@ def test_filter_object_types(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] @@ -184,7 +184,7 @@ def test_filter_annotation_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] @@ -237,7 +237,7 @@ def test_filter_annotation_types(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] @@ -274,7 +274,7 @@ def test_filter_sensors(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] @@ -319,7 +319,7 @@ def test_filter_include_attribute_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["0"]["objects"][ @@ -364,7 +364,7 @@ def test_filter_exclude_attribute_ids(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["0"]["objects"][ @@ -406,7 +406,7 @@ def test_filter_exclude_attribute_values(json_paths, json_data): data = json_data["openlabel_v1_short"] # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"], validate=False) + scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data del data["openlabel"]["frames"]["0"]["objects"][ diff --git a/tests/test_raillabel/format/raillabel/test_bbox.py b/tests/test_raillabel/format/raillabel/test_bbox.py index 08a8456..b8fae6f 100644 --- a/tests/test_raillabel/format/raillabel/test_bbox.py +++ b/tests/test_raillabel/format/raillabel/test_bbox.py @@ -9,7 +9,6 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel._util._warning import _WarningsLogger from raillabel.format import Bbox # == Fixtures ========================= @@ -103,31 +102,6 @@ def test_fromdict( assert bbox.sensor == sensor_camera assert bbox.attributes == attributes_multiple_types -def test_fromdict_unknown_coordinate_system_warning( - point2d_dict, - size2d_dict, - sensors, - object_person, -): - with _WarningsLogger() as logger: - bbox = Bbox.fromdict( - { - "uid": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "name": "rgb_middle__bbox__person", - "val": point2d_dict + size2d_dict, - "coordinate_system": "UNKNOWN_COORDINATE_SYSTEM", - }, - sensors, - object_person - ) - - assert len(logger.warnings) == 1 - assert "78f0ad89-2750-4a30-9d66-44c9da73a714" in logger.warnings[0] - assert "'UNKNOWN_COORDINATE_SYSTEM'" in logger.warnings[0] - assert "sensor" in logger.warnings[0] - - assert bbox.sensor is None - def test_asdict( point2d, point2d_dict, diff --git a/tests/test_raillabel/format/raillabel/test_frame.py b/tests/test_raillabel/format/raillabel/test_frame.py index 06f9c3a..e50bc65 100644 --- a/tests/test_raillabel/format/raillabel/test_frame.py +++ b/tests/test_raillabel/format/raillabel/test_frame.py @@ -10,7 +10,6 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel._util._warning import _WarningsLogger from raillabel.format import Frame # == Fixtures ========================= @@ -119,74 +118,6 @@ def test_fromdict_annotations( assert frame.annotations == all_annotations -def test_fromdict_uri_attribute( - bbox_dict, - sensor_reference_camera_dict, sensors, - object_person, objects, -): - bbox_with_uri_attribute = bbox_dict - bbox_with_uri_attribute["attributes"]["text"].append({ - "name": "uri", - "val": "test_uri.png" - }) - - with _WarningsLogger() as logger: - frame = Frame.fromdict( - uid=0, - data_dict={ - "frame_properties": { - "streams": { - "rgb_middle": sensor_reference_camera_dict - } - }, - "objects": { - object_person.uid: { - "object_data": { - "bbox": [bbox_with_uri_attribute] - } - } - } - }, - sensors=sensors, - objects=objects, - ) - - assert len(logger.warnings) == 1 - assert "uri" in logger.warnings[0] - assert bbox_with_uri_attribute["uid"] in logger.warnings[0] - assert "raillabel.save()" in logger.warnings[0] - - assert frame.sensors["rgb_middle"].uri == "test_uri.png" - assert "uri" not in frame.annotations[bbox_with_uri_attribute["uid"]].attributes - -def test_fromdict_duplicate_annotation_uid_warning( - sensors, - object_person, objects, - bbox_dict, cuboid_dict -): - cuboid_dict["uid"] = bbox_dict["uid"] - - with _WarningsLogger() as logger: - frame = Frame.fromdict( - uid=2, - data_dict={ - "objects": { - object_person.uid: { - "object_data": { - "bbox": [bbox_dict], - "cuboid": [cuboid_dict], - } - } - } - }, - sensors=sensors, - objects=objects, - ) - - assert len(logger.warnings) == 1 - assert bbox_dict["uid"] in logger.warnings[0] - assert list(frame.annotations.values())[0].uid != list(frame.annotations.values())[1].uid - def test_asdict_sensors( sensor_reference_camera_dict, diff --git a/tests/test_raillabel/format/raillabel/test_scene.py b/tests/test_raillabel/format/raillabel/test_scene.py index c552302..5d46ed1 100644 --- a/tests/test_raillabel/format/raillabel/test_scene.py +++ b/tests/test_raillabel/format/raillabel/test_scene.py @@ -10,7 +10,6 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) from raillabel import exceptions -from raillabel._util._warning import _WarningsLogger from raillabel.format import Frame, FrameInterval, Scene from raillabel.format.raillabel.scene import _clean_dict @@ -275,24 +274,6 @@ def test_fromdict_frames( frame.uid: frame, } -def test_fromdict_duplicate_frame_id(metadata_full_dict): - with _WarningsLogger() as logger: - scene = Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "frames": { - "0": {}, - "00": {}, - } - } - } - ) - - assert len(logger.warnings) == 1 - assert "0" in logger.warnings[0] - assert len(scene.frames) == 1 - def test_asdict_sensors( metadata_full, metadata_full_dict, diff --git a/tests/test_raillabel/save/test_save.py b/tests/test_raillabel/save/test_save.py index 235bf17..cbe2456 100644 --- a/tests/test_raillabel/save/test_save.py +++ b/tests/test_raillabel/save/test_save.py @@ -18,10 +18,10 @@ def test_save_scene(json_paths): with tempfile.TemporaryDirectory("w") as temp_dir: - scene_orig = raillabel.load(json_paths["openlabel_v1_short"], False, False) + scene_orig = raillabel.load(json_paths["openlabel_v1_short"]) raillabel.save(scene_orig, Path(temp_dir) / "test_save_file.json") - scene_saved = raillabel.load(Path(temp_dir) / "test_save_file.json", False, False) + scene_saved = raillabel.load(Path(temp_dir) / "test_save_file.json") assert scene_orig == scene_saved @@ -39,7 +39,7 @@ def test_save_json(json_data): with (Path(temp_dir) / "stripped_input_data.json").open("w") as f: json.dump(stripped_input_data, f) - scene = raillabel.load(Path(temp_dir) / "stripped_input_data.json", False, False) + scene = raillabel.load(Path(temp_dir) / "stripped_input_data.json") raillabel.save(scene, Path(temp_dir) / "test_save_file.json") with (Path(temp_dir) / "test_save_file.json").open() as f: From eca43db6188d74ca358e054c0362ad80bf179ce8 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:39:52 +0100 Subject: [PATCH 008/190] refactor: remove scene validation --- raillabel/__init__.py | 1 - raillabel/save/save.py | 32 +- .../validate/schemas/raillabel_schema.json | 937 ------------------ .../schemas/raillabel_schema.json.license | 2 - .../schemas/understand_ai_t4_schema.json | 604 ----------- .../understand_ai_t4_schema.json.license | 2 - raillabel/validate/validate.py | 96 -- tests/__test_assets__/metaschema.json | 172 ---- tests/__test_assets__/metaschema.json.license | 2 - tests/conftest.py | 1 - .../schemas/test_raillabel_v2_schema.py | 34 - .../test_raillabel/validate/test_validate.py | 61 -- 12 files changed, 2 insertions(+), 1942 deletions(-) delete mode 100644 raillabel/validate/schemas/raillabel_schema.json delete mode 100644 raillabel/validate/schemas/raillabel_schema.json.license delete mode 100644 raillabel/validate/schemas/understand_ai_t4_schema.json delete mode 100644 raillabel/validate/schemas/understand_ai_t4_schema.json.license delete mode 100644 raillabel/validate/validate.py delete mode 100644 tests/__test_assets__/metaschema.json delete mode 100644 tests/__test_assets__/metaschema.json.license delete mode 100644 tests/test_raillabel/validate/schemas/test_raillabel_v2_schema.py delete mode 100644 tests/test_raillabel/validate/test_validate.py diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 8058cd0..3991e17 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -9,7 +9,6 @@ from .format import Scene from .load.load import load from .save.save import save -from .validate.validate import validate try: __version__ = metadata.version("raillabel") diff --git a/raillabel/save/save.py b/raillabel/save/save.py index 08bafe6..f0304ad 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -6,10 +6,9 @@ from .. import exceptions from ..format import Scene -from ..validate.validate import validate as validate_func -def save(scene: Scene, path: str, prettify_json: bool = False, validate: bool = False): +def save(scene: Scene, path: str, prettify_json: bool = False): """Save a raillabel.Scene in a JSON file. Parameters @@ -23,39 +22,12 @@ def save(scene: Scene, path: str, prettify_json: bool = False, validate: bool = prettify_json: bool, optional If true, the JSON is saved with linebreaks and indents. This increases readibility but also the file size. Default is False. - validate: bool, optional - If True, the annotation data is validated via the OpenLabel schema. This is highly - recommended, as not validating the data may lead to Errors during loading or while handling - the scene. However, validating may increase the loading time. Default is False. - - Raises - ------ - SchemaError - If the data does not validate via the OpenLabel schema. - FileNotFoundError - If the save_path does not point to an accessible file. """ path = Path(path) - - # Converts the data stored in the scene into a dictionary + data = scene.asdict() - # Validates the data - if validate: - is_data_valid, err_msgs = validate_func(data) - if not is_data_valid: - schema_err_msg = ( - "The data could not be saved, because it does not validate " - "against the OpenLabel schema:" - ) - - for err_msg in err_msgs: - schema_err_msg += "\n - " + err_msg - - raise exceptions.SchemaError(schema_err_msg) - - # Saves the data with path.open("w") as save_file: if prettify_json: diff --git a/raillabel/validate/schemas/raillabel_schema.json b/raillabel/validate/schemas/raillabel_schema.json deleted file mode 100644 index 1365048..0000000 --- a/raillabel/validate/schemas/raillabel_schema.json +++ /dev/null @@ -1,937 +0,0 @@ -{ - "$id": "https://openlabel.asam.net/V1-0-0/schema#", - "$schema": "http://json-schema.org/draft-07/schema#", - "version": "3.0.0", - "description": "This schema is a sub-set of the OpenLABEL schema, that more accurately describes the usage by raillabel. Every JSON file, that is valid in this schema, is also valid in the OpenLABEL schema.", - "additionalProperties": false, - "definitions": { - "attributes": { - "additionalProperties": false, - "description": "Attributes is the alias of element data that can be nested inside geometric object data. For example, a certain bounding box can have attributes related to its score, visibility, etc. These values can be nested inside the bounding box as attributes.", - "properties": { - "boolean": { - "items": { - "$ref": "#/definitions/boolean_attribute" - }, - "type": "array" - }, - "num": { - "items": { - "$ref": "#/definitions/num_attribute" - }, - "type": "array" - }, - "text": { - "items": { - "$ref": "#/definitions/text_attribute" - }, - "type": "array" - }, - "vec": { - "items": { - "$ref": "#/definitions/vec_attribute" - }, - "type": "array" - } - }, - "type": "object" - }, - "bbox": { - "additionalProperties": false, - "description": "A 2D bounding box is defined as a 4-dimensional vector [x, y, w, h], where [x, y] is the center of the bounding box and [w, h] represent the width (horizontal, x-coordinate dimension) and height (vertical, y-coordinate dimension), respectively.", - "properties": { - "attributes": { - "$ref": "#/definitions/attributes" - }, - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.", - "type": "string" - }, - "val": { - "description": "The array of 4 values that define the [x, y, w, h] values of the bbox.", - "items": { - "type": "number" - }, - "maxItems": 4, - "minItems": 4, - "type": "array" - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "name", - "val" - ], - "type": "object" - }, - "boolean_attribute": { - "additionalProperties": false, - "description": "A boolean attribute.", - "properties": { - "name": { - "description": "Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.", - "type": "string" - }, - "val": { - "description": "The boolean value.", - "type": "boolean" - } - }, - "required": [ - "val" - ], - "type": "object" - }, - "coordinate_system": { - "additionalProperties": false, - "description": "A coordinate system is a 3D reference frame. Spatial information on objects and their properties can be defined with respect to coordinate systems.", - "properties": { - "children": { - "description": "List of children of this coordinate system.", - "items": { - "description": "This is the string UID of this child coordinate system.", - "type": "string" - }, - "type": "array" - }, - "parent": { - "description": "This is the string UID of the parent coordinate system this coordinate system is referring to.", - "type": "string", - "enum": [ - "base", - "" - ] - }, - "pose_wrt_parent": { - "$ref": "#/definitions/transform_data" - }, - "type": { - "description": "This is a string that describes the type of the coordinate system, for example, \"local\", \"geo\").", - "type": "string", - "enum": [ - "sensor", - "local" - ] - } - }, - "required": [ - "type", - "parent" - ] - }, - "coordinate_systems": { - "additionalProperties": false, - "description": "This is a JSON object which contains OpenLABEL coordinate systems. Coordinate system keys can be any string, for example, a friendly coordinate system name.", - "patternProperties": { - "^": { - "$ref": "#/definitions/coordinate_system" - } - }, - "type": "object" - }, - "cuboid": { - "additionalProperties": false, - "description": "A cuboid or 3D bounding box. It is defined by the position of its center, the rotation in 3D, and its dimensions.", - "properties": { - "attributes": { - "$ref": "#/definitions/attributes" - }, - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.", - "type": "string" - }, - "val": { - "description": "List of values encoding the position, rotation and dimensions. Two options are supported, using 9 or 10 values. If 9 values are used, the format is (x, y, z, rx, ry, rz, sx, sy, sz), where (x, y, z) encodes the position, (rx, ry, rz) encodes the Euler angles that encode the rotation, and (sx, sy, sz) are the dimensions of the cuboid in its object coordinate system. If 10 values are used, then the format is (x, y, z, qx, qy, qz, qw, sx, sy, sz) with the only difference of the rotation values which are the 4 values of a quaternion.", - "oneOf": [ - { - "items": { - "type": "number" - }, - "maxItems": 10, - "minItems": 9, - "type": "array" - }, - { - "type": "null" - } - ] - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "name", - "val" - ], - "type": "object" - }, - "element_data_pointer": { - "description": "This item contains pointers to element data of elements, indexed by \"name\", and containing information about the element data type, for example, bounding box, cuboid, and the frame intervals in which this element_data exists within an element. That means, these pointers can be used to explore element data dynamic information within the JSON content.", - "properties": { - "attribute_pointers": { - "description": "This is a JSON object which contains pointers to the attributes of the element data pointed by this pointer. The attributes pointer keys shall be the \"name\" of the attribute of the element data this pointer points to.", - "patternProperties": { - "^": { - "description": "The attribute pointer values are strings which define the type of the attribute.", - "enum": [ - "num", - "text", - "boolean", - "vec" - ], - "type": "string" - } - }, - "type": "object" - }, - "frame_intervals": { - "description": "List of frame intervals of the element data pointed by this pointer.", - "items": { - "$ref": "#/definitions/frame_interval" - }, - "type": "array" - }, - "type": { - "description": "Type of the element data pointed by this pointer.", - "enum": [ - "bbox", - "num", - "poly2d", - "poly3d", - "cuboid", - "vec" - ], - "type": "string" - } - }, - "required": [ - "attribute_pointers", - "frame_intervals", - "type" - ], - "additionalProperties": false - }, - "element_data_pointers": { - "additionalProperties": false, - "description": "This is a JSON object which contains OpenLABEL element data pointers. Element data pointer keys shall be the \"name\" of the element data this pointer points to.", - "patternProperties": { - "^": { - "$ref": "#/definitions/element_data_pointer" - } - }, - "type": "object" - }, - "frame": { - "additionalProperties": false, - "description": "A frame is a container of dynamic, timewise, information.", - "properties": { - "frame_properties": { - "additionalProperties": false, - "description": "This is a JSON object which contains information about this frame.", - "properties": { - "streams": { - "additionalProperties": false, - "description": "Streams is a JSON object which contains OpenLABEL streams with specific information for this frame. Stream keys can be any string, for example, a friendly stream name.", - "patternProperties": { - "^": { - "$ref": "#/definitions/stream_sync" - } - }, - "type": "object" - }, - "timestamp": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ], - "description": "The timestamp indicates a time instant as a UTC string or numerical value to describe this frame." - }, - "frame_data": { - "additionalProperties": false, - "description": "Additional data to describe attributes of the frame.", - "properties": { - "num": { - "description": "List of \"num\" that describe this frame.", - "items": { - "$ref": "#/definitions/num" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "objects": { - "additionalProperties": false, - "description": "This is a JSON object that contains dynamic information on OpenLABEL objects. Object keys are strings containing numerical UIDs or 32 bytes UUIDs. Object values may contain an \"object_data\" JSON object.", - "patternProperties": { - "^(-?[0-9]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$": { - "additionalProperties": false, - "properties": { - "object_data": { - "$ref": "#/definitions/object_data" - } - }, - "type": "object" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "frame_interval": { - "additionalProperties": false, - "description": "A frame interval defines a starting and ending frame number as a closed interval. That means the interval includes the limit frame numbers.", - "properties": { - "frame_end": { - "description": "Ending frame number of the interval.", - "type": "integer" - }, - "frame_start": { - "description": "Initial frame number of the interval.", - "type": "integer" - } - }, - "type": "object" - }, - "metadata": { - "additionalProperties": true, - "description": "This JSON object contains information, that is, metadata, about the annotation file itself.", - "properties": { - "comment": { - "description": "Additional information or description about the annotation content.", - "type": "string" - }, - "file_version": { - "description": "Version number of the OpenLABEL annotation content.", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", - "type": "string" - }, - "name": { - "description": "Name of the OpenLABEL annotation content.", - "type": "string" - }, - "schema_version": { - "description": "Version number of the OpenLABEL schema this annotation JSON object follows.", - "enum": [ - "1.0.0" - ], - "type": "string" - }, - "tagged_file": { - "description": "File name or URI of the data file being tagged.", - "type": "string" - }, - "exporter_version": { - "description": "Version number of the exporter software.", - "type": "string" - }, - "subschema_version": { - "description": "Version number of the RailLabel subschema this annotation JSON object follows.", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", - "type": "string" - } - }, - "required": [ - "schema_version" - ], - "type": "object" - }, - "num": { - "additionalProperties": false, - "description": "A number.", - "properties": { - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data.", - "type": "string" - }, - "val": { - "description": "The numerical value of the number.", - "type": "number" - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "name", - "val" - ], - "type": "object" - }, - "num_attribute": { - "additionalProperties": false, - "description": "A number attribute.", - "properties": { - "name": { - "description": "Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.", - "type": "string" - }, - "val": { - "description": "The numerical value of the number.", - "type": "number" - } - }, - "required": [ - "val" - ], - "type": "object" - }, - "object": { - "additionalProperties": false, - "description": "An object is the main type of annotation element. Object is designed to represent spatiotemporal entities, such as physical objects in the real world. Objects shall have a name and type. Objects may have static and dynamic data. Objects are the only type of elements that may have geometric data, such as bounding boxes, cuboids, polylines, images, etc.", - "properties": { - "frame_intervals": { - "description": "The array of frame intervals where this object exists or is defined.", - "items": { - "$ref": "#/definitions/frame_interval" - }, - "type": "array" - }, - "name": { - "description": "Name of the object. It is a friendly name and not used for indexing.", - "type": "string" - }, - "object_data_pointers": { - "$ref": "#/definitions/element_data_pointers" - }, - "type": { - "description": "The type of an object defines the class the object corresponds to.", - "type": "string" - } - }, - "required": [ - "name", - "type" - ], - "type": "object" - }, - "object_data": { - "additionalProperties": false, - "description": "Additional data to describe attributes of the object.", - "properties": { - "bbox": { - "description": "List of \"bbox\" that describe this object.", - "items": { - "$ref": "#/definitions/bbox" - }, - "type": "array" - }, - "cuboid": { - "description": "List of \"cuboid\" that describe this object.", - "items": { - "$ref": "#/definitions/cuboid" - }, - "type": "array" - }, - "poly2d": { - "description": "List of \"poly2d\" that describe this object.", - "items": { - "$ref": "#/definitions/poly2d" - }, - "type": "array" - }, - "poly3d": { - "description": "List of \"poly3d\" that describe this object.", - "items": { - "$ref": "#/definitions/poly3d" - }, - "type": "array" - }, - "vec": { - "description": "List of \"vec\" that describe this object.", - "items": { - "$ref": "#/definitions/vec" - }, - "type": "array" - } - }, - "type": "object" - }, - "openlabel": { - "additionalProperties": false, - "description": "The OpenLABEL root JSON object, which contains all other JSON objects.", - "properties": { - "coordinate_systems": { - "$ref": "#/definitions/coordinate_systems" - }, - "frame_intervals": { - "description": "This is an array of frame intervals.", - "item": { - "$ref": "#/definitions/frame_interval" - }, - "type": "array" - }, - "frames": { - "additionalProperties": false, - "description": "This is the JSON object of frames that contain the dynamic, timewise, annotations. Keys are strings containing numerical frame identifiers, which are denoted as master frame numbers.", - "patternProperties": { - "^[0-9]+$": { - "$ref": "#/definitions/frame" - } - }, - "type": "object" - }, - "metadata": { - "$ref": "#/definitions/metadata" - }, - "objects": { - "additionalProperties": false, - "description": "This is the JSON object of OpenLABEL objects. Object keys are strings containing numerical UIDs or 32 bytes UUIDs.", - "patternProperties": { - "^(-?[0-9]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$": { - "$ref": "#/definitions/object" - } - }, - "type": "object" - }, - "streams": { - "$ref": "#/definitions/streams" - } - }, - "required": [ - "metadata" - ], - "type": "object" - }, - "poly2d": { - "additionalProperties": false, - "description": "A 2D polyline defined as a sequence of 2D points.", - "properties": { - "attributes": { - "$ref": "#/definitions/attributes" - }, - "closed": { - "description": "A boolean that defines whether the polyline is closed or not. In case it is closed, it is assumed that the last point of the sequence is connected with the first one.", - "type": "boolean" - }, - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "mode": { - "description": "Mode of the polyline list of values: \"MODE_POLY2D_ABSOLUTE\" determines that the poly2d list contains the sequence of (x, y) values of all points of the polyline. \"MODE_POLY2D_RELATIVE\" specifies that only the first point of the sequence is defined with its (x, y) values, while all the rest are defined relative to it. \"MODE_POLY2D_SRF6DCC\" specifies that SRF6DCC chain code method is used. \"MODE_POLY2D_RS6FCC\" specifies that the RS6FCC method is used.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.", - "type": "string" - }, - "val": { - "description": "List of numerical values of the polyline, according to its mode.", - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "items": { - "type": "number" - }, - "type": "array" - } - ] - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "name", - "val", - "mode", - "closed" - ], - "type": "object" - }, - "poly3d": { - "additionalProperties": false, - "description": "A 3D polyline defined as a sequence of 3D points.", - "properties": { - "attributes": { - "$ref": "#/definitions/attributes" - }, - "closed": { - "type": "boolean", - "description": "A boolean that defines whether the polyline is closed or not. In case it is closed, it is assumed that the last point of the sequence is connected with the first one." - }, - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.", - "type": "string" - }, - "val": { - "description": "List of numerical values of the polyline, according to its mode.", - "items": { - "type": "number" - }, - "type": "array" - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "name", - "val", - "closed" - ], - "type": "object" - }, - "stream_camera": { - "additionalProperties": false, - "description": "A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a camera sensor.", - "properties": { - "description": { - "description": "Description of the stream.", - "type": "string" - }, - "stream_properties": { - "additionalProperties": false, - "description": "Additional properties of the stream.", - "properties": { - "intrinsics_pinhole": { - "additionalProperties": false, - "description": "This JSON object defines an instance of the intrinsic parameters of a pinhole camera.", - "properties": { - "camera_matrix": { - "description": "This is a 3x4 camera matrix which projects 3D homogeneous points (4x1) from a camera coordinate system into the image plane (3x1). This is the usual K matrix for camera projection as in OpenCV. It is extended from 3x3 to 3x4 to enable its direct utilisation to project 4x1 homogeneous 3D points. The matrix is defined to follow the camera model: x-to-right, y-down, z-forward. The following equation applies: x_img = camera_matrix * X_ccs.", - "items": { - "type": "number" - }, - "maxItems": 12, - "minItems": 12, - "type": "array" - }, - "distortion_coeffs": { - "description": "This is the array 1xN radial and tangential distortion coefficients.", - "items": { - "type": "number" - }, - "maxItems": 14, - "minItems": 5, - "type": "array" - }, - "height_px": { - "type": "integer" - }, - "width_px": { - "type": "integer" - } - }, - "type": "object", - "required": [ - "camera_matrix", - "distortion_coeffs", - "height_px", - "width_px" - ] - } - } - }, - "type": { - "description": "A string encoding the type of the stream.", - "enum": [ - "camera" - ], - "type": "string" - }, - "uri": { - "description": "A string encoding the subdirectory containing the sensor files.", - "type": "string" - } - }, - "type": "object" - }, - "stream_radar": { - "additionalProperties": false, - "description": "A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a camera sensor.", - "properties": { - "description": { - "description": "Description of the stream.", - "type": "string" - }, - "stream_properties": { - "additionalProperties": false, - "description": "Additional properties of the stream.", - "properties": { - "intrinsics_radar": { - "additionalProperties": false, - "description": "This JSON object defines an instance of the intrinsic parameters of a radar.", - "properties": { - "resolution_px_per_m": { - "type": "number" - }, - "height_px": { - "type": "integer" - }, - "width_px": { - "type": "integer" - } - }, - "type": "object", - "required": [ - "resolution_px_per_m", - "height_px", - "width_px" - ] - } - } - }, - "type": { - "description": "A string encoding the type of the stream.", - "enum": [ - "radar" - ], - "type": "string" - }, - "uri": { - "description": "A string encoding the subdirectory containing the sensor files.", - "type": "string" - } - }, - "type": "object" - }, - "stream_other": { - "additionalProperties": false, - "description": "A stream describes the source of a data sequence, usually a sensor. This specific object is used for streams without intrinsic calibration.", - "properties": { - "description": { - "description": "Description of the stream.", - "type": "string" - }, - "type": { - "description": "A string encoding the type of the stream.", - "enum": [ - "lidar", - "gps_imu", - "other" - ], - "type": "string" - }, - "uri": { - "description": "A string encoding the subdirectory containing the sensor files.", - "type": "string" - } - }, - "type": "object" - }, - "stream_sync": { - "additionalProperties": false, - "description": "Syncronization information of a stream in a frame.", - "properties": { - "stream_properties": { - "additionalProperties": false, - "properties": { - "sync": { - "additionalProperties": false, - "properties": { - "timestamp": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ], - "description": "The timestamp indicates a time instant as a UTC string or numerical value to describe this frame." - } - }, - "type": "object" - } - }, - "type": "object" - }, - "uri": { - "description": "A string encoding the URI, for example, a URL, or file name, for example, a video file name, the stream corresponds to.", - "type": "string" - } - }, - "type": "object" - }, - "streams": { - "additionalProperties": false, - "description": "This is a JSON object which contains OpenLABEL streams. Stream keys can be any string, for example, a friendly stream name.", - "patternProperties": { - "^": { - "oneOf": [ - { "$ref": "#/definitions/stream_camera" }, - { "$ref": "#/definitions/stream_radar" }, - { "$ref": "#/definitions/stream_other" } - ] - } - }, - "type": "object" - }, - "text_attribute": { - "additionalProperties": false, - "description": "A text attribute.", - "properties": { - "name": { - "description": "Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.", - "type": "string" - }, - "val": { - "description": "The characters of the text.", - "type": "string" - } - }, - "required": [ - "val" - ], - "type": "object" - }, - "transform_data": { - "additionalProperties": false, - "description": "A transform can be defined with a quaternion to encode the rotation of a coordinate system with respect to another, and a translation.", - "properties": { - "quaternion": { - "description": "List of 4 values encoding a quaternion (x, y, z, w).", - "items": { - "type": "number" - }, - "maxItems": 4, - "minItems": 4, - "type": "array" - }, - "translation": { - "description": "List of 3 values encoding the translation vector (x, y, z)", - "items": { - "type": "number" - }, - "maxItems": 3, - "minItems": 3, - "type": "array" - } - }, - "required": [ - "quaternion", - "translation" - ], - "type": "object" - }, - "vec": { - "additionalProperties": false, - "description": "A vector (list) of numbers or strings.", - "properties": { - "attributes": { - "$ref": "#/definitions/attributes" - }, - "coordinate_system": { - "description": "Name of the coordinate system in respect of which this object data is expressed.", - "type": "string" - }, - "name": { - "description": "This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.", - "type": "string" - }, - "type": { - "description": "This attribute specifies whether the vector shall be considered as a descriptor of individual values or as a definition of a range.", - "enum": [ - "values", - "range" - ], - "type": "string" - }, - "val": { - "description": "The numerical values of the vector (list) of numbers.", - "items": { - "oneOf": [ - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "type": "array" - }, - "uid": { - "description": "This is a string encoding the Universal Unique identifyer of the annotation.", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$", - "type": "string" - } - }, - "required": [ - "val" - ], - "type": "object" - }, - "vec_attribute": { - "additionalProperties": false, - "description": "A vector attribute.", - "properties": { - "name": { - "description": "Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.", - "type": "string" - }, - "val": { - "description": "The numerical values of the vector (list) of numbers.", - "items": { - "oneOf": [ - { - "type": "number" - }, - { - "type": "string" - } - ] - }, - "type": "array" - } - }, - "required": [ - "val" - ], - "type": "object" - } - }, - "properties": { - "openlabel": { - "$ref": "#/definitions/openlabel" - } - }, - "required": [ - "openlabel" - ], - "type": "object" -} diff --git a/raillabel/validate/schemas/raillabel_schema.json.license b/raillabel/validate/schemas/raillabel_schema.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/raillabel/validate/schemas/raillabel_schema.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/raillabel/validate/schemas/understand_ai_t4_schema.json b/raillabel/validate/schemas/understand_ai_t4_schema.json deleted file mode 100644 index eeeba35..0000000 --- a/raillabel/validate/schemas/understand_ai_t4_schema.json +++ /dev/null @@ -1,604 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "additionalProperties": false, - "definitions": { - "2D_BOUNDING_BOX": { - "type": "array", - "items": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "objectId": { - "type": "string" - }, - "className": { - "type": "string" - }, - "geometry": { - "additionalProperties": false, - "type": "object", - "properties": { - "xMin": { - "type": "number" - }, - "yMin": { - "type": "number" - }, - "xMax": { - "type": "number" - }, - "yMax": { - "type": "number" - } - }, - "required": [ - "xMax", - "xMin", - "yMax", - "yMin" - ] - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "sensor": { - "$ref": "#/definitions/sensor" - } - }, - "required": [ - "attributes", - "className", - "geometry", - "id", - "objectId", - "sensor" - ] - } - }, - "2D_POLYGON": { - "type": "array", - "items": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "objectId": { - "type": "string" - }, - "className": { - "type": "string" - }, - "geometry": { - "additionalProperties": false, - "type": "object", - "properties": { - "points": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - }, - "minItems": 2, - "maxItems": 2 - } - } - }, - "required": [ - "points" - ] - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "sensor": { - "$ref": "#/definitions/sensor" - } - }, - "required": [ - "attributes", - "className", - "geometry", - "id", - "objectId", - "sensor" - ] - } - }, - "2D_POLYLINE": { - "type": "array", - "items": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "objectId": { - "type": "string" - }, - "className": { - "type": "string" - }, - "geometry": { - "additionalProperties": false, - "type": "object", - "properties": { - "points": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "number" - }, - "minItems": 2, - "maxItems": 2 - } - } - }, - "required": [ - "points" - ] - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "sensor": { - "$ref": "#/definitions/sensor" - } - }, - "required": [ - "attributes", - "className", - "geometry", - "id", - "objectId", - "sensor" - ] - } - }, - "3D_BOUNDING_BOX": { - "type": "array", - "items": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "objectId": { - "type": "string" - }, - "className": { - "type": "string" - }, - "geometry": { - "additionalProperties": false, - "type": "object", - "properties": { - "size": { - "additionalProperties": false, - "type": "object", - "properties": { - "height": { - "type": "number" - }, - "width": { - "type": "number" - }, - "length": { - "type": "number" - } - }, - "required": [ - "height", - "length", - "width" - ] - }, - "center": { - "additionalProperties": false, - "type": "object", - "properties": { - "x": { - "type": "number" - }, - "y": { - "type": "number" - }, - "z": { - "type": "number" - } - }, - "required": [ - "x", - "y", - "z" - ] - }, - "quaternion": { - "additionalProperties": false, - "type": "object", - "properties": { - "x": { - "type": "number" - }, - "y": { - "type": "number" - }, - "z": { - "type": "number" - }, - "w": { - "type": "number" - } - }, - "required": [ - "w", - "x", - "y", - "z" - ] - } - }, - "required": [ - "center", - "quaternion", - "size" - ] - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "sensor": { - "$ref": "#/definitions/sensor" - } - }, - "required": [ - "attributes", - "className", - "geometry", - "id", - "objectId", - "sensor" - ] - } - }, - "3D_SEGMENTATION": { - "type": "array", - "items": { - "additionalProperties": false, - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "objectId": { - "type": "string" - }, - "className": { - "type": "string" - }, - "geometry": { - "additionalProperties": false, - "type": "object", - "properties": { - "associatedPoints": { - "additionalProperties": false, - "type": "array", - "items": { - "type": "integer" - } - }, - "numberOfPointsInBox": { - "type": "integer" - } - }, - "required": [ - "associatedPoints", - "numberOfPointsInBox" - ] - }, - "attributes": { - "$ref": "#/definitions/attributes" - }, - "sensor": { - "$ref": "#/definitions/sensor" - } - }, - "required": [ - "attributes", - "className", - "geometry", - "id", - "objectId", - "sensor" - ] - } - }, - "attributes": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "patternProperties": { - "^": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "array", - "items": { - "type": "number" - } - } - ] - } - } - }, - "coordinateSystems": { - "description": "tbd", - "type": "array", - "items": { - "additionalProperties": false, - "type": "object", - "properties": { - "coordinate_system_id": { - "type": "string" - }, - "topic": { - "description": "tbd", - "type": "string" - }, - "frame_id": { - "description": "tbd", - "type": "string" - }, - "position": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 3, - "maxItems": 3 - }, - "rotation_quaternion": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 4, - "maxItems": 4 - }, - "rotation_matrix": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 9, - "maxItems": 9 - }, - "angle_axis_rotation": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 3, - "maxItems": 3 - }, - "homogeneous_transform": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 16, - "maxItems": 16 - }, - "measured_position": { - "description": "tbd", - "type": "array", - "items": { - "type": "integer" - }, - "minItems": 3, - "maxItems": 3 - }, - "camera_matrix": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 9, - "maxItems": 9 - }, - "dist_coeffs": { - "description": "tbd", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 5, - "maxItems": 5 - }, - "rotation_around_z_in_degrees": { - "description": "tbd", - "type": "number" - } - }, - "required": [ - "angle_axis_rotation", - "coordinate_system_id", - "frame_id", - "position", - "rotation_matrix", - "rotation_quaternion", - "topic" - ] - } - }, - "frames": { - "type": "array", - "items": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "frameId": { - "type": "string" - }, - "timestamp": { - "type": "string" - }, - "annotations": { - "description": "tbd", - "additionalProperties": false, - "type": "object", - "properties": { - "2D_BOUNDING_BOX": { - "$ref": "#/definitions/2D_BOUNDING_BOX" - }, - "2D_POLYLINE": { - "$ref": "#/definitions/2D_POLYLINE" - }, - "2D_POLYGON": { - "$ref": "#/definitions/2D_POLYGON" - }, - "3D_BOUNDING_BOX": { - "$ref": "#/definitions/3D_BOUNDING_BOX" - }, - "3D_SEGMENTATION": { - "$ref": "#/definitions/3D_SEGMENTATION" - } - }, - "required": [ - "2D_BOUNDING_BOX", - "2D_POLYGON", - "2D_POLYLINE", - "3D_BOUNDING_BOX", - "3D_SEGMENTATION" - ] - } - }, - "required": [ - "annotations", - "frameId", - "timestamp" - ] - } - }, - "metadata": { - "additionalProperties": false, - "type": "object", - "properties": { - "clip_id": { - "description": "tbd", - "type": "string" - }, - "external_clip_id": { - "description": "tbd", - "type": "string" - }, - "project_id": { - "description": "tbd", - "type": "string" - }, - "export_time": { - "type": "string", - "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2} UTC$" - }, - "exporter_version": { - "type": "string" - }, - "coordinate_system_3d": { - "description": "tbd", - "type": "string" - }, - "coordinate_system_reference": { - "description": "tbd", - "type": "string" - }, - "folder_name": { - "description": "tbd", - "type": "string" - } - }, - "required": [ - "clip_id", - "coordinate_system_3d", - "coordinate_system_reference", - "export_time", - "exporter_version", - "external_clip_id", - "folder_name", - "project_id" - ] - }, - "sensor": { - "additionalProperties": false, - "type": "object", - "properties": { - "type": { - "description": "tbd", - "type": "string" - }, - "uri": { - "description": "tbd", - "type": "string" - }, - "timestamp": { - "description": "tbd", - "type": "string" - } - }, - "required": [ - "timestamp", - "type", - "uri" - ] - } - }, - "properties": { - "metadata": { - "$ref": "#/definitions/metadata" - }, - "coordinateSystems": { - "$ref": "#/definitions/coordinateSystems" - }, - "frames": { - "$ref": "#/definitions/frames" - } - }, - "required": [ - "coordinateSystems", - "frames", - "metadata" - ] -} diff --git a/raillabel/validate/schemas/understand_ai_t4_schema.json.license b/raillabel/validate/schemas/understand_ai_t4_schema.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/raillabel/validate/schemas/understand_ai_t4_schema.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/raillabel/validate/validate.py b/raillabel/validate/validate.py deleted file mode 100644 index 0beab3e..0000000 --- a/raillabel/validate/validate.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import json -import os -import typing as t -from pathlib import Path - -import fastjsonschema -import jsonschema - -from .. import exceptions - - -def validate(data: dict, schema_path: str = "raillabel") -> t.Tuple[bool, t.List[str]]: - """Validate JSON data represented by a dict via a given schema. - - Parameters - ---------- - data: dict - JSON data to be validated. - schema_path: str, optional - Path to the JSON schema used for the validation. If the schema is in the /schemas - folder, the format name can be used (i.e. schema_path can be 'raillabel' or - 'raillabel_schema' to load the raillabel_schema.json file). Default is - 'raillabel'. - - Returns - ------- - is_data_valid: bool - True if the data validates against the schema, False if not. - schema_errors: t.List of str - All SchemaError messages found in the data. If the data is valid (if no SchemaErrors are - found), this is an empty t.List. - """ - - # Since the schema_path can either be a complete path or the short name of a schema, these two - # options must be distinguished. It is therefore assumed, that a complete path contains at # - # least one '/' or '\'. - - if "/" in schema_path or "\\" in schema_path: # if schema_path is a complete path - schema_path = Path(schema_path) - - else: # if schema_path is a schema name in /schemas - local_schemas = [ # t.List of json files in /schemas - p for p in os.listdir(Path(__file__).parent / "schemas") if p.endswith(".json") - ] - - applicable_local_schemas = [] # t.List of possible schemas - for local_schema_path in local_schemas: - if schema_path in local_schema_path: - applicable_local_schemas.append(local_schema_path) - - if len(applicable_local_schemas) == 1: # if exactly one applicable file has been found - schema_path = Path(__file__).parent / "schemas" / applicable_local_schemas[0] - - elif len(applicable_local_schemas) == 0: # if no applicable files have been found - - err_msg = f"The key {schema_path} does not apply to a schema. Available schema files:" - for p in local_schemas: - err_msg += "\n - " + p - - raise FileNotFoundError(err_msg) - - else: # if more than one applicable files have been found - - err_msg = f"The key {schema_path} applies to multiple files in /schemas:" - for p in applicable_local_schemas: - err_msg += "\n - " + p - - raise exceptions.AmbiguousSchemaNameError(err_msg) - - # Loads the schema data - try: - with schema_path.open() as schema_file: - schema = json.load(schema_file) - - except FileNotFoundError as e: - raise FileNotFoundError(f"The schema file could not be found in {schema_path}") from e - - # Validates the data - schema_errors = [] - - try: - # Use fastjsonschema since its faster (duh), and use jsonschema as a fallback if we find - # an error to maintain jsonschemas way of reporting all errors - fastjsonschema.validate(schema, data) - except fastjsonschema.JsonSchemaException as _: - validator = jsonschema.Draft7Validator(schema=schema) - - for error in validator.iter_errors(data): - schema_errors.append("$" + error.json_path[1:] + ": " + str(error.message)) - - is_data_valid = len(schema_errors) == 0 - - return is_data_valid, schema_errors diff --git a/tests/__test_assets__/metaschema.json b/tests/__test_assets__/metaschema.json deleted file mode 100644 index fb92c7f..0000000 --- a/tests/__test_assets__/metaschema.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://json-schema.org/draft-07/schema#", - "title": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "allOf": [ - { "$ref": "#/definitions/nonNegativeInteger" }, - { "default": 0 } - ] - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "uniqueItems": true, - "default": [] - } - }, - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "$comment": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": true, - "readOnly": { - "type": "boolean", - "default": false - }, - "writeOnly": { - "type": "boolean", - "default": false - }, - "examples": { - "type": "array", - "items": true - }, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, - "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { "$ref": "#" }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": true - }, - "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, - "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { "$ref": "#" }, - "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, - "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { "$ref": "#" }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "propertyNames": { "format": "regex" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "propertyNames": { "$ref": "#" }, - "const": true, - "enum": { - "type": "array", - "items": true, - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { "type": "string" }, - "contentMediaType": { "type": "string" }, - "contentEncoding": { "type": "string" }, - "if": { "$ref": "#" }, - "then": { "$ref": "#" }, - "else": { "$ref": "#" }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "default": true -} diff --git a/tests/__test_assets__/metaschema.json.license b/tests/__test_assets__/metaschema.json.license deleted file mode 100644 index fbb8ddf..0000000 --- a/tests/__test_assets__/metaschema.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and the raillabel contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/tests/conftest.py b/tests/conftest.py index 4e635c6..5b58ab1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,6 @@ json_data_directories = [ Path(__file__).parent / "__test_assets__", - Path(__file__).parent.parent / "raillabel" / "validate" / "schemas" ] @pytest.fixture(scope="session", autouse=True) diff --git a/tests/test_raillabel/validate/schemas/test_raillabel_v2_schema.py b/tests/test_raillabel/validate/schemas/test_raillabel_v2_schema.py deleted file mode 100644 index 33ae8bd..0000000 --- a/tests/test_raillabel/validate/schemas/test_raillabel_v2_schema.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os - -import jsonschema -import pytest - - -def test_metaschema_validation(json_data): - assert jsonschema.validate( - json_data["raillabel_schema"], - json_data["metaschema"] - ) is None - - -def test_sample_data_validation_subschema(json_data): - assert jsonschema.validate( - json_data["openlabel_v1_short"], - json_data["raillabel_schema"] - ) is None - - -def test_sample_data_validation_superschema(json_data): - assert jsonschema.validate( - json_data["openlabel_v1_short"], - json_data["openlabel_v1_schema"] - ) is None - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) diff --git a/tests/test_raillabel/validate/test_validate.py b/tests/test_raillabel/validate/test_validate.py deleted file mode 100644 index b22b28d..0000000 --- a/tests/test_raillabel/validate/test_validate.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - -import raillabel - - -def test_valid_file(json_data): - validation_result = raillabel.validate(json_data["openlabel_v1_short"]) - assert validation_result[0] - - -def test_file_one_type_error(json_data): - data = json_data["openlabel_v1_short"] - - data["openlabel"]["streams"]["lidar"]["uri"] = 42 - - validation_result = raillabel.validate(data) - - assert not validation_result[0] and len(validation_result[1]) == 1 - - -def test_file_two_type_errors(json_data): - data = json_data["openlabel_v1_short"] - - data["openlabel"]["streams"]["lidar"]["uri"] = 42 - data["openlabel"]["coordinate_systems"]["base"]["type"] = "invalid_value" - - validation_result = raillabel.validate(data) - - assert not validation_result[0] and len(validation_result[1]) == 2 - - -def test_valid_file_path(json_data, json_paths): - validation_result = raillabel.validate(json_data["openlabel_v1_short"], str(json_paths["raillabel_schema"])) - assert validation_result[0] - - -def test_invalid_file_path(json_data, json_paths): - json_paths["raillabel_schema"] = str(json_paths["raillabel_schema"]) + "_invalid" - - with pytest.raises(FileNotFoundError): - raillabel.validate(json_data["openlabel_v1_short"], json_paths["raillabel_schema"]) - - -def test_invalid_schema_key(json_data): - with pytest.raises(FileNotFoundError): - raillabel.validate(json_data["openlabel_v1_short"], "invalid_schema") - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) From 3b31c2cbfdbb1737ea598136fb89c334bbaae7c2 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:54:40 +0100 Subject: [PATCH 009/190] refactor: move AttributeType --- raillabel/__init__.py | 2 +- raillabel/_util/__init__.py | 2 -- raillabel/{_util => format/raillabel}/_attribute_type.py | 0 raillabel/format/raillabel/_object_annotation.py | 2 +- raillabel/format/raillabel/element_data_pointer.py | 2 +- .../format/raillabel/test_element_data_pointer.py | 2 +- tests/test_raillabel/format/raillabel/test_object.py | 2 +- 7 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 raillabel/_util/__init__.py rename raillabel/{_util => format/raillabel}/_attribute_type.py (100%) diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 3991e17..3ea3568 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -3,7 +3,7 @@ """Devkit for working with recorded and annotated train ride data from DB.""" from importlib import metadata -from . import _util, format +from . import format from .exceptions import * from .filter.filter import filter from .format import Scene diff --git a/raillabel/_util/__init__.py b/raillabel/_util/__init__.py deleted file mode 100644 index dd5d085..0000000 --- a/raillabel/_util/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 diff --git a/raillabel/_util/_attribute_type.py b/raillabel/format/raillabel/_attribute_type.py similarity index 100% rename from raillabel/_util/_attribute_type.py rename to raillabel/format/raillabel/_attribute_type.py diff --git a/raillabel/format/raillabel/_object_annotation.py b/raillabel/format/raillabel/_object_annotation.py index 941ebdd..6bd484b 100644 --- a/raillabel/format/raillabel/_object_annotation.py +++ b/raillabel/format/raillabel/_object_annotation.py @@ -9,7 +9,7 @@ from pathlib import Path from pkgutil import iter_modules -from ..._util._attribute_type import AttributeType +from ._attribute_type import AttributeType from .object import Object from .sensor import Sensor diff --git a/raillabel/format/raillabel/element_data_pointer.py b/raillabel/format/raillabel/element_data_pointer.py index f0e03cd..0d30a00 100644 --- a/raillabel/format/raillabel/element_data_pointer.py +++ b/raillabel/format/raillabel/element_data_pointer.py @@ -4,7 +4,7 @@ import typing as t from dataclasses import dataclass -from ..._util._attribute_type import AttributeType +from ._attribute_type import AttributeType from .frame_interval import FrameInterval diff --git a/tests/test_raillabel/format/raillabel/test_element_data_pointer.py b/tests/test_raillabel/format/raillabel/test_element_data_pointer.py index 5ee07a4..d3f0eda 100644 --- a/tests/test_raillabel/format/raillabel/test_element_data_pointer.py +++ b/tests/test_raillabel/format/raillabel/test_element_data_pointer.py @@ -9,7 +9,7 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel._util._attribute_type import AttributeType +from raillabel.format.raillabel._attribute_type import AttributeType from raillabel.format import ElementDataPointer # == Fixtures ========================= diff --git a/tests/test_raillabel/format/raillabel/test_object.py b/tests/test_raillabel/format/raillabel/test_object.py index 647289f..bfccad5 100644 --- a/tests/test_raillabel/format/raillabel/test_object.py +++ b/tests/test_raillabel/format/raillabel/test_object.py @@ -12,7 +12,7 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel._util._attribute_type import AttributeType +from raillabel.format.raillabel._attribute_type import AttributeType from raillabel.format import ( Bbox, Cuboid, From 141e2287933d2a1dc2b15a06cf75844e6ff8391b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:56:22 +0100 Subject: [PATCH 010/190] refactor: move raillabel format classes one layer higher --- raillabel/format/__init__.py | 46 +++++++++---------- .../format/{raillabel => }/_attribute_type.py | 0 .../{raillabel => }/_object_annotation.py | 2 +- raillabel/format/{raillabel => }/bbox.py | 0 raillabel/format/{raillabel => }/cuboid.py | 0 .../{raillabel => }/element_data_pointer.py | 0 raillabel/format/{raillabel => }/frame.py | 0 .../format/{raillabel => }/frame_interval.py | 0 .../{raillabel => }/intrinsics_pinhole.py | 0 .../{raillabel => }/intrinsics_radar.py | 0 raillabel/format/{raillabel => }/metadata.py | 0 raillabel/format/{raillabel => }/num.py | 0 raillabel/format/{raillabel => }/object.py | 0 raillabel/format/{raillabel => }/point2d.py | 0 raillabel/format/{raillabel => }/point3d.py | 0 raillabel/format/{raillabel => }/poly2d.py | 0 raillabel/format/{raillabel => }/poly3d.py | 0 .../format/{raillabel => }/quaternion.py | 0 raillabel/format/raillabel/__init__.py | 3 -- raillabel/format/{raillabel => }/scene.py | 2 +- raillabel/format/{raillabel => }/seg3d.py | 0 raillabel/format/{raillabel => }/sensor.py | 0 .../{raillabel => }/sensor_reference.py | 0 raillabel/format/{raillabel => }/size2d.py | 0 raillabel/format/{raillabel => }/size3d.py | 0 raillabel/format/{raillabel => }/transform.py | 0 .../raillabel/test_element_data_pointer.py | 2 +- .../format/raillabel/test_object.py | 2 +- .../format/raillabel/test_scene.py | 2 +- 29 files changed, 28 insertions(+), 31 deletions(-) rename raillabel/format/{raillabel => }/_attribute_type.py (100%) rename raillabel/format/{raillabel => }/_object_annotation.py (98%) rename raillabel/format/{raillabel => }/bbox.py (100%) rename raillabel/format/{raillabel => }/cuboid.py (100%) rename raillabel/format/{raillabel => }/element_data_pointer.py (100%) rename raillabel/format/{raillabel => }/frame.py (100%) rename raillabel/format/{raillabel => }/frame_interval.py (100%) rename raillabel/format/{raillabel => }/intrinsics_pinhole.py (100%) rename raillabel/format/{raillabel => }/intrinsics_radar.py (100%) rename raillabel/format/{raillabel => }/metadata.py (100%) rename raillabel/format/{raillabel => }/num.py (100%) rename raillabel/format/{raillabel => }/object.py (100%) rename raillabel/format/{raillabel => }/point2d.py (100%) rename raillabel/format/{raillabel => }/point3d.py (100%) rename raillabel/format/{raillabel => }/poly2d.py (100%) rename raillabel/format/{raillabel => }/poly3d.py (100%) rename raillabel/format/{raillabel => }/quaternion.py (100%) delete mode 100644 raillabel/format/raillabel/__init__.py rename raillabel/format/{raillabel => }/scene.py (99%) rename raillabel/format/{raillabel => }/seg3d.py (100%) rename raillabel/format/{raillabel => }/sensor.py (100%) rename raillabel/format/{raillabel => }/sensor_reference.py (100%) rename raillabel/format/{raillabel => }/size2d.py (100%) rename raillabel/format/{raillabel => }/size3d.py (100%) rename raillabel/format/{raillabel => }/transform.py (100%) diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 5af563e..4ca2d6b 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -2,26 +2,26 @@ # SPDX-License-Identifier: Apache-2.0 """Module containing all relevant format classes.""" -from .raillabel._object_annotation import _ObjectAnnotation, annotation_classes -from .raillabel.bbox import Bbox -from .raillabel.cuboid import Cuboid -from .raillabel.element_data_pointer import ElementDataPointer -from .raillabel.frame import Frame -from .raillabel.frame_interval import FrameInterval -from .raillabel.intrinsics_pinhole import IntrinsicsPinhole -from .raillabel.intrinsics_radar import IntrinsicsRadar -from .raillabel.metadata import Metadata -from .raillabel.num import Num -from .raillabel.object import Object -from .raillabel.point2d import Point2d -from .raillabel.point3d import Point3d -from .raillabel.poly2d import Poly2d -from .raillabel.poly3d import Poly3d -from .raillabel.quaternion import Quaternion -from .raillabel.scene import Scene -from .raillabel.seg3d import Seg3d -from .raillabel.sensor import Sensor, SensorType -from .raillabel.sensor_reference import SensorReference -from .raillabel.size2d import Size2d -from .raillabel.size3d import Size3d -from .raillabel.transform import Transform +from ._object_annotation import _ObjectAnnotation, annotation_classes +from .bbox import Bbox +from .cuboid import Cuboid +from .element_data_pointer import ElementDataPointer +from .frame import Frame +from .frame_interval import FrameInterval +from .intrinsics_pinhole import IntrinsicsPinhole +from .intrinsics_radar import IntrinsicsRadar +from .metadata import Metadata +from .num import Num +from .object import Object +from .point2d import Point2d +from .point3d import Point3d +from .poly2d import Poly2d +from .poly3d import Poly3d +from .quaternion import Quaternion +from .scene import Scene +from .seg3d import Seg3d +from .sensor import Sensor, SensorType +from .sensor_reference import SensorReference +from .size2d import Size2d +from .size3d import Size3d +from .transform import Transform diff --git a/raillabel/format/raillabel/_attribute_type.py b/raillabel/format/_attribute_type.py similarity index 100% rename from raillabel/format/raillabel/_attribute_type.py rename to raillabel/format/_attribute_type.py diff --git a/raillabel/format/raillabel/_object_annotation.py b/raillabel/format/_object_annotation.py similarity index 98% rename from raillabel/format/raillabel/_object_annotation.py rename to raillabel/format/_object_annotation.py index 6bd484b..30e9206 100644 --- a/raillabel/format/raillabel/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -151,7 +151,7 @@ def _collect_annotation_classes(): package_dir = str(Path(__file__).resolve().parent) for _, module_name, _ in iter_modules([package_dir]): - module = import_module(f"raillabel.format.raillabel.{module_name}") + module = import_module(f"raillabel.format.{module_name}") for attribute_name in dir(module): attribute = getattr(module, attribute_name) diff --git a/raillabel/format/raillabel/bbox.py b/raillabel/format/bbox.py similarity index 100% rename from raillabel/format/raillabel/bbox.py rename to raillabel/format/bbox.py diff --git a/raillabel/format/raillabel/cuboid.py b/raillabel/format/cuboid.py similarity index 100% rename from raillabel/format/raillabel/cuboid.py rename to raillabel/format/cuboid.py diff --git a/raillabel/format/raillabel/element_data_pointer.py b/raillabel/format/element_data_pointer.py similarity index 100% rename from raillabel/format/raillabel/element_data_pointer.py rename to raillabel/format/element_data_pointer.py diff --git a/raillabel/format/raillabel/frame.py b/raillabel/format/frame.py similarity index 100% rename from raillabel/format/raillabel/frame.py rename to raillabel/format/frame.py diff --git a/raillabel/format/raillabel/frame_interval.py b/raillabel/format/frame_interval.py similarity index 100% rename from raillabel/format/raillabel/frame_interval.py rename to raillabel/format/frame_interval.py diff --git a/raillabel/format/raillabel/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py similarity index 100% rename from raillabel/format/raillabel/intrinsics_pinhole.py rename to raillabel/format/intrinsics_pinhole.py diff --git a/raillabel/format/raillabel/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py similarity index 100% rename from raillabel/format/raillabel/intrinsics_radar.py rename to raillabel/format/intrinsics_radar.py diff --git a/raillabel/format/raillabel/metadata.py b/raillabel/format/metadata.py similarity index 100% rename from raillabel/format/raillabel/metadata.py rename to raillabel/format/metadata.py diff --git a/raillabel/format/raillabel/num.py b/raillabel/format/num.py similarity index 100% rename from raillabel/format/raillabel/num.py rename to raillabel/format/num.py diff --git a/raillabel/format/raillabel/object.py b/raillabel/format/object.py similarity index 100% rename from raillabel/format/raillabel/object.py rename to raillabel/format/object.py diff --git a/raillabel/format/raillabel/point2d.py b/raillabel/format/point2d.py similarity index 100% rename from raillabel/format/raillabel/point2d.py rename to raillabel/format/point2d.py diff --git a/raillabel/format/raillabel/point3d.py b/raillabel/format/point3d.py similarity index 100% rename from raillabel/format/raillabel/point3d.py rename to raillabel/format/point3d.py diff --git a/raillabel/format/raillabel/poly2d.py b/raillabel/format/poly2d.py similarity index 100% rename from raillabel/format/raillabel/poly2d.py rename to raillabel/format/poly2d.py diff --git a/raillabel/format/raillabel/poly3d.py b/raillabel/format/poly3d.py similarity index 100% rename from raillabel/format/raillabel/poly3d.py rename to raillabel/format/poly3d.py diff --git a/raillabel/format/raillabel/quaternion.py b/raillabel/format/quaternion.py similarity index 100% rename from raillabel/format/raillabel/quaternion.py rename to raillabel/format/quaternion.py diff --git a/raillabel/format/raillabel/__init__.py b/raillabel/format/raillabel/__init__.py deleted file mode 100644 index cf306c7..0000000 --- a/raillabel/format/raillabel/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Collection of all raillabel format classes.""" diff --git a/raillabel/format/raillabel/scene.py b/raillabel/format/scene.py similarity index 99% rename from raillabel/format/raillabel/scene.py rename to raillabel/format/scene.py index 8402230..876e767 100644 --- a/raillabel/format/raillabel/scene.py +++ b/raillabel/format/scene.py @@ -4,7 +4,7 @@ import typing as t from dataclasses import dataclass, field -from ... import exceptions +from .. import exceptions from .frame import Frame from .frame_interval import FrameInterval from .metadata import Metadata diff --git a/raillabel/format/raillabel/seg3d.py b/raillabel/format/seg3d.py similarity index 100% rename from raillabel/format/raillabel/seg3d.py rename to raillabel/format/seg3d.py diff --git a/raillabel/format/raillabel/sensor.py b/raillabel/format/sensor.py similarity index 100% rename from raillabel/format/raillabel/sensor.py rename to raillabel/format/sensor.py diff --git a/raillabel/format/raillabel/sensor_reference.py b/raillabel/format/sensor_reference.py similarity index 100% rename from raillabel/format/raillabel/sensor_reference.py rename to raillabel/format/sensor_reference.py diff --git a/raillabel/format/raillabel/size2d.py b/raillabel/format/size2d.py similarity index 100% rename from raillabel/format/raillabel/size2d.py rename to raillabel/format/size2d.py diff --git a/raillabel/format/raillabel/size3d.py b/raillabel/format/size3d.py similarity index 100% rename from raillabel/format/raillabel/size3d.py rename to raillabel/format/size3d.py diff --git a/raillabel/format/raillabel/transform.py b/raillabel/format/transform.py similarity index 100% rename from raillabel/format/raillabel/transform.py rename to raillabel/format/transform.py diff --git a/tests/test_raillabel/format/raillabel/test_element_data_pointer.py b/tests/test_raillabel/format/raillabel/test_element_data_pointer.py index d3f0eda..56b8659 100644 --- a/tests/test_raillabel/format/raillabel/test_element_data_pointer.py +++ b/tests/test_raillabel/format/raillabel/test_element_data_pointer.py @@ -9,7 +9,7 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel.format.raillabel._attribute_type import AttributeType +from raillabel.format._attribute_type import AttributeType from raillabel.format import ElementDataPointer # == Fixtures ========================= diff --git a/tests/test_raillabel/format/raillabel/test_object.py b/tests/test_raillabel/format/raillabel/test_object.py index bfccad5..5d1fd57 100644 --- a/tests/test_raillabel/format/raillabel/test_object.py +++ b/tests/test_raillabel/format/raillabel/test_object.py @@ -12,7 +12,7 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel.format.raillabel._attribute_type import AttributeType +from raillabel.format._attribute_type import AttributeType from raillabel.format import ( Bbox, Cuboid, diff --git a/tests/test_raillabel/format/raillabel/test_scene.py b/tests/test_raillabel/format/raillabel/test_scene.py index 5d46ed1..884a61f 100644 --- a/tests/test_raillabel/format/raillabel/test_scene.py +++ b/tests/test_raillabel/format/raillabel/test_scene.py @@ -11,7 +11,7 @@ from raillabel import exceptions from raillabel.format import Frame, FrameInterval, Scene -from raillabel.format.raillabel.scene import _clean_dict +from raillabel.format.scene import _clean_dict # == Fixtures ========================= From 8d0c91ca2e070537ec4e302028c021000a9e3d2d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 15:58:20 +0100 Subject: [PATCH 011/190] test: move tests to reflect src tree --- tests/test_raillabel/format/{raillabel => }/conftest.py | 0 tests/test_raillabel/format/raillabel/__init__.py | 2 -- tests/test_raillabel/format/{raillabel => }/test_attributes.py | 0 tests/test_raillabel/format/{raillabel => }/test_bbox.py | 0 tests/test_raillabel/format/{raillabel => }/test_cuboid.py | 0 .../format/{raillabel => }/test_element_data_pointer.py | 0 tests/test_raillabel/format/{raillabel => }/test_frame.py | 0 .../format/{raillabel => }/test_frame_interval.py | 0 .../format/{raillabel => }/test_intrinsics_pinhole.py | 0 .../format/{raillabel => }/test_intrinsics_radar.py | 0 tests/test_raillabel/format/{raillabel => }/test_metadata.py | 0 tests/test_raillabel/format/{raillabel => }/test_num.py | 0 tests/test_raillabel/format/{raillabel => }/test_object.py | 0 .../format/{raillabel => }/test_object_annotation.py | 0 tests/test_raillabel/format/{raillabel => }/test_object_data.py | 0 tests/test_raillabel/format/{raillabel => }/test_point2d.py | 0 tests/test_raillabel/format/{raillabel => }/test_point3d.py | 0 tests/test_raillabel/format/{raillabel => }/test_poly2d.py | 0 tests/test_raillabel/format/{raillabel => }/test_poly3d.py | 0 tests/test_raillabel/format/{raillabel => }/test_quaternion.py | 0 tests/test_raillabel/format/{raillabel => }/test_scene.py | 0 tests/test_raillabel/format/{raillabel => }/test_seg3d.py | 0 tests/test_raillabel/format/{raillabel => }/test_sensor.py | 0 .../format/{raillabel => }/test_sensor_reference.py | 0 tests/test_raillabel/format/{raillabel => }/test_size2d.py | 0 tests/test_raillabel/format/{raillabel => }/test_size3d.py | 0 tests/test_raillabel/format/{raillabel => }/test_transform.py | 0 tests/test_raillabel/{load_ => load}/test_load.py | 0 28 files changed, 2 deletions(-) rename tests/test_raillabel/format/{raillabel => }/conftest.py (100%) delete mode 100644 tests/test_raillabel/format/raillabel/__init__.py rename tests/test_raillabel/format/{raillabel => }/test_attributes.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_bbox.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_cuboid.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_element_data_pointer.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_frame.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_frame_interval.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_intrinsics_pinhole.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_intrinsics_radar.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_metadata.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_num.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_object.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_object_annotation.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_object_data.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_point2d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_point3d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_poly2d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_poly3d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_quaternion.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_scene.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_seg3d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_sensor.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_sensor_reference.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_size2d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_size3d.py (100%) rename tests/test_raillabel/format/{raillabel => }/test_transform.py (100%) rename tests/test_raillabel/{load_ => load}/test_load.py (100%) diff --git a/tests/test_raillabel/format/raillabel/conftest.py b/tests/test_raillabel/format/conftest.py similarity index 100% rename from tests/test_raillabel/format/raillabel/conftest.py rename to tests/test_raillabel/format/conftest.py diff --git a/tests/test_raillabel/format/raillabel/__init__.py b/tests/test_raillabel/format/raillabel/__init__.py deleted file mode 100644 index dd5d085..0000000 --- a/tests/test_raillabel/format/raillabel/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/test_raillabel/format/raillabel/test_attributes.py b/tests/test_raillabel/format/test_attributes.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_attributes.py rename to tests/test_raillabel/format/test_attributes.py diff --git a/tests/test_raillabel/format/raillabel/test_bbox.py b/tests/test_raillabel/format/test_bbox.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_bbox.py rename to tests/test_raillabel/format/test_bbox.py diff --git a/tests/test_raillabel/format/raillabel/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_cuboid.py rename to tests/test_raillabel/format/test_cuboid.py diff --git a/tests/test_raillabel/format/raillabel/test_element_data_pointer.py b/tests/test_raillabel/format/test_element_data_pointer.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_element_data_pointer.py rename to tests/test_raillabel/format/test_element_data_pointer.py diff --git a/tests/test_raillabel/format/raillabel/test_frame.py b/tests/test_raillabel/format/test_frame.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_frame.py rename to tests/test_raillabel/format/test_frame.py diff --git a/tests/test_raillabel/format/raillabel/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_frame_interval.py rename to tests/test_raillabel/format/test_frame_interval.py diff --git a/tests/test_raillabel/format/raillabel/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_intrinsics_pinhole.py rename to tests/test_raillabel/format/test_intrinsics_pinhole.py diff --git a/tests/test_raillabel/format/raillabel/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_intrinsics_radar.py rename to tests/test_raillabel/format/test_intrinsics_radar.py diff --git a/tests/test_raillabel/format/raillabel/test_metadata.py b/tests/test_raillabel/format/test_metadata.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_metadata.py rename to tests/test_raillabel/format/test_metadata.py diff --git a/tests/test_raillabel/format/raillabel/test_num.py b/tests/test_raillabel/format/test_num.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_num.py rename to tests/test_raillabel/format/test_num.py diff --git a/tests/test_raillabel/format/raillabel/test_object.py b/tests/test_raillabel/format/test_object.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_object.py rename to tests/test_raillabel/format/test_object.py diff --git a/tests/test_raillabel/format/raillabel/test_object_annotation.py b/tests/test_raillabel/format/test_object_annotation.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_object_annotation.py rename to tests/test_raillabel/format/test_object_annotation.py diff --git a/tests/test_raillabel/format/raillabel/test_object_data.py b/tests/test_raillabel/format/test_object_data.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_object_data.py rename to tests/test_raillabel/format/test_object_data.py diff --git a/tests/test_raillabel/format/raillabel/test_point2d.py b/tests/test_raillabel/format/test_point2d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_point2d.py rename to tests/test_raillabel/format/test_point2d.py diff --git a/tests/test_raillabel/format/raillabel/test_point3d.py b/tests/test_raillabel/format/test_point3d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_point3d.py rename to tests/test_raillabel/format/test_point3d.py diff --git a/tests/test_raillabel/format/raillabel/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_poly2d.py rename to tests/test_raillabel/format/test_poly2d.py diff --git a/tests/test_raillabel/format/raillabel/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_poly3d.py rename to tests/test_raillabel/format/test_poly3d.py diff --git a/tests/test_raillabel/format/raillabel/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_quaternion.py rename to tests/test_raillabel/format/test_quaternion.py diff --git a/tests/test_raillabel/format/raillabel/test_scene.py b/tests/test_raillabel/format/test_scene.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_scene.py rename to tests/test_raillabel/format/test_scene.py diff --git a/tests/test_raillabel/format/raillabel/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_seg3d.py rename to tests/test_raillabel/format/test_seg3d.py diff --git a/tests/test_raillabel/format/raillabel/test_sensor.py b/tests/test_raillabel/format/test_sensor.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_sensor.py rename to tests/test_raillabel/format/test_sensor.py diff --git a/tests/test_raillabel/format/raillabel/test_sensor_reference.py b/tests/test_raillabel/format/test_sensor_reference.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_sensor_reference.py rename to tests/test_raillabel/format/test_sensor_reference.py diff --git a/tests/test_raillabel/format/raillabel/test_size2d.py b/tests/test_raillabel/format/test_size2d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_size2d.py rename to tests/test_raillabel/format/test_size2d.py diff --git a/tests/test_raillabel/format/raillabel/test_size3d.py b/tests/test_raillabel/format/test_size3d.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_size3d.py rename to tests/test_raillabel/format/test_size3d.py diff --git a/tests/test_raillabel/format/raillabel/test_transform.py b/tests/test_raillabel/format/test_transform.py similarity index 100% rename from tests/test_raillabel/format/raillabel/test_transform.py rename to tests/test_raillabel/format/test_transform.py diff --git a/tests/test_raillabel/load_/test_load.py b/tests/test_raillabel/load/test_load.py similarity index 100% rename from tests/test_raillabel/load_/test_load.py rename to tests/test_raillabel/load/test_load.py From b891e13bc90ec983d8dc076bba0ca34c02469652 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:14:14 +0100 Subject: [PATCH 012/190] lint: replace isort, black, pydocstyle, pylint with ruff --- .github/workflows/lint.yml | 16 -- .pre-commit-config.yaml | 27 +-- docs/source/conf.py | 4 +- pyproject.toml | 205 +++++++----------- raillabel/__init__.py | 1 + raillabel/filter/_filter_classes/__init__.py | 1 - .../filter/_filter_classes/_filter_abc.py | 2 - .../_filter_classes/_filter_annotation_ids.py | 7 +- .../_filter_annotation_types.py | 7 +- .../_filter_classes/_filter_attributes.py | 27 +-- .../filter/_filter_classes/_filter_end.py | 7 +- .../filter/_filter_classes/_filter_frames.py | 7 +- .../_filter_classes/_filter_object_ids.py | 7 +- .../_filter_classes/_filter_object_types.py | 7 +- .../filter/_filter_classes/_filter_sensors.py | 7 +- .../filter/_filter_classes/_filter_start.py | 7 +- raillabel/filter/filter.py | 8 +- raillabel/format/_attribute_type.py | 17 +- raillabel/format/_object_annotation.py | 9 +- raillabel/format/bbox.py | 5 +- raillabel/format/cuboid.py | 5 +- raillabel/format/element_data_pointer.py | 3 +- raillabel/format/frame.py | 23 +- raillabel/format/frame_interval.py | 8 +- raillabel/format/intrinsics_pinhole.py | 5 +- raillabel/format/intrinsics_radar.py | 5 +- raillabel/format/metadata.py | 7 +- raillabel/format/num.py | 6 +- raillabel/format/object.py | 38 ++-- raillabel/format/point2d.py | 5 +- raillabel/format/point3d.py | 5 +- raillabel/format/poly2d.py | 5 +- raillabel/format/poly3d.py | 5 +- raillabel/format/quaternion.py | 5 +- raillabel/format/scene.py | 20 +- raillabel/format/seg3d.py | 5 +- raillabel/format/sensor.py | 12 +- raillabel/format/sensor_reference.py | 5 +- raillabel/format/size2d.py | 5 +- raillabel/format/size3d.py | 5 +- raillabel/format/transform.py | 5 +- raillabel/load/load.py | 1 + raillabel/save/save.py | 6 +- tests/conftest.py | 10 +- tests/test_raillabel/filter/test_filter.py | 180 +++++++-------- .../test_raillabel/format/test_attributes.py | 97 +++------ tests/test_raillabel/format/test_bbox.py | 36 ++- tests/test_raillabel/format/test_cuboid.py | 50 +++-- .../format/test_element_data_pointer.py | 33 ++- tests/test_raillabel/format/test_frame.py | 99 +++------ .../format/test_frame_interval.py | 21 +- .../format/test_intrinsics_pinhole.py | 98 ++------- .../format/test_intrinsics_radar.py | 4 + tests/test_raillabel/format/test_metadata.py | 49 ++--- tests/test_raillabel/format/test_num.py | 10 +- tests/test_raillabel/format/test_object.py | 146 ++++++------- .../format/test_object_annotation.py | 7 +- .../test_raillabel/format/test_object_data.py | 2 + tests/test_raillabel/format/test_point2d.py | 9 +- tests/test_raillabel/format/test_point3d.py | 9 +- tests/test_raillabel/format/test_poly2d.py | 39 ++-- tests/test_raillabel/format/test_poly3d.py | 38 ++-- .../test_raillabel/format/test_quaternion.py | 33 +-- tests/test_raillabel/format/test_scene.py | 138 +++++++----- tests/test_raillabel/format/test_seg3d.py | 24 +- tests/test_raillabel/format/test_sensor.py | 55 +++-- .../format/test_sensor_reference.py | 38 ++-- tests/test_raillabel/format/test_size2d.py | 8 +- tests/test_raillabel/format/test_size3d.py | 14 +- tests/test_raillabel/format/test_transform.py | 33 +-- tests/test_raillabel/save/test_save.py | 2 - 71 files changed, 787 insertions(+), 1062 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e0a441e..9654df4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,19 +24,3 @@ jobs: - name: Run Pre-Commit run: |- pre-commit run --all-files - pylint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - with: - python-version: "3.12" - - name: Upgrade pip - run: |- - python -m pip install -U pip - - name: Install pylint - run: |- - python -m pip install pylint - - name: Run pylint - run: |- - pylint -dfixme raillabel || exit $(($? & ~24)) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e2a002..bfeef0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,21 +25,16 @@ repos: - id: end-of-file-fixer - id: fix-byte-order-marker - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - repo: https://github.com/PyCQA/pydocstyle - rev: 6.3.0 + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.0 hooks: - - id: pydocstyle - exclude: '^tests/' - additional_dependencies: - - pydocstyle[toml] + - id: ruff-format + name: Run Formatter + - id: ruff + name: Run Linter + args: [ --fix ] + - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: @@ -83,11 +78,13 @@ repos: - LICENSES/.license_header.txt - --comment-style - '..| |' + - repo: https://github.com/fsfe/reuse-tool rev: v4.0.3 hooks: - id: reuse + - repo: https://github.com/qoomon/git-conventional-commits - rev: v2.1.1 + rev: v2.6.7 hooks: - id: conventional-commits diff --git a/docs/source/conf.py b/docs/source/conf.py index 98f3cba..c5bda95 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,9 +66,7 @@ rst_epilog = """ .. |Project| replace:: {project} .. |Version| replace:: {version} -""".format( - project=project, version=version -) +""".format(project=project, version=version) # -- Options for copy-button ------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index e4b42b5..8689255 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 [build-system] -requires = [ - "setuptools>=64", - "setuptools_scm[toml]>=3.4", - "wheel" -] +requires = ["setuptools>=64", "setuptools_scm[toml]>=3.4", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -17,59 +13,91 @@ description = "A devkit for working with recorded and annotated train ride data readme = "README.md" requires-python = ">=3.8, <3.14" license = { text = "Apache-2.0" } -authors = [ - { name = "DB InfraGO AG" }, -] +authors = [{ name = "DB InfraGO AG" }] keywords = [] classifiers = [ - "Development Status :: 1 - Planning", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "jsonschema>=4.4.0", - "fastjsonschema>=2.16.2" + "Development Status :: 1 - Planning", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] +dependencies = ["jsonschema>=4.4.0", "fastjsonschema>=2.16.2"] [project.urls] Homepage = "https://github.com/DSD-DBS/raillabel" Documentation = "https://dsd-dbs.github.io/raillabel" [project.optional-dependencies] -docs = [ - "furo", - "sphinx", - "sphinx-copybutton", - "tomli; python_version<'3.11'", -] - -test = [ - "pytest", - "pytest-cov", - "json5" +docs = ["furo", "sphinx", "sphinx-copybutton", "tomli; python_version<'3.11'"] + +test = ["pytest", "pytest-cov", "json5"] + +[tool.ruff] +line-length = 101 + +[tool.ruff.lint] +exclude = ["tests/*", "docs/*"] +select = ["ALL"] +ignore = [ + "COM812", # conflicts with ruff formatter + + "D100", # imo no docstrings are necessary in public modules + "D107", # __init__ docstrings are not necessary + "D203", # incompatible with D211 + "D213", # incompatible with D212 + + "FBT001", # flags in functions are not bad practice + "FBT002", # flags in functions are not bad practice + + "ISC001", # conflicts with ruff formatter + + "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects + "TCH002", # same as TCH001 + "TCH003", # same as TCH001 + + "SIM103", # less readable in some cases imo + + # Will be removed gradually + "E721", + "SLF001", + "ISC003", + "N806", + "TID252", + "FA100", + "F403", + "EM102", + "F401", + "SIM102", + "RET504", + "INP001", + "ANN003", + "ANN202", + "A001", + "PLW0602", + "N802", + "PERF401", + "ARG003", + "ANN001", + "ANN201", + "S301", + "ANN206", + "TRY003", + "D417", + "A002", + "RUF012", + "ANN204", + "SIM110", + "E741", + "C901", ] -[tool.black] -line-length = 100 -target-version = ["py312"] -force-exclude = "tests/" - -[tool.docformatter] -wrap-descriptions = 72 -wrap-summaries = 79 - -[tool.isort] -profile = 'black' -line_length = 100 - [tool.mypy] check_untyped_defs = true no_implicit_optional = true @@ -83,89 +111,6 @@ module = ["tests.*"] allow_incomplete_defs = true allow_untyped_defs = true -[[tool.mypy.overrides]] -# Untyped third party libraries -module = [ - # ... -] -ignore_missing_imports = true - -[tool.pydocstyle] -convention = "numpy" -add-select = [ - "D212", # Multi-line docstring summary should start at the first line - "D402", # First line should not be the functions "signature" - "D417", # Missing argument descriptions in the docstring -] -add-ignore = [ - "D100", # Missing docstring in public module - "D201", # No blank lines allowed before function docstring # auto-formatting - "D202", # No blank lines allowed after function docstring # auto-formatting - "D203", # 1 blank line required before class docstring # auto-formatting - "D204", # 1 blank line required after class docstring # auto-formatting - "D209", # Multi-line docstring closing quotes should be on a separate line - "D211", # No blank lines allowed before class docstring # auto-formatting - "D213", # Multi-line docstring summary should start at the second line -] - -[tool.pylint.master] -max-line-length = 100 - -[tool.pylint.messages_control] -disable = [ - "arguments-renamed", - "global-statement", - "invalid-name", - "no-else-return", # using else returns is more readible imo - "protected-access", # class comparisons raised as false positive - "redefined-builtin", # the domain is full of builtin-names (object, type, format, ...) - "too-few-public-methods", # does not contribute to code quality imo - "too-many-arguments", # 6 as a limit is too low - "too-many-instance-attributes", # classes mirror OpenLABEL, therefore the number of fields is set - "unidiomatic-typecheck", # type() is necessary in some cases - "unspecified-encoding", # default encoding is sufficient in all cases - "unsupported-membership-test", # raise false positives for dicts - "global-variable-not-assigned", # raises false positive when global variable is a dict and items are assigned - - # Auto-formatting - "bad-indentation", - "inconsistent-quotes", - "missing-final-newline", - "missing-class-docstring", - "missing-function-docstring", - "missing-module-docstring", - "mixed-line-endings", - "multiple-imports", - "multiple-statements", - "trailing-newlines", - "trailing-whitespace", - "unexpected-line-ending-format", - "ungrouped-imports", - "wrong-import-order", - "wrong-import-position", - - # Handled by mypy - "arguments-differ", - "assignment-from-no-return", - "import-error", - "missing-kwoa", - "no-member", - "no-value-for-parameter", - "redundant-keyword-arg", - "signature-differs", - "syntax-error", - "too-many-function-args", - "unbalanced-tuple-unpacking", - "undefined-variable", - "unexpected-keyword-arg", -] -enable = [ - "c-extension-no-member", - "deprecated-pragma", - "use-symbolic-message-instead", - "useless-suppression", -] - [tool.pytest.ini_options] addopts = """ --strict-config diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 3ea3568..bfcb251 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Devkit for working with recorded and annotated train ride data from DB.""" + from importlib import metadata from . import format diff --git a/raillabel/filter/_filter_classes/__init__.py b/raillabel/filter/_filter_classes/__init__.py index 190e8a8..097c89c 100644 --- a/raillabel/filter/_filter_classes/__init__.py +++ b/raillabel/filter/_filter_classes/__init__.py @@ -10,7 +10,6 @@ # iterate through the modules in the current package package_dir = str(Path(__file__).resolve().parent) for _, module_name, _ in iter_modules([package_dir]): - # import the module and iterate through its attributes module = import_module(f"{__name__}.{module_name}") for attribute_name in dir(module): diff --git a/raillabel/filter/_filter_classes/_filter_abc.py b/raillabel/filter/_filter_classes/_filter_abc.py index 237e3a1..5de6b43 100644 --- a/raillabel/filter/_filter_classes/_filter_abc.py +++ b/raillabel/filter/_filter_classes/_filter_abc.py @@ -32,11 +32,9 @@ def LEVELS(self) -> t.List[str]: raise NotImplementedError def __init__(self, kwargs): - set_parameter = None for param in self.PARAMETERS: if param in kwargs and param is not None: - if set_parameter is None: setattr(self, param, self._process_filter_args(kwargs[param])) set_parameter = param diff --git a/raillabel/filter/_filter_classes/_filter_annotation_ids.py b/raillabel/filter/_filter_classes/_filter_annotation_ids.py index 4ff50f5..8ed3738 100644 --- a/raillabel/filter/_filter_classes/_filter_annotation_ids.py +++ b/raillabel/filter/_filter_classes/_filter_annotation_ids.py @@ -7,17 +7,14 @@ class _FilterAnnotationIds(_FilterABC): - PARAMETERS = ["include_annotation_ids", "exclude_annotation_ids"] LEVELS = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_annotation_ids is not None: return annotation.uid in self.include_annotation_ids - elif self.exclude_annotation_ids is not None: + if self.exclude_annotation_ids is not None: return annotation.uid not in self.exclude_annotation_ids - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_annotation_types.py b/raillabel/filter/_filter_classes/_filter_annotation_types.py index a929983..328bbc5 100644 --- a/raillabel/filter/_filter_classes/_filter_annotation_types.py +++ b/raillabel/filter/_filter_classes/_filter_annotation_types.py @@ -7,20 +7,17 @@ class _FilterAnnotationTypes(_FilterABC): - PARAMETERS = ["include_annotation_types", "exclude_annotation_types"] LEVELS = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_annotation_types is not None: return annotation.__class__.__name__.lower() in self.include_annotation_types - elif self.exclude_annotation_types is not None: + if self.exclude_annotation_types is not None: return annotation.__class__.__name__.lower() not in self.exclude_annotation_types - else: - return True + return True def _process_filter_args(self, filter_args): return [arg.lower() for arg in filter_args] diff --git a/raillabel/filter/_filter_classes/_filter_attributes.py b/raillabel/filter/_filter_classes/_filter_attributes.py index 01d3450..d4f93dd 100644 --- a/raillabel/filter/_filter_classes/_filter_attributes.py +++ b/raillabel/filter/_filter_classes/_filter_attributes.py @@ -7,39 +7,32 @@ class _FilterAttributes(_FilterABC): - PARAMETERS = ["include_attributes", "exclude_attributes"] LEVELS = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_attributes is not None: for attribute_id, attribute_val in self.include_attributes.items(): - if attribute_val is None: if attribute_id not in annotation.attributes: return False - else: - if ( - attribute_id not in annotation.attributes - or annotation.attributes[attribute_id] != attribute_val - ): - return False + elif ( + attribute_id not in annotation.attributes + or annotation.attributes[attribute_id] != attribute_val + ): + return False elif self.exclude_attributes is not None: - for attribute_id, attribute_val in self.exclude_attributes.items(): - if attribute_val is None: if attribute_id in annotation.attributes: return False - else: - if ( - attribute_id in annotation.attributes - and attribute_val == annotation.attributes[attribute_id] - ): - return False + elif ( + attribute_id in annotation.attributes + and attribute_val == annotation.attributes[attribute_id] + ): + return False return True diff --git a/raillabel/filter/_filter_classes/_filter_end.py b/raillabel/filter/_filter_classes/_filter_end.py index 5673447..f7edc7b 100644 --- a/raillabel/filter/_filter_classes/_filter_end.py +++ b/raillabel/filter/_filter_classes/_filter_end.py @@ -7,17 +7,14 @@ class _FilterEnd(_FilterABC): - PARAMETERS = ["end_frame", "end_timestamp"] LEVELS = ["frame"] def passes_filter(self, frame: Frame) -> bool: - if self.end_frame is not None: return frame.uid <= self.end_frame - elif self.end_timestamp is not None: + if self.end_timestamp is not None: return frame.timestamp <= Decimal(self.end_timestamp) - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_frames.py b/raillabel/filter/_filter_classes/_filter_frames.py index 6cfd7fd..02130fc 100644 --- a/raillabel/filter/_filter_classes/_filter_frames.py +++ b/raillabel/filter/_filter_classes/_filter_frames.py @@ -5,17 +5,14 @@ class _FilterFrame(_FilterABC): - PARAMETERS = ["include_frames", "exclude_frames"] LEVELS = ["frame"] def passes_filter(self, frame: Frame) -> bool: - if self.include_frames is not None: return int(frame.uid) in self.include_frames - elif self.exclude_frames is not None: + if self.exclude_frames is not None: return int(frame.uid) not in self.exclude_frames - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_object_ids.py b/raillabel/filter/_filter_classes/_filter_object_ids.py index aa293fa..600ecaa 100644 --- a/raillabel/filter/_filter_classes/_filter_object_ids.py +++ b/raillabel/filter/_filter_classes/_filter_object_ids.py @@ -7,17 +7,14 @@ class _FilterObjectIds(_FilterABC): - PARAMETERS = ["include_object_ids", "exclude_object_ids"] LEVELS = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_object_ids is not None: return annotation.object.uid in self.include_object_ids - elif self.exclude_object_ids is not None: + if self.exclude_object_ids is not None: return annotation.object.uid not in self.exclude_object_ids - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_object_types.py b/raillabel/filter/_filter_classes/_filter_object_types.py index 2c652b3..35150d5 100644 --- a/raillabel/filter/_filter_classes/_filter_object_types.py +++ b/raillabel/filter/_filter_classes/_filter_object_types.py @@ -7,17 +7,14 @@ class _FilterObjectTypes(_FilterABC): - PARAMETERS = ["include_object_types", "exclude_object_types"] LEVELS = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_object_types is not None: return annotation.object.type in self.include_object_types - elif self.exclude_object_types is not None: + if self.exclude_object_types is not None: return annotation.object.type not in self.exclude_object_types - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_sensors.py b/raillabel/filter/_filter_classes/_filter_sensors.py index 2bdf2b4..5c4f98e 100644 --- a/raillabel/filter/_filter_classes/_filter_sensors.py +++ b/raillabel/filter/_filter_classes/_filter_sensors.py @@ -7,17 +7,14 @@ class _FilterSensors(_FilterABC): - PARAMETERS = ["include_sensors", "exclude_sensors"] LEVELS = ["frame_data", "annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_sensors is not None: return annotation.sensor.uid in self.include_sensors - elif self.exclude_sensors is not None: + if self.exclude_sensors is not None: return annotation.sensor.uid not in self.exclude_sensors - else: - return True + return True diff --git a/raillabel/filter/_filter_classes/_filter_start.py b/raillabel/filter/_filter_classes/_filter_start.py index ab1e00b..684049b 100644 --- a/raillabel/filter/_filter_classes/_filter_start.py +++ b/raillabel/filter/_filter_classes/_filter_start.py @@ -7,17 +7,14 @@ class _FilterStart(_FilterABC): - PARAMETERS = ["start_frame", "start_timestamp"] LEVELS = ["frame"] def passes_filter(self, frame: Frame) -> bool: - if self.start_frame is not None: return frame.uid >= self.start_frame - elif self.start_timestamp is not None: + if self.start_timestamp is not None: return frame.timestamp >= Decimal(self.start_timestamp) - else: - return True + return True diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index afa17bf..6acf867 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -82,8 +82,8 @@ def filter(scene: format.Scene, **kwargs) -> format.Scene: if two mutually exclusive parameters are set. TypeError if an unexpected keyword argument has been set. - """ + """ filters_by_level = _collect_filter_classes(kwargs) filtered_scene, used_sensors, used_objects = _filter_scene(_copy(scene), filters_by_level) filtered_scene = _remove_unused(filtered_scene, used_sensors, used_objects) @@ -138,18 +138,15 @@ def _seperate_filters_by_level(filters: t.List[t.Type]) -> t.Dict[str, t.List[t. def _filter_scene( scene: format.Scene, filters_by_level: t.Dict[str, t.List[t.Type]] ) -> t.Tuple[format.Scene, t.Set[str], t.Set[str]]: - used_sensors = set() used_objects = set() for frame_id, frame in list(scene.frames.items()): - if not _passes_filters(frame, filters_by_level["frame"]): del scene.frames[frame_id] continue for frame_data_id, frame_data in list(frame.frame_data.items()): - if _passes_filters(frame_data, filters_by_level["frame_data"]): used_sensors.add(frame_data.sensor.uid) @@ -157,7 +154,6 @@ def _filter_scene( del scene.frames[frame_id].frame_data[frame_data_id] for annotation_id, annotation in list(frame.annotations.items()): - if _passes_filters(annotation, filters_by_level["annotation"]): used_objects.add(annotation.object.uid) used_sensors.add(annotation.sensor.uid) @@ -174,12 +170,10 @@ def _filter_scene( def _remove_unused( scene: format.Scene, used_sensors: t.Set[str], used_objects: t.Set[str] ) -> format.Scene: - scene = _remove_unused_sensors(scene, used_sensors) scene = _remove_unused_objects(scene, used_objects) for frame_id in scene.frames: - scene.frames[frame_id] = _remove_unused_sensor_references( scene.frames[frame_id], used_sensors ) diff --git a/raillabel/format/_attribute_type.py b/raillabel/format/_attribute_type.py index 3fff392..9238131 100644 --- a/raillabel/format/_attribute_type.py +++ b/raillabel/format/_attribute_type.py @@ -31,22 +31,21 @@ def from_value(cls, attribute_value_class: t.Type) -> "AttributeType": ------ ValueError if attribute value class does not correspond to an Attribute Type. - """ + """ if attribute_value_class == str: return AttributeType.TEXT - elif attribute_value_class in [float, int]: + if attribute_value_class in [float, int]: return AttributeType.NUM - elif attribute_value_class == bool: + if attribute_value_class == bool: return AttributeType.BOOLEAN - elif attribute_value_class in [list, tuple]: + if attribute_value_class in [list, tuple]: return AttributeType.VEC - else: - raise ValueError( - f"Type {attribute_value_class} does not correspond to a valid RailLabel attribute " - + "type. Supported types are str, float, int, bool, list, tuple." - ) + raise ValueError( + f"Type {attribute_value_class} does not correspond to a valid RailLabel attribute " + + "type. Supported types are str, float, int, bool, list, tuple." + ) diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 30e9206..3613841 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -16,7 +16,6 @@ @dataclass class _ObjectAnnotation(ABC): - uid: str object: Object sensor: t.Optional[Sensor] = None @@ -69,7 +68,6 @@ def _annotation_required_fields_asdict(self) -> t.Dict: def _annotation_optional_fields_asdict(self) -> t.Dict: """Return the optional fields from the parent class to dict.""" - dict_repr = {} if self.sensor is not None: @@ -84,7 +82,6 @@ def _attributes_asdict(self, attributes: dict) -> dict: attributes_dict = {} for attr_name, attr_value in attributes.items(): - attr_type = AttributeType.from_value(type(attr_value)).value if attr_type not in attributes_dict: @@ -96,7 +93,6 @@ def _attributes_asdict(self, attributes: dict) -> dict: @classmethod def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Optional[Sensor]: - is_coordinate_system_in_data = ( "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" ) @@ -111,7 +107,6 @@ def _attributes_fromdict( cls, data_dict: dict, ) -> t.Dict[str, t.Union[int, float, bool, str, list]]: - if "attributes" not in data_dict: return {} @@ -131,8 +126,8 @@ def __post_init__(self): ------ TypeError If a required field has not been set. - """ + """ for f in self._REQ_FIELDS: if getattr(self, f) is None: raise TypeError(f"{f} is a required argument for {self.__class__.__name__}") @@ -145,12 +140,10 @@ def annotation_classes() -> t.Dict[str, t.Type[_ObjectAnnotation]]: def _collect_annotation_classes(): """Collect annotation child classes and store them.""" - global ANNOTATION_CLASSES package_dir = str(Path(__file__).resolve().parent) for _, module_name, _ in iter_modules([package_dir]): - module = import_module(f"raillabel.format.{module_name}") for attribute_name in dir(module): attribute = getattr(module, attribute_name) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 3b254b2..5f44cb2 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -33,6 +33,7 @@ class Bbox(_ObjectAnnotation): ---------------------- name: str Name of the annotation used by the VCD player for indexing in the object data pointers. + """ pos: Point2d = None @@ -58,8 +59,8 @@ def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Bbox": ------- annotation: Bbox Converted annotation. - """ + """ return Bbox( uid=str(data_dict["uid"]), pos=Point2d(x=data_dict["val"][0], y=data_dict["val"][1]), @@ -81,8 +82,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = self._annotation_required_fields_asdict() dict_repr["val"] = [ diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index f4f88ff..e218d05 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -37,6 +37,7 @@ class Cuboid(_ObjectAnnotation): ---------------------- name: str Name of the annotation used by the VCD player for indexing in the object data pointers. + """ pos: Point3d = None @@ -63,8 +64,8 @@ def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Cuboid": ------- annotation: Cuboid Converted annotation. - """ + """ return Cuboid( uid=str(data_dict["uid"]), pos=Point3d( @@ -100,8 +101,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = self._annotation_required_fields_asdict() dict_repr["val"] = [ diff --git a/raillabel/format/element_data_pointer.py b/raillabel/format/element_data_pointer.py index 0d30a00..d88a8e8 100644 --- a/raillabel/format/element_data_pointer.py +++ b/raillabel/format/element_data_pointer.py @@ -27,6 +27,7 @@ class ElementDataPointer: ---------------------- uid: str Unique identifier of the ElementDataPointer built from the attributes. + """ uid: str @@ -50,8 +51,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return { "type": self.annotation_type, "frame_intervals": self._frame_intervals_asdict(), diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index ebf0bee..7ec9068 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -38,6 +38,7 @@ class Frame: object_data: dict[str, dict[str, _ObjectAnnotation subclass]] Annotations categorized by object. Keys are object uids and values are the annotations as a dict, that are part of the object. + """ uid: int @@ -55,8 +56,8 @@ def object_data(self) -> t.Dict[str, t.Dict[str, t.Type[_ObjectAnnotation]]]: dict[str, dict[UUID, _ObjectAnnotation subclass]] Dictionary of annotations. Keys are object uids and values are annotations, that are contained in the object. - """ + """ object_data = {} for ann_id, annotation in self.annotations.items(): if annotation.object.uid not in object_data: @@ -91,6 +92,7 @@ def fromdict( ------- frame: raillabel.format.Frame Converted Frame object. + """ frame = Frame( uid=int(uid), @@ -113,8 +115,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = {} if self.timestamp is not None or self.sensors != {} or self.frame_data != {}: @@ -140,7 +142,6 @@ def asdict(self) -> dict: @classmethod def _timestamp_fromdict(cls, data_dict: dict) -> t.Optional[decimal.Decimal]: - if "frame_properties" not in data_dict or "timestamp" not in data_dict["frame_properties"]: return None @@ -150,7 +151,6 @@ def _timestamp_fromdict(cls, data_dict: dict) -> t.Optional[decimal.Decimal]: def _sensors_fromdict( cls, data_dict: dict, frame_uid: int, scene_sensors: t.Dict[str, Sensor] ) -> t.Dict[str, SensorReference]: - if "frame_properties" not in data_dict or "streams" not in data_dict["frame_properties"]: return {} @@ -164,10 +164,7 @@ def _sensors_fromdict( return sensors @classmethod - def _frame_data_fromdict( - cls, data_dict: dict, sensors: t.Dict[str, Sensor] - ) -> t.Dict[str, Num]: - + def _frame_data_fromdict(cls, data_dict: dict, sensors: t.Dict[str, Sensor]) -> t.Dict[str, Num]: if "frame_properties" not in data_dict or "frame_data" not in data_dict["frame_properties"]: return {} @@ -186,7 +183,6 @@ def _objects_fromdict( objects: t.Dict[str, Object], sensors: t.Dict[str, Sensor], ) -> t.Dict[uuid.UUID, t.Type[_ObjectAnnotation]]: - if "objects" not in data_dict: return {} @@ -211,12 +207,10 @@ def _object_annotations_fromdict( object: Object, sensors: t.Dict[str, Sensor], ) -> t.Iterator[t.Type[_ObjectAnnotation]]: - for ann_type, annotations_raw in data_dict.items(): for ann_raw in annotations_raw: yield annotation_classes()[ann_type].fromdict(ann_raw, sensors, object) - def _annotations_asdict(self) -> dict: annotations_dict = {} for object_id, annotations in self.object_data.items(): @@ -234,7 +228,6 @@ def _annotations_asdict(self) -> dict: def __eq__(self, other) -> bool: """Handel equal comparisons.""" - if not hasattr(other, "__dict__"): return False @@ -242,13 +235,11 @@ def __eq__(self, other) -> bool: return False for attr in self.__dict__: - if type(getattr(self, attr)) == type(self): if getattr(self, attr).uid != getattr(other, attr).uid: return False - else: - if getattr(self, attr) != getattr(other, attr): - return False + elif getattr(self, attr) != getattr(other, attr): + return False return True diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 5d5d728..8243c87 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -15,6 +15,7 @@ class FrameInterval: Initial frame number of the interval (inclusive). frame_end: int Ending frame number of the interval (inclusive). + """ frame_start: int @@ -28,8 +29,8 @@ def fromdict(cls, data_dict: dict) -> "FrameInterval": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return FrameInterval( frame_start=data_dict["frame_start"], frame_end=data_dict["frame_end"], @@ -57,8 +58,8 @@ def from_frame_uids(cls, frame_uids: t.List[int]) -> t.List["FrameInterval"]: FrameInterval(9, 9), FrameInterval(12, 14), ] - """ + """ sorted_frame_uids = sorted(frame_uids) frame_uid_intervals = cls._slice_into_intervals(sorted_frame_uids) @@ -80,8 +81,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return { "frame_start": int(self.frame_start), "frame_end": int(self.frame_end), @@ -93,7 +94,6 @@ def __len__(self) -> int: @classmethod def _slice_into_intervals(cls, sorted_frame_uids: t.List[int]) -> t.List[t.List[int]]: - if len(sorted_frame_uids) == 0: return [] diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 4ce1a09..2e59cf4 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -24,6 +24,7 @@ class IntrinsicsPinhole: Width of the image frame in pixels. height_px: int Height of the image frame in pixels. + """ camera_matrix: t.Tuple[float, ...] @@ -44,8 +45,8 @@ def fromdict(cls, data_dict: dict) -> "IntrinsicsPinhole": ------- raillabel.format.IntrinsicsPinhole Converted IntrinsicsPinhole object. - """ + """ return IntrinsicsPinhole( camera_matrix=tuple(data_dict["camera_matrix"]), distortion=tuple(data_dict["distortion_coeffs"]), @@ -65,8 +66,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = { "camera_matrix": list(self.camera_matrix), "distortion_coeffs": list(self.distortion), diff --git a/raillabel/format/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py index 63c9acb..2097871 100644 --- a/raillabel/format/intrinsics_radar.py +++ b/raillabel/format/intrinsics_radar.py @@ -17,6 +17,7 @@ class IntrinsicsRadar: Width of the cartesian image frame in pixels. height_px: int Height of the cartesian image frame in pixels. + """ resolution_px_per_m: float @@ -36,8 +37,8 @@ def fromdict(cls, data_dict: dict) -> "IntrinsicsRadar": ------- raillabel.format.IntrinsicsRadar Converted IntrinsicsRadar object. - """ + """ return IntrinsicsRadar( resolution_px_per_m=data_dict["resolution_px_per_m"], width_px=data_dict["width_px"], @@ -56,8 +57,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return { "resolution_px_per_m": float(self.resolution_px_per_m), "width_px": int(self.width_px), diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index 3c06667..09522f2 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -48,6 +48,7 @@ class Metadata: Version number of the RailLabel schema this annotation object follows. Default is None. tagged_file: str, optional Directory with the exported data_dict (e.g. images, point clouds). Default is None. + """ schema_version: str @@ -75,8 +76,8 @@ def fromdict(cls, data_dict: dict, subschema_version: t.Optional[str] = None) -> ------- metadata: Metadata Converted metadata. - """ + """ metadata = Metadata( schema_version=data_dict["schema_version"], subschema_version=subschema_version, @@ -92,13 +93,12 @@ def asdict(self) -> dict: ------- dict_repr: dict Dict representation of this class instance. - """ + """ return self._remove_empty_fields(vars(self)) @classmethod def _collect_exporter_version(cls) -> t.Optional[str]: - try: exporter_version = importlib_metadata.version("raillabel") except importlib_metadata.PackageNotFoundError: @@ -109,7 +109,6 @@ def _collect_exporter_version(cls) -> t.Optional[str]: @classmethod def _set_additional_attributes(cls, metadata: "Metadata", data_dict: dict) -> "Metadata": - PRESET_KEYS = ["schema_version", "subschema_version", "exporter_version"] for key, value in data_dict.items(): diff --git a/raillabel/format/num.py b/raillabel/format/num.py index c9a5dc4..a71e396 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -24,6 +24,7 @@ class Num: attribute values. Default is {}. sensor: raillabel.format.Sensor, optional A reference to the sensor, this value is represented in. Default is None. + """ uid: str @@ -46,8 +47,8 @@ def fromdict(cls, data_dict: dict, sensors: dict) -> "Num": ------- annotation: Num Converted annotation. - """ + """ return Num( uid=str(data_dict["uid"]), name=str(data_dict["name"]), @@ -67,8 +68,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return { "uid": str(self.uid), "name": str(self.name), @@ -78,7 +79,6 @@ def asdict(self) -> dict: @classmethod def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Optional[Sensor]: - is_coordinate_system_in_data = ( "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" ) diff --git a/raillabel/format/object.py b/raillabel/format/object.py index b0649a4..702e2f6 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -2,9 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t -from dataclasses import dataclass - from _collections_abc import dict_values +from dataclasses import dataclass from .element_data_pointer import AttributeType, ElementDataPointer from .frame_interval import FrameInterval @@ -26,6 +25,7 @@ class Object: name is used followed by an underscore and an integer (i.e. person_0032). type: str The type of an object defines the class the object corresponds to. + """ uid: str @@ -49,6 +49,7 @@ def fromdict(cls, data_dict: dict, object_uid: str) -> "Object": ------- object: raillabel.format.Object Converted object. + """ return Object(uid=object_uid, type=data_dict["type"], name=data_dict["name"]) @@ -67,20 +68,19 @@ def asdict(self, frames: t.Optional[t.Dict[int, "Frame"]] = None) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ if frames is None: return {"name": str(self.name), "type": str(self.type)} - else: - return { - "name": str(self.name), - "type": str(self.type), - "frame_intervals": self._frame_intervals_asdict(self.frame_intervals(frames)), - "object_data_pointers": self._object_data_pointers_asdict( - self.object_data_pointers(frames) - ), - } + return { + "name": str(self.name), + "type": str(self.type), + "frame_intervals": self._frame_intervals_asdict(self.frame_intervals(frames)), + "object_data_pointers": self._object_data_pointers_asdict( + self.object_data_pointers(frames) + ), + } def frame_intervals(self, frames: t.Dict[int, "Frame"]) -> t.List[FrameInterval]: """Return frame intervals in which this object is present. @@ -94,8 +94,8 @@ def frame_intervals(self, frames: t.Dict[int, "Frame"]) -> t.List[FrameInterval] ------- list[FrameInterval] List of the FrameIntervals, where this object is contained. - """ + """ frame_uids_containing_object = [ frame.uid for frame in frames.values() if self._is_object_in_frame(frame) ] @@ -114,8 +114,8 @@ def object_data_pointers(self, frames: t.Dict[int, "Frame"]) -> t.Dict[str, Elem ------- dict[str, ElementDataPointer] ObjectDataPointers dict as required by WebLABEL. Keys are the ObjectDataPointer uids. - """ + """ pointer_ids_per_frame = self._collect_pointer_ids_per_frame(frames) frame_uids_per_pointer_id = self._reverse_frame_pointer_ids(pointer_ids_per_frame) frame_intervals_per_pointer_id = self._convert_to_intervals(frame_uids_per_pointer_id) @@ -137,9 +137,7 @@ def _frame_intervals_asdict(self, frame_intervals: t.List[FrameInterval]) -> dic def _object_data_pointers_asdict( self, object_data_pointers: t.Dict[str, ElementDataPointer] ) -> dict: - return { - pointer_id: pointer.asdict() for pointer_id, pointer in object_data_pointers.items() - } + return {pointer_id: pointer.asdict() for pointer_id, pointer in object_data_pointers.items()} def _is_object_in_frame(self, frame: "Frame") -> bool: return self.uid in frame.object_data @@ -150,7 +148,6 @@ def _filtered_annotations(self, frame: "Frame") -> dict_values: def _collect_pointer_ids_per_frame( self, frames: t.Dict[int, "Frame"] ) -> t.Dict[int, t.Set[str]]: - pointer_ids_per_frame = {} for frame in frames.values(): pointer_ids_per_frame[frame.uid] = set() @@ -163,11 +160,9 @@ def _collect_pointer_ids_per_frame( def _reverse_frame_pointer_ids( self, pointer_ids_per_frame: t.Dict[int, t.Set[str]] ) -> t.Dict[str, t.Set[int]]: - frame_uids_per_pointer_id = {} for frame_uid, pointer_ids in pointer_ids_per_frame.items(): for pointer_id in pointer_ids: - if pointer_id not in frame_uids_per_pointer_id: frame_uids_per_pointer_id[pointer_id] = set() @@ -178,7 +173,6 @@ def _reverse_frame_pointer_ids( def _convert_to_intervals( self, frame_uids_per_pointer_id: t.Dict[str, t.Set[int]] ) -> t.Dict[str, t.List[FrameInterval]]: - frame_intervals = {} for pointer_id, frame_uids in frame_uids_per_pointer_id.items(): frame_intervals[pointer_id] = FrameInterval.from_frame_uids(list(frame_uids)) @@ -188,7 +182,6 @@ def _convert_to_intervals( def _collect_attributes_per_pointer_id( self, frames: t.Dict[int, "Frame"] ) -> t.Dict[str, t.Dict[str, t.Any]]: - attributes_per_pointer_id = {} for frame in frames.values(): for annotation in self._filtered_annotations(frame): @@ -213,7 +206,6 @@ def _create_object_data_pointers( frame_intervals_per_pointer_id: t.Dict[str, t.List[FrameInterval]], attribute_pointers_per_pointer_id: t.Dict[str, t.Dict[str, AttributeType]], ) -> t.Dict[str, ElementDataPointer]: - object_data_pointers = {} for pointer_id in frame_intervals_per_pointer_id: object_data_pointers[pointer_id] = ElementDataPointer( diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 37d913f..57efe30 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -14,6 +14,7 @@ class Point2d: The x-coordinate of the point in the image. y: float or int The y-coordinate of the point in the image. + """ x: float @@ -27,8 +28,8 @@ def fromdict(cls, data_dict: dict) -> "Point2d": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Point2d( x=data_dict[0], y=data_dict[1], @@ -46,6 +47,6 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return [float(self.x), float(self.y)] diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index fe09775..2a4d5f4 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -16,6 +16,7 @@ class Point3d: The y-coordinate of the point. z: float or int The z-coordinate of the point. + """ x: float @@ -30,8 +31,8 @@ def fromdict(cls, data_dict: dict) -> "Point3d": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Point3d( x=data_dict[0], y=data_dict[1], @@ -50,6 +51,6 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index cfa9e68..a6a19fc 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -41,6 +41,7 @@ class Poly2d(_ObjectAnnotation): ---------------------- name: str Name of the annotation used by the VCD player for indexing in the object data pointers. + """ points: t.List[Point2d] = None @@ -67,8 +68,8 @@ def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly2d": ------- annotation: Poly2d Converted annotation. - """ + """ return Poly2d( uid=str(data_dict["uid"]), closed=data_dict["closed"], @@ -91,8 +92,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = self._annotation_required_fields_asdict() dict_repr["closed"] = bool(self.closed) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 28e092b..bc9ae0e 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -34,6 +34,7 @@ class Poly3d(_ObjectAnnotation): ---------------------- name: str Name of the annotation used by the VCD player for indexing in the object data pointers. + """ points: t.List[Point3d] = None @@ -59,8 +60,8 @@ def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly3d": ------- annotation: Poly3d Converted annotation. - """ + """ return Poly3d( uid=str(data_dict["uid"]), closed=data_dict["closed"], @@ -82,8 +83,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = self._annotation_required_fields_asdict() dict_repr["closed"] = bool(self.closed) diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index 2e77405..4462a17 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -18,6 +18,7 @@ class Quaternion: The z component of the quaternion. w: float or int The w component of the quaternion. + """ x: float @@ -33,8 +34,8 @@ def fromdict(cls, data_dict: dict) -> "Quaternion": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Quaternion( x=data_dict[0], y=data_dict[1], @@ -54,6 +55,6 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return [float(self.x), float(self.y), float(self.z), float(self.w)] diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 876e767..793cd48 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -31,6 +31,7 @@ class Scene: ---------------------- frame_intervals: list[FrameIntervals] List of frame intervals describing the frames present in this scene. + """ metadata: Metadata @@ -69,8 +70,8 @@ def fromdict(cls, data_dict: dict, subschema_version: t.Optional[str] = None) -> if a coordinate system has no corresponding stream. raillabel.exceptions.UnsupportedParentError if a coordinate system has no corresponding stream. - """ + """ data_dict = cls._prepare_data(data_dict) sensors = cls._sensors_fromdict(data_dict["streams"], data_dict["coordinate_systems"]) @@ -98,8 +99,8 @@ def asdict(self, calculate_pointers: bool = True) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return { "openlabel": _clean_dict( { @@ -130,8 +131,8 @@ def _prepare_data(cls, data: dict) -> dict: ------- dict Enhanced JSON data. - """ + """ if "coordinate_systems" not in data["openlabel"]: data["openlabel"]["coordinate_systems"] = {} @@ -150,7 +151,6 @@ def _prepare_data(cls, data: dict) -> dict: def _sensors_fromdict( cls, streams_dict: dict, coordinate_systems_dict: dict ) -> t.Dict[str, Sensor]: - cls._check_sensor_completeness(streams_dict, coordinate_systems_dict) sensors = {} @@ -166,7 +166,6 @@ def _sensors_fromdict( @classmethod def _check_sensor_completeness(cls, streams_dict: dict, coordinate_systems_dict: dict): - for stream_uid in streams_dict: if stream_uid not in coordinate_systems_dict: raise exceptions.MissingCoordinateSystemError( @@ -196,7 +195,6 @@ def _objects_fromdict(cls, object_dict: dict) -> t.Dict[str, Object]: def _frames_fromdict( cls, frames_dict: dict, sensors: t.Dict[str, Sensor], objects: t.Dict[str, Object] ) -> t.Dict[int, Frame]: - frames = {} for frame_uid, frame_dict in frames_dict.items(): frames[int(frame_uid)] = Frame.fromdict(frame_uid, frame_dict, objects, sensors) @@ -209,7 +207,6 @@ def _streams_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: return {uid: sensor.asdict()["stream"] for uid, sensor in sensors.items()} def _coordinate_systems_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: - if len(sensors) == 0: return None @@ -222,11 +219,9 @@ def _coordinate_systems_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: return coordinate_systems def _objects_asdict(self, objects: t.Dict[str, Object], calculate_pointers: bool) -> dict: - if calculate_pointers: return {str(uid): object.asdict(self.frames) for uid, object in objects.items()} - else: - return {str(uid): object.asdict() for uid, object in objects.items()} + return {str(uid): object.asdict() for uid, object in objects.items()} def _frames_asdict(self, frames: t.Dict[int, Frame]) -> dict: return {str(uid): frame.asdict() for uid, frame in frames.items()} @@ -235,13 +230,10 @@ def _frame_intervals_asdict(self, frame_intervals: t.List[FrameInterval]) -> t.L return [fi.asdict() for fi in frame_intervals] - def _clean_dict(input_dict: dict) -> dict: """Remove all fields from a dict that are None or have a length of 0.""" - empty_keys = [] for key, value in input_dict.items(): - is_field_empty = value is None or (hasattr(value, "__len__") and len(value) == 0) if is_field_empty: @@ -250,4 +242,4 @@ def _clean_dict(input_dict: dict) -> dict: for key in empty_keys: del input_dict[key] - return input_dict \ No newline at end of file + return input_dict diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 948295d..5cdbb3b 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -30,6 +30,7 @@ class Seg3d(_ObjectAnnotation): ---------------------- name: str Name of the annotation used by the VCD player for indexing in the object data pointers. + """ point_ids: t.List[int] = None @@ -54,8 +55,8 @@ def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Seg3d": ------- annotation: Seg3d Converted annotation. - """ + """ return Seg3d( uid=str(data_dict["uid"]), point_ids=data_dict["val"], @@ -76,8 +77,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = self._annotation_required_fields_asdict() dict_repr["val"] = [int(pid) for pid in self.point_ids] diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index 94f2658..c757b8e 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -36,6 +36,7 @@ class Sensor: Name of the subdirectory containing the sensor files. Default is None. description: str, optional Description of the sensor. Default is None. + """ uid: str @@ -62,8 +63,8 @@ def fromdict(cls, uid: str, cs_data_dict: dict, stream_data_dict: dict) -> "Sens ------- sensor: raillabel.format.Sensor Converted Sensor object. - """ + """ return Sensor( uid=uid, extrinsics=cls._extrinsics_fromdict(cs_data_dict), @@ -82,15 +83,14 @@ def asdict(self) -> dict: ------- dict_repr: dict Dict representation of this class instance. - """ + """ return { "coordinate_system": self._as_coordinate_system_dict(), "stream": self._as_stream_dict(), } def _as_coordinate_system_dict(self) -> dict: - coordinate_system_repr = {"type": "sensor", "parent": "base"} if self.extrinsics is not None: @@ -99,7 +99,6 @@ def _as_coordinate_system_dict(self) -> dict: return coordinate_system_repr def _as_stream_dict(self) -> dict: - stream_repr = {} if self.type is not None: @@ -121,7 +120,6 @@ def _as_stream_dict(self) -> dict: @classmethod def _extrinsics_fromdict(cls, data_dict) -> t.Optional[Transform]: - if "pose_wrt_parent" not in data_dict: return None @@ -143,19 +141,16 @@ def _extrinsics_fromdict(cls, data_dict) -> t.Optional[Transform]: def _intrinsics_fromdict( cls, data_dict, sensor_type: t.Optional["SensorType"] ) -> t.Optional[IntrinsicsPinhole]: - if "stream_properties" not in data_dict: return None if sensor_type == SensorType.CAMERA: - if "intrinsics_pinhole" in data_dict["stream_properties"]: return IntrinsicsPinhole.fromdict( data_dict["stream_properties"]["intrinsics_pinhole"] ) elif sensor_type == SensorType.RADAR: - if "intrinsics_radar" in data_dict["stream_properties"]: return IntrinsicsRadar.fromdict(data_dict["stream_properties"]["intrinsics_radar"]) @@ -163,7 +158,6 @@ def _intrinsics_fromdict( @classmethod def _type_fromdict(cls, data_dict) -> t.Optional["SensorType"]: - if "type" not in data_dict: return None diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index da693e3..02093ab 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -22,6 +22,7 @@ class SensorReference: uri: str, optional URI to the file corresponding to the frame recording in the particular frame. Default is None. + """ sensor: Sensor @@ -43,8 +44,8 @@ def fromdict(cls, data_dict: dict, sensor: Sensor) -> "SensorReference": ------- sensor_reference: raillabel.format.SensorReference Converted SensorReference object. - """ + """ return SensorReference( sensor=sensor, timestamp=cls._timestamp_fromdict(data_dict["stream_properties"]), @@ -63,8 +64,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = {"stream_properties": {"sync": {"timestamp": str(self.timestamp)}}} if self.uri is not None: diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index 9976f70..bcd3ea4 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -14,6 +14,7 @@ class Size2d: The size along the x-axis. y: float or int The size along the y-axis. + """ x: float @@ -27,8 +28,8 @@ def fromdict(cls, data_dict: dict) -> "Size2d": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Size2d( x=data_dict[0], y=data_dict[1], @@ -46,6 +47,6 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return [float(self.x), float(self.y)] diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index 61d926d..e9016f3 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -16,6 +16,7 @@ class Size3d: The size along the y-axis. z: float or int The size along the z-axis. + """ x: float @@ -30,8 +31,8 @@ def fromdict(cls, data_dict: dict) -> "Size3d": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Size3d( x=data_dict[0], y=data_dict[1], @@ -50,6 +51,6 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index bffddce..36f19e6 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -17,6 +17,7 @@ class Transform: Translation with regards to the parent coordinate system. quat: raillabel.format.Quaternion Rotation quaternion with regards to the parent coordinate system. + """ pos: Point3d @@ -30,8 +31,8 @@ def fromdict(cls, data_dict: dict) -> "Transform": ---------- data_dict: dict RailLabel format snippet containing the relevant data. - """ + """ return Transform( pos=Point3d.fromdict(data_dict["translation"]), quat=Quaternion.fromdict(data_dict["quaternion"]), @@ -49,8 +50,8 @@ def asdict(self) -> dict: ------ ValueError if an attribute can not be converted to the type required by the OpenLabel schema. - """ + """ dict_repr = { "translation": self.pos.asdict(), "quaternion": self.quat.asdict(), diff --git a/raillabel/load/load.py b/raillabel/load/load.py index 96b574c..e0c5ce3 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -18,6 +18,7 @@ def load(path: str) -> Scene: ------- scene: raillabel.Scene Scene with the loaded data. + """ with path.open() as scene_file: raw_scene = json.load(scene_file) diff --git a/raillabel/save/save.py b/raillabel/save/save.py index f0304ad..8778441 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -4,7 +4,6 @@ import json from pathlib import Path -from .. import exceptions from ..format import Scene @@ -22,14 +21,13 @@ def save(scene: Scene, path: str, prettify_json: bool = False): prettify_json: bool, optional If true, the JSON is saved with linebreaks and indents. This increases readibility but also the file size. Default is False. - """ + """ path = Path(path) - + data = scene.asdict() with path.open("w") as save_file: - if prettify_json: json.dump(data, save_file, indent=4) else: diff --git a/tests/conftest.py b/tests/conftest.py index 5b58ab1..e5045a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,7 @@ Path(__file__).parent / "__test_assets__", ] + @pytest.fixture(scope="session", autouse=True) def compile_uncommented_test_file(): """Compiles the main test file from json5 to json.""" @@ -29,6 +30,7 @@ def compile_uncommented_test_file(): with open(str(json5_file_path)[:-1], "w") as f: json.dump(data, f, indent=4) + @pytest.fixture def json_paths(request) -> t.Dict[str, Path]: json_paths = _fetch_json_paths_from_cache(request) @@ -38,9 +40,11 @@ def json_paths(request) -> t.Dict[str, Path]: return json_paths + def _fetch_json_paths_from_cache(request) -> t.Optional[t.Dict[str, Path]]: return request.config.cache.get("json_paths", None) + def _collect_json_paths() -> t.List[Path]: json_paths = [] @@ -49,6 +53,7 @@ def _collect_json_paths() -> t.List[Path]: return json_paths + def _get_file_identifier(path: Path) -> str: """Return relative path from test asset dir as string.""" @@ -58,13 +63,14 @@ def _get_file_identifier(path: Path) -> str: test_assets_dir_index = path.parts.index("__test_assets__") relative_path = "" - for part in path.parts[test_assets_dir_index+1:-1]: + for part in path.parts[test_assets_dir_index + 1 : -1]: relative_path += part + "/" relative_path += path.stem return relative_path + @pytest.fixture def json_data(request) -> t.Dict[str, dict]: json_data = _fetch_json_data_from_cache(request) @@ -74,9 +80,11 @@ def json_data(request) -> t.Dict[str, dict]: return json_data + def _fetch_json_data_from_cache(request) -> t.Optional[t.Dict[str, Path]]: return request.config.cache.get("json_data", None) + def _load_json_data(path: Path) -> dict: with path.open() as f: json_data = json.load(f) diff --git a/tests/test_raillabel/filter/test_filter.py b/tests/test_raillabel/filter/test_filter.py index 91e1eb6..9259abf 100644 --- a/tests/test_raillabel/filter/test_filter.py +++ b/tests/test_raillabel/filter/test_filter.py @@ -16,9 +16,7 @@ def delete_sensor_from_data(data: dict, sensor_id: str) -> dict: del data["openlabel"]["streams"][sensor_id] del data["openlabel"]["coordinate_systems"][sensor_id] del data["openlabel"]["coordinate_systems"]["base"]["children"][ - data["openlabel"]["coordinate_systems"]["base"]["children"].index( - sensor_id - ) + data["openlabel"]["coordinate_systems"]["base"]["children"].index(sensor_id) ] for frame_id in data["openlabel"]["frames"]: @@ -123,12 +121,8 @@ def test_filter_object_ids(json_paths, json_data): # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data @@ -160,12 +154,8 @@ def test_filter_object_types(json_paths, json_data): # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data @@ -188,12 +178,8 @@ def test_filter_annotation_ids(json_paths, json_data): # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data @@ -242,19 +228,15 @@ def test_filter_annotation_types(json_paths, json_data): # Deletes the excluded data del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["cuboid"] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["vec"] - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["cuboid"] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["vec"] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data @@ -282,25 +264,17 @@ def test_filter_sensors(json_paths, json_data): data = delete_sensor_from_data(data, "lidar") data = delete_sensor_from_data(data, "radar") - del data["openlabel"]["frames"]["0"]["frame_properties"]["frame_data"][ - "num" - ][-1] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["cuboid"] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["vec"] - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["0"]["frame_properties"]["frame_data"]["num"][-1] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["cuboid"] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["vec"] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["1"]["frame_properties"]["frame_data"][ - "num" - ][-1] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] + del data["openlabel"]["frames"]["1"]["frame_properties"]["frame_data"]["num"][-1] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] # Loads the ground truth filtered data scene_filtered_ground_truth = raillabel.Scene.fromdict(data) @@ -322,31 +296,29 @@ def test_filter_include_attribute_ids(json_paths, json_data): scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["bbox"][1] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["poly2d"][1] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["cuboid"][1] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["vec"][1] - - del data["openlabel"]["frames"]["1"]["objects"][ - "6fe55546-0dd7-4e40-b6b4-bb7ea3445772" - ]["object_data"]["bbox"][1] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ]["object_data"]["cuboid"][1] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ]["object_data"]["vec"][1] + del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["bbox"][1] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["poly2d"][1] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["cuboid"][1] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["vec"][1] + + del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ + "object_data" + ]["bbox"][1] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ + "object_data" + ]["cuboid"][1] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ + "object_data" + ]["vec"][1] data = delete_sensor_from_data(data, "ir_middle") data = delete_sensor_from_data(data, "radar") @@ -367,31 +339,31 @@ def test_filter_exclude_attribute_ids(json_paths, json_data): scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["bbox"][0] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["poly2d"][0] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["cuboid"][0] - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["vec"][0] - - del data["openlabel"]["frames"]["1"]["objects"][ - "6fe55546-0dd7-4e40-b6b4-bb7ea3445772" - ]["object_data"]["bbox"][0] - del data["openlabel"]["frames"]["1"]["objects"][ - "6fe55546-0dd7-4e40-b6b4-bb7ea3445772" - ]["object_data"]["poly2d"][0] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ]["object_data"]["cuboid"][0] - del data["openlabel"]["frames"]["1"]["objects"][ - "22dedd49-6dcb-413b-87ef-00ccfb532e98" - ]["object_data"]["vec"][0] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["bbox"][0] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["poly2d"][0] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["cuboid"][0] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["vec"][0] + + del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ + "object_data" + ]["bbox"][0] + del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ + "object_data" + ]["poly2d"][0] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ + "object_data" + ]["cuboid"][0] + del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ + "object_data" + ]["vec"][0] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data @@ -409,9 +381,9 @@ def test_filter_exclude_attribute_values(json_paths, json_data): scene = raillabel.load(json_paths["openlabel_v1_short"]) # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"][ - "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - ]["object_data"]["poly2d"][0] + del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ + "object_data" + ]["poly2d"][0] data = delete_sensor_from_data(data, "radar") # Loads the ground truth filtered data diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/test_raillabel/format/test_attributes.py index 25a6927..1fb7699 100644 --- a/tests/test_raillabel/format/test_attributes.py +++ b/tests/test_raillabel/format/test_attributes.py @@ -13,21 +13,17 @@ # == Fixtures ========================= + @pytest.fixture def attributes_single_type_dict() -> dict: return { "text": [ - { - "name": "test_text_attr0", - "val": "test_text_attr0_val" - }, - { - "name": "test_text_attr1", - "val": "test_text_attr1_val" - } + {"name": "test_text_attr0", "val": "test_text_attr0_val"}, + {"name": "test_text_attr1", "val": "test_text_attr1_val"}, ] } + @pytest.fixture def attributes_single_type() -> dict: return { @@ -39,24 +35,13 @@ def attributes_single_type() -> dict: @pytest.fixture def attributes_multiple_types_dict() -> dict: return { - "text": [{ - "name": "text_attr", - "val": "text_val" - }], - "num": [{ - "name": "num_attr", - "val": 0 - }], - "boolean": [{ - "name": "bool_attr", - "val": True - }], - "vec": [{ - "name": "vec_attr", - "val": [0, 1, 2] - }], + "text": [{"name": "text_attr", "val": "text_val"}], + "num": [{"name": "num_attr", "val": 0}], + "boolean": [{"name": "bool_attr", "val": True}], + "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], } + @pytest.fixture def attributes_multiple_types() -> dict: return { @@ -66,20 +51,16 @@ def attributes_multiple_types() -> dict: "vec_attr": [0, 1, 2], } + # == Tests ============================ + def test_fromdict__single_type(): attributes_dict = { "attributes": { "text": [ - { - "name": "test_text_attr0", - "val": "test_text_attr0_val" - }, - { - "name": "test_text_attr1", - "val": "test_text_attr1_val" - } + {"name": "test_text_attr0", "val": "test_text_attr0_val"}, + {"name": "test_text_attr1", "val": "test_text_attr1_val"}, ] } } @@ -89,25 +70,14 @@ def test_fromdict__single_type(): "test_text_attr1": "test_text_attr1_val", } + def test_fromdict__multiple_types(): attributes_dict = { "attributes": { - "text": [{ - "name": "text_attr", - "val": "text_val" - }], - "num": [{ - "name": "num_attr", - "val": 0 - }], - "boolean": [{ - "name": "bool_attr", - "val": True - }], - "vec": [{ - "name": "vec_attr", - "val": [0, 1, 2] - }], + "text": [{"name": "text_attr", "val": "text_val"}], + "num": [{"name": "num_attr", "val": 0}], + "boolean": [{"name": "bool_attr", "val": True}], + "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], } } @@ -127,17 +97,12 @@ def test_asdict__single_type(): assert _ObjectAnnotation._attributes_asdict(None, attributes) == { "text": [ - { - "name": "test_text_attr0", - "val": "test_text_attr0_val" - }, - { - "name": "test_text_attr1", - "val": "test_text_attr1_val" - } + {"name": "test_text_attr0", "val": "test_text_attr0_val"}, + {"name": "test_text_attr1", "val": "test_text_attr1_val"}, ] } + def test_asdict__multiple_types(): attributes = { "text_attr": "text_val", @@ -147,22 +112,10 @@ def test_asdict__multiple_types(): } assert _ObjectAnnotation._attributes_asdict(None, attributes) == { - "text": [{ - "name": "text_attr", - "val": "text_val" - }], - "num": [{ - "name": "num_attr", - "val": 0 - }], - "boolean": [{ - "name": "bool_attr", - "val": True - }], - "vec": [{ - "name": "vec_attr", - "val": [0, 1, 2] - }], + "text": [{"name": "text_attr", "val": "text_val"}], + "num": [{"name": "num_attr", "val": 0}], + "boolean": [{"name": "bool_attr", "val": True}], + "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], } diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index b8fae6f..27d0e57 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -13,6 +13,7 @@ # == Fixtures ========================= + @pytest.fixture def bbox_dict( sensor_camera, @@ -25,9 +26,10 @@ def bbox_dict( "name": "rgb_middle__bbox__person", "val": point2d_dict + size2d_dict, "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } + @pytest.fixture def bbox( point2d, @@ -53,9 +55,10 @@ def bbox_train_dict(sensor_camera, attributes_single_type_dict, point2d_dict, si "name": "rgb_middle__bbox__train", "val": point2d_dict + size2d_dict, "coordinate_system": sensor_camera.uid, - "attributes": attributes_single_type_dict + "attributes": attributes_single_type_dict, } + @pytest.fixture def bbox_train( point2d, @@ -73,14 +76,20 @@ def bbox_train( object=object_train, ) + # == Tests ============================ + def test_fromdict( - point2d, point2d_dict, - size2d, size2d_dict, - sensor_camera, sensors, + point2d, + point2d_dict, + size2d, + size2d_dict, + sensor_camera, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): bbox = Bbox.fromdict( { @@ -88,10 +97,10 @@ def test_fromdict( "name": "rgb_middle__bbox__person", "val": point2d_dict + size2d_dict, "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, }, sensors, - object_person + object_person, ) assert bbox.uid == "78f0ad89-2750-4a30-9d66-44c9da73a714" @@ -104,11 +113,14 @@ def test_fromdict( def test_asdict( - point2d, point2d_dict, - size2d, size2d_dict, + point2d, + point2d_dict, + size2d, + size2d_dict, sensor_camera, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): bbox = Bbox( uid="78f0ad89-2750-4a30-9d66-44c9da73a714", @@ -124,7 +136,7 @@ def test_asdict( "name": "rgb_middle__bbox__person", "val": point2d_dict + size2d_dict, "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index 500c356..81e7fbc 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -13,28 +13,23 @@ # == Fixtures ========================= + @pytest.fixture def cuboid_dict( - sensor_lidar, - attributes_multiple_types_dict, - point3d_dict, - size3d_dict, - quaternion_dict + sensor_lidar, attributes_multiple_types_dict, point3d_dict, size3d_dict, quaternion_dict ) -> dict: return { "uid": "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", "name": "lidar__cuboid__person", "val": point3d_dict + quaternion_dict + size3d_dict, "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } + @pytest.fixture def cuboid( - point3d, size3d, quaternion, - sensor_lidar, - attributes_multiple_types, - object_person + point3d, size3d, quaternion, sensor_lidar, attributes_multiple_types, object_person ) -> dict: return Cuboid( uid="2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", @@ -46,15 +41,22 @@ def cuboid( attributes=attributes_multiple_types, ) + # == Tests ============================ + def test_fromdict( - point3d, point3d_dict, - size3d, size3d_dict, - quaternion, quaternion_dict, - sensor_lidar, sensors, + point3d, + point3d_dict, + size3d, + size3d_dict, + quaternion, + quaternion_dict, + sensor_lidar, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): cuboid = Cuboid.fromdict( { @@ -62,10 +64,10 @@ def test_fromdict( "name": "lidar__cuboid__person", "val": point3d_dict + quaternion_dict + size3d_dict, "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, }, sensors, - object_person + object_person, ) assert cuboid.uid == "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad" @@ -79,12 +81,16 @@ def test_fromdict( def test_asdict( - point3d, point3d_dict, - size3d, size3d_dict, - quaternion, quaternion_dict, + point3d, + point3d_dict, + size3d, + size3d_dict, + quaternion, + quaternion_dict, sensor_lidar, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): cuboid = Cuboid( uid="2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", @@ -101,7 +107,7 @@ def test_asdict( "name": "lidar__cuboid__person", "val": point3d_dict + quaternion_dict + size3d_dict, "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } diff --git a/tests/test_raillabel/format/test_element_data_pointer.py b/tests/test_raillabel/format/test_element_data_pointer.py index 56b8659..f673651 100644 --- a/tests/test_raillabel/format/test_element_data_pointer.py +++ b/tests/test_raillabel/format/test_element_data_pointer.py @@ -14,6 +14,7 @@ # == Fixtures ========================= + @pytest.fixture def element_data_pointer_minimal_dict() -> dict: return { @@ -22,12 +23,11 @@ def element_data_pointer_minimal_dict() -> dict: "attribute_pointers": {}, } + @pytest.fixture def element_data_pointer_minimal(): return ElementDataPointer( - uid="rgb_middle__bbox__person", - frame_intervals=[], - attribute_pointers={} + uid="rgb_middle__bbox__person", frame_intervals=[], attribute_pointers={} ) @@ -35,9 +35,7 @@ def element_data_pointer_minimal(): def element_data_pointer_full_dict(frame_interval_dict) -> dict: return { "type": "bbox", - "frame_intervals": [ - frame_interval_dict - ], + "frame_intervals": [frame_interval_dict], "attribute_pointers": { "text_attr": "text", "num_attr": "num", @@ -46,29 +44,27 @@ def element_data_pointer_full_dict(frame_interval_dict) -> dict: }, } + @pytest.fixture def element_data_pointer_full(sensor_camera, object_person, frame_interval): return ElementDataPointer( uid="rgb_middle__bbox__person", - frame_intervals=[ - frame_interval - ], + frame_intervals=[frame_interval], attribute_pointers={ "text_attr": AttributeType.TEXT, "num_attr": AttributeType.NUM, "bool_attr": AttributeType.BOOLEAN, "vec_attr": AttributeType.VEC, - } + }, ) + # == Tests ============================ def test_asdict_minimal(sensor_camera, object_person): element_data_pointer = ElementDataPointer( - uid="rgb_middle__bbox__person", - frame_intervals=[], - attribute_pointers={} + uid="rgb_middle__bbox__person", frame_intervals=[], attribute_pointers={} ) assert element_data_pointer.asdict() == { @@ -77,25 +73,22 @@ def test_asdict_minimal(sensor_camera, object_person): "attribute_pointers": {}, } + def test_asdict_full(sensor_camera, object_person, frame_interval, frame_interval_dict): element_data_pointer = ElementDataPointer( uid="rgb_middle__bbox__person", - frame_intervals=[ - frame_interval - ], + frame_intervals=[frame_interval], attribute_pointers={ "text_attr": AttributeType.TEXT, "num_attr": AttributeType.NUM, "bool_attr": AttributeType.BOOLEAN, "vec_attr": AttributeType.VEC, - } + }, ) assert element_data_pointer.asdict() == { "type": "bbox", - "frame_intervals": [ - frame_interval_dict - ], + "frame_intervals": [frame_interval_dict], "attribute_pointers": { "text_attr": "text", "num_attr": "num", diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index e50bc65..de0adf6 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -14,58 +14,50 @@ # == Fixtures ========================= + @pytest.fixture def frame_dict( sensor_reference_camera_dict, num_dict, - object_person, object_data_person_dict, - object_train, object_data_train_dict, + object_person, + object_data_person_dict, + object_train, + object_data_train_dict, ) -> dict: return { "frame_properties": { "timestamp": "1632321743.100000072", - "streams": { - "rgb_middle": sensor_reference_camera_dict - }, - "frame_data": { - "num": [num_dict] - }, + "streams": {"rgb_middle": sensor_reference_camera_dict}, + "frame_data": {"num": [num_dict]}, }, "objects": { object_person.uid: object_data_person_dict, object_train.uid: object_data_train_dict, - } + }, } + @pytest.fixture -def frame( - sensor_reference_camera, - num, - all_annotations -) -> dict: +def frame(sensor_reference_camera, num, all_annotations) -> dict: return Frame( uid=0, timestamp=Decimal("1632321743.100000072"), sensors={sensor_reference_camera.sensor.uid: sensor_reference_camera}, frame_data={num.name: num}, - annotations=all_annotations + annotations=all_annotations, ) + # == Tests ============================ -def test_fromdict_sensors( - sensor_reference_camera_dict, - sensor_reference_camera, - sensor_camera -): + +def test_fromdict_sensors(sensor_reference_camera_dict, sensor_reference_camera, sensor_camera): frame = Frame.fromdict( uid=0, data_dict={ "frame_properties": { "timestamp": "1632321743.100000072", - "streams": { - "rgb_middle": sensor_reference_camera_dict - } + "streams": {"rgb_middle": sensor_reference_camera_dict}, } }, sensors={sensor_camera.uid: sensor_camera}, @@ -76,28 +68,23 @@ def test_fromdict_sensors( assert frame.timestamp == Decimal("1632321743.100000072") assert frame.sensors == {sensor_reference_camera.sensor.uid: sensor_reference_camera} -def test_fromdict_frame_data( - num, num_dict, - sensor_camera -): + +def test_fromdict_frame_data(num, num_dict, sensor_camera): frame = Frame.fromdict( uid=1, - data_dict={ - "frame_properties": { - "frame_data": { - "num": [num_dict] - } - } - }, + data_dict={"frame_properties": {"frame_data": {"num": [num_dict]}}}, sensors={sensor_camera.uid: sensor_camera}, objects={}, ) assert frame.frame_data == {num.name: num} + def test_fromdict_annotations( - object_data_person_dict, object_person, - object_data_train_dict, object_train, + object_data_person_dict, + object_person, + object_data_train_dict, + object_train, sensors, all_annotations, ): @@ -132,35 +119,21 @@ def test_asdict_sensors( assert frame.asdict() == { "frame_properties": { "timestamp": "1632321743.100000072", - "streams": { - "rgb_middle": sensor_reference_camera_dict - } + "streams": {"rgb_middle": sensor_reference_camera_dict}, } } + def test_asdict_frame_data(num, num_dict): - frame = Frame( - uid=0, - frame_data={num.name: num} - ) + frame = Frame(uid=0, frame_data={num.name: num}) + + assert frame.asdict() == {"frame_properties": {"frame_data": {"num": [num_dict]}}} - assert frame.asdict() == { - "frame_properties": { - "frame_data": { - "num": [num_dict] - } - } - } def test_asdict_object_data( - object_data_person_dict, object_person, - object_data_train_dict, object_train, - all_annotations + object_data_person_dict, object_person, object_data_train_dict, object_train, all_annotations ): - frame = Frame( - uid=0, - annotations=all_annotations - ) + frame = Frame(uid=0, annotations=all_annotations) assert frame.asdict() == { "objects": { @@ -170,11 +143,7 @@ def test_asdict_object_data( } -def test_object_data( - object_person, object_train, - bbox, cuboid, poly2d, poly3d, seg3d, - bbox_train -): +def test_object_data(object_person, object_train, bbox, cuboid, poly2d, poly3d, seg3d, bbox_train): frame = Frame( uid=2, annotations={ @@ -184,7 +153,7 @@ def test_object_data( poly3d.uid: poly3d, seg3d.uid: seg3d, bbox_train.uid: bbox_train, - } + }, ) assert frame.object_data == { @@ -195,9 +164,7 @@ def test_object_data( poly3d.uid: poly3d, seg3d.uid: seg3d, }, - object_train.uid: { - bbox_train.uid: bbox_train - } + object_train.uid: {bbox_train.uid: bbox_train}, } diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index dbae0de..6de42a1 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -13,12 +13,11 @@ # == Fixtures ========================= + @pytest.fixture def frame_interval_dict() -> dict: - return { - "frame_start": 12, - "frame_end": 16 - } + return {"frame_start": 12, "frame_end": 16} + @pytest.fixture def frame_interval() -> dict: @@ -27,8 +26,10 @@ def frame_interval() -> dict: frame_end=16, ) + # == Tests ============================ + def test_fromdict(): frame_interval = FrameInterval.fromdict( { @@ -67,19 +68,18 @@ def test_from_frame_uids_empty(): assert FrameInterval.from_frame_uids(frame_uids) == [] + def test_from_frame_uids_one_frame(): frame_uids = [1] - assert FrameInterval.from_frame_uids(frame_uids) == [ - FrameInterval(1, 1) - ] + assert FrameInterval.from_frame_uids(frame_uids) == [FrameInterval(1, 1)] + def test_from_frame_uids_one_interval(): frame_uids = [1, 2, 3, 4] - assert FrameInterval.from_frame_uids(frame_uids) == [ - FrameInterval(1, 4) - ] + assert FrameInterval.from_frame_uids(frame_uids) == [FrameInterval(1, 4)] + def test_from_frame_uids_multiple_intervals(): frame_uids = [0, 1, 2, 3, 6, 7, 9, 12, 13, 14] @@ -91,6 +91,7 @@ def test_from_frame_uids_multiple_intervals(): FrameInterval(12, 14), ] + def test_from_frame_uids_unsorted(): frame_uids = [5, 2, 1, 3] diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py index b0b9aae..fa860f1 100644 --- a/tests/test_raillabel/format/test_intrinsics_pinhole.py +++ b/tests/test_raillabel/format/test_intrinsics_pinhole.py @@ -13,115 +13,59 @@ # == Fixtures ========================= + @pytest.fixture def intrinsics_pinhole_dict() -> dict: return { - "camera_matrix": [ - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ], - "distortion_coeffs": [ - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ], + "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], + "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], "width_px": 2464, - "height_px": 1600 + "height_px": 1600, } + @pytest.fixture def intrinsics_pinhole() -> dict: return IntrinsicsPinhole( - camera_matrix=( - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ), - distortion=( - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ), + camera_matrix=(0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0), + distortion=(0.49, 0.69, 0.31, 0.81, 0.99), width_px=2464, - height_px=1600 + height_px=1600, ) + # == Tests ============================ + def test_fromdict(): intrinsics_pinhole = IntrinsicsPinhole.fromdict( { - "camera_matrix": [ - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ], - "distortion_coeffs": [ - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ], + "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], + "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], "width_px": 2464, - "height_px": 1600 + "height_px": 1600, } ) - assert intrinsics_pinhole.camera_matrix == ( - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ) - assert intrinsics_pinhole.distortion == ( - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ) + assert intrinsics_pinhole.camera_matrix == (0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0) + assert intrinsics_pinhole.distortion == (0.49, 0.69, 0.31, 0.81, 0.99) assert intrinsics_pinhole.width_px == 2464 assert intrinsics_pinhole.height_px == 1600 def test_asdict(): intrinsics_pinhole = IntrinsicsPinhole( - camera_matrix=( - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ), - distortion=( - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ), + camera_matrix=(0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0), + distortion=(0.49, 0.69, 0.31, 0.81, 0.99), width_px=2464, - height_px=1600 + height_px=1600, ) assert intrinsics_pinhole.asdict() == { - "camera_matrix": [ - 0.48, 0 , 0.81, 0, - 0 , 0.16, 0.83, 0, - 0 , 0 , 1 , 0 - ], - "distortion_coeffs": [ - 0.49, - 0.69, - 0.31, - 0.81, - 0.99 - ], + "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], + "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], "width_px": 2464, - "height_px": 1600 + "height_px": 1600, } diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py index 4dd9f09..9a53fba 100644 --- a/tests/test_raillabel/format/test_intrinsics_radar.py +++ b/tests/test_raillabel/format/test_intrinsics_radar.py @@ -13,6 +13,7 @@ # == Fixtures ========================= + @pytest.fixture def intrinsics_radar_dict() -> dict: return { @@ -21,6 +22,7 @@ def intrinsics_radar_dict() -> dict: "height_px": 1428, } + @pytest.fixture def intrinsics_radar() -> dict: return IntrinsicsRadar( @@ -29,8 +31,10 @@ def intrinsics_radar() -> dict: height_px=1428, ) + # == Tests ============================ + def test_fromdict(): intrinsics_radar = IntrinsicsRadar.fromdict( { diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py index 4aa4d27..2ca9d66 100644 --- a/tests/test_raillabel/format/test_metadata.py +++ b/tests/test_raillabel/format/test_metadata.py @@ -13,11 +13,10 @@ # == Fixtures ========================= + @pytest.fixture def metadata_minimal_dict() -> dict: - return { - "schema_version": "1.0.0" - } + return {"schema_version": "1.0.0"} @pytest.fixture @@ -31,11 +30,10 @@ def metadata_full_dict() -> dict: "tagged_file": "test_folder", } + @pytest.fixture def metadata_minimal() -> dict: - return Metadata( - schema_version="1.0.0" - ) + return Metadata(schema_version="1.0.0") @pytest.fixture @@ -49,13 +47,13 @@ def metadata_full() -> dict: tagged_file="test_folder", ) + # == Tests ============================ + def test_fromdict_minimal(): metadata = Metadata.fromdict( - { - "schema_version": "1.0.0" - }, + {"schema_version": "1.0.0"}, ) assert metadata.schema_version == "1.0.0" @@ -72,7 +70,7 @@ def test_fromdict_full(): "name": "test_project", "tagged_file": "test_folder", }, - "2.1.1" + "2.1.1", ) assert metadata.annotator == "test_annotator" @@ -84,12 +82,7 @@ def test_fromdict_full(): def test_fromdict_additional_arg_valid(): - metadata = Metadata.fromdict( - { - "schema_version": "1.0.0", - "additional_argument": "Some Value" - } - ) + metadata = Metadata.fromdict({"schema_version": "1.0.0", "additional_argument": "Some Value"}) assert metadata.schema_version == "1.0.0" assert metadata.additional_argument == "Some Value" @@ -97,22 +90,13 @@ def test_fromdict_additional_arg_valid(): def test_fromdict_additional_arg_invalid(): with pytest.raises(KeyError): - Metadata.fromdict( - { - "schema_version": "1.0.0", - "invalid python variable": "Some Value" - } - ) + Metadata.fromdict({"schema_version": "1.0.0", "invalid python variable": "Some Value"}) def test_asdict_minimal(): - metadata_dict = Metadata( - schema_version="1.0.0" - ).asdict() + metadata_dict = Metadata(schema_version="1.0.0").asdict() - assert metadata_dict == { - "schema_version": "1.0.0" - } + assert metadata_dict == {"schema_version": "1.0.0"} def test_asdict_full(): @@ -136,16 +120,11 @@ def test_asdict_full(): def test_fromdict_additional_arg(): - metadata = Metadata( - schema_version="1.0.0" - ) + metadata = Metadata(schema_version="1.0.0") metadata.additional_argument = "Some Value" - assert metadata.asdict() == { - "schema_version": "1.0.0", - "additional_argument": "Some Value" - } + assert metadata.asdict() == {"schema_version": "1.0.0", "additional_argument": "Some Value"} if __name__ == "__main__": diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py index c14dfb0..cc7e164 100644 --- a/tests/test_raillabel/format/test_num.py +++ b/tests/test_raillabel/format/test_num.py @@ -13,15 +13,17 @@ # == Fixtures ========================= + @pytest.fixture def num_dict(sensor_camera) -> dict: return { "uid": "4e86c449-3B19-410c-aa64-603d46da3b26", "name": "some_number", "val": 24, - "coordinate_system": sensor_camera.uid + "coordinate_system": sensor_camera.uid, } + @pytest.fixture def num(sensor_camera) -> dict: return Num( @@ -31,8 +33,10 @@ def num(sensor_camera) -> dict: sensor=sensor_camera, ) + # == Tests ============================ + def test_fromdict(sensor_camera): num = Num.fromdict( { @@ -41,9 +45,7 @@ def test_fromdict(sensor_camera): "val": 24, "coordinate_system": sensor_camera.uid, }, - { - sensor_camera.uid: sensor_camera - } + {sensor_camera.uid: sensor_camera}, ) assert num.uid == "4e86c449-3B19-410c-aa64-603d46da3b26" diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 5d1fd57..ad6b466 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -29,6 +29,7 @@ # == Fixtures ========================= + @pytest.fixture def objects_dict(object_person_dict, object_train_dict) -> dict: return { @@ -36,6 +37,7 @@ def objects_dict(object_person_dict, object_train_dict) -> dict: object_train_dict["object_uid"]: object_train_dict["data_dict"], } + @pytest.fixture def objects(object_person, object_train) -> t.Dict[str, Object]: return { @@ -54,6 +56,7 @@ def object_person_dict() -> dict: }, } + @pytest.fixture def object_person() -> dict: return Object( @@ -73,6 +76,7 @@ def object_train_dict() -> dict: }, } + @pytest.fixture def object_train() -> dict: return Object( @@ -81,15 +85,17 @@ def object_train() -> dict: type="train", ) + # == Tests ============================ + def test_fromdict(): object = Object.fromdict( object_uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", data_dict={ "name": "person_0000", "type": "person", - } + }, ) assert object.uid == "b40ba3ad-0327-46ff-9c28-2506cfd6d934" @@ -109,6 +115,7 @@ def test_asdict_no_frames(): "type": "person", } + def test_asdict_with_frames(): object = Object( uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", @@ -117,11 +124,7 @@ def test_asdict_with_frames(): ) frames = { - 0: build_frame(0, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), + 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), } object_dict = object.asdict(frames) @@ -155,83 +158,67 @@ def test_object_data_pointers__sensor(): object = build_object("person") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object: [ build_annotation("rgb_middle__bbox__person", object), - build_annotation("lidar__bbox__person", object) + build_annotation("lidar__bbox__person", object), ] - } + }, ) } object_data_pointers = object.object_data_pointers(frames) - assert set(object_data_pointers.keys()) == set(["rgb_middle__bbox__person", "lidar__bbox__person"]) + assert set(object_data_pointers.keys()) == set( + ["rgb_middle__bbox__person", "lidar__bbox__person"] + ) + def test_object_data_pointers__annotation_type(): object = build_object("person") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object: [ build_annotation("rgb_middle__bbox__person", object), - build_annotation("rgb_middle__cuboid__person", object) + build_annotation("rgb_middle__cuboid__person", object), ] - } + }, ) } object_data_pointers = object.object_data_pointers(frames) - assert set(object_data_pointers.keys()) == set([ - "rgb_middle__bbox__person", - "rgb_middle__cuboid__person" - ]) + assert set(object_data_pointers.keys()) == set( + ["rgb_middle__bbox__person", "rgb_middle__cuboid__person"] + ) + def test_object_data_pointers__one_frame_interval(): object = build_object("person") frames = { - 0: build_frame(0, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), - 1: build_frame(1, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), + 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), + 1: build_frame(1, {object: [build_annotation("rgb_middle__bbox__person", object)]}), } object_data_pointers = object.object_data_pointers(frames) assert len(object_data_pointers) == 1 - assert object_data_pointers["rgb_middle__bbox__person"].frame_intervals == [ - FrameInterval(0, 1) - ] + assert object_data_pointers["rgb_middle__bbox__person"].frame_intervals == [FrameInterval(0, 1)] + def test_object_data_pointers__two_frame_intervals(): object = build_object("person") frames = { - 0: build_frame(0, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), - 1: build_frame(1, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), - 8: build_frame(8, - { - object: [build_annotation("rgb_middle__bbox__person", object)] - } - ), + 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), + 1: build_frame(1, {object: [build_annotation("rgb_middle__bbox__person", object)]}), + 8: build_frame(8, {object: [build_annotation("rgb_middle__bbox__person", object)]}), } object_data_pointers = object.object_data_pointers(frames) @@ -242,11 +229,13 @@ def test_object_data_pointers__two_frame_intervals(): FrameInterval(8, 8), ] + def test_object_data_pointers__attributes_one_annotation(): object = build_object("person") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object: [ build_annotation( @@ -257,10 +246,10 @@ def test_object_data_pointers__attributes_one_annotation(): "num_attr": 0, "bool_attr": True, "vec_attr": [0, 1], - } + }, ), ] - } + }, ) } @@ -273,11 +262,13 @@ def test_object_data_pointers__attributes_one_annotation(): "vec_attr": AttributeType.VEC, } + def test_object_data_pointers__attributes_multiple_annotations_with_differing_attributes(): object = build_object("person") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object: [ build_annotation( @@ -286,7 +277,7 @@ def test_object_data_pointers__attributes_multiple_annotations_with_differing_at attributes={ "text_attr": "some value", "num_attr": 0, - } + }, ), build_annotation( name="rgb_middle__bbox__person", @@ -294,10 +285,10 @@ def test_object_data_pointers__attributes_multiple_annotations_with_differing_at attributes={ "bool_attr": True, "vec_attr": [0, 1], - } + }, ), ] - } + }, ) } @@ -310,25 +301,28 @@ def test_object_data_pointers__attributes_multiple_annotations_with_differing_at "vec_attr": AttributeType.VEC, } + def test_object_data_pointers__multiple_objects_of_differing_type(): object_person = build_object("person") object_train = build_object("train") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object_person: [ build_annotation("lidar__bbox__person", object_person), ] - } + }, ), - 1: build_frame(1, + 1: build_frame( + 1, { object_train: [ build_annotation("lidar__bbox__train", object_train), ] - } - ) + }, + ), } person_object_data_pointers = object_person.object_data_pointers(frames) @@ -339,46 +333,45 @@ def test_object_data_pointers__multiple_objects_of_differing_type(): train_object_data_pointers = object_train.object_data_pointers(frames) assert len(train_object_data_pointers) == 1 - assert train_object_data_pointers["lidar__bbox__train"].frame_intervals == [ - FrameInterval(1, 1) - ] + assert train_object_data_pointers["lidar__bbox__train"].frame_intervals == [FrameInterval(1, 1)] + def test_object_data_pointers__multiple_objects_of_same_type(): object1 = build_object("person") object2 = build_object("person") frames = { - 0: build_frame(0, + 0: build_frame( + 0, { object1: [ build_annotation("lidar__bbox__person", object1), ] - } + }, ), - 1: build_frame(1, + 1: build_frame( + 1, { object2: [ build_annotation("lidar__bbox__person", object2), ] - } - ) + }, + ), } object1_data_pointers = object1.object_data_pointers(frames) assert len(object1_data_pointers) == 1 - assert object1_data_pointers["lidar__bbox__person"].frame_intervals == [ - FrameInterval(0, 0) - ] + assert object1_data_pointers["lidar__bbox__person"].frame_intervals == [FrameInterval(0, 0)] object2_data_pointers = object2.object_data_pointers(frames) assert len(object2_data_pointers) == 1 - assert object2_data_pointers["lidar__bbox__person"].frame_intervals == [ - FrameInterval(1, 1) - ] + assert object2_data_pointers["lidar__bbox__person"].frame_intervals == [FrameInterval(1, 1)] + # == Helpers ========================== -def build_annotation(name: str, object: Object, attributes: dict={}) -> t.Union[Bbox, Cuboid]: + +def build_annotation(name: str, object: Object, attributes: dict = {}) -> t.Union[Bbox, Cuboid]: sensor_uid, ann_type, object_type = tuple(name.split("__")) sensor = Sensor(sensor_uid) @@ -390,7 +383,7 @@ def build_annotation(name: str, object: Object, attributes: dict={}) -> t.Union[ attributes=attributes, sensor=sensor, pos=Point2d(50, 100), - size=Size2d(30, 30) + size=Size2d(30, 30), ) elif ann_type == "cuboid": @@ -407,6 +400,7 @@ def build_annotation(name: str, object: Object, attributes: dict={}) -> t.Union[ else: raise ValueError() + def build_frame(uid: int, raw_object_data: t.Dict[Object, t.List[t.Union[Bbox, Cuboid]]]) -> Frame: annotations = {} for object, object_data in raw_object_data.items(): @@ -415,13 +409,13 @@ def build_frame(uid: int, raw_object_data: t.Dict[Object, t.List[t.Union[Bbox, C return Frame(uid=uid, annotations=annotations) + def build_object(type: str) -> Object: return Object( - uid=str(uuid4()), - name=f"{type}_{str(random.randint(0, 9999)).zfill(4)}", - type=type + uid=str(uuid4()), name=f"{type}_{str(random.randint(0, 9999)).zfill(4)}", type=type ) + if __name__ == "__main__": os.system("clear") pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_object_annotation.py b/tests/test_raillabel/format/test_object_annotation.py index 7989e55..9f72bbb 100644 --- a/tests/test_raillabel/format/test_object_annotation.py +++ b/tests/test_raillabel/format/test_object_annotation.py @@ -14,9 +14,11 @@ # == Fixtures ========================= + @pytest.fixture def all_annotations( - bbox, bbox_train, + bbox, + bbox_train, cuboid, poly2d, poly3d, @@ -31,8 +33,10 @@ def all_annotations( seg3d.uid: seg3d, } + # == Tests ============================ + def test_post_init_happy(object_person, point2d, size2d): raillabel.format.Bbox( uid="d2764400-8560-4991-a491-ada598b345c8", @@ -41,6 +45,7 @@ def test_post_init_happy(object_person, point2d, size2d): size=size2d, ) + def test_post_init_unhappy(object_person, point2d): with pytest.raises(TypeError): raillabel.format.Bbox( diff --git a/tests/test_raillabel/format/test_object_data.py b/tests/test_raillabel/format/test_object_data.py index 01093de..be79441 100644 --- a/tests/test_raillabel/format/test_object_data.py +++ b/tests/test_raillabel/format/test_object_data.py @@ -11,6 +11,7 @@ # == Fixtures ========================= + @pytest.fixture def object_data_person_dict(bbox_dict, poly2d_dict, cuboid_dict, poly3d_dict, seg3d_dict) -> dict: return { @@ -23,6 +24,7 @@ def object_data_person_dict(bbox_dict, poly2d_dict, cuboid_dict, poly3d_dict, se } } + @pytest.fixture def object_data_train_dict(bbox_train_dict) -> dict: return { diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 07b9f4c..e156e65 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -13,10 +13,12 @@ # == Fixtures ========================= + @pytest.fixture def point2d_dict() -> dict: return [1.5, 222] + @pytest.fixture def point2d() -> dict: return Point2d(1.5, 222) @@ -26,16 +28,17 @@ def point2d() -> dict: def point2d_another_dict() -> dict: return [19, 84] + @pytest.fixture def point2d_another() -> dict: return Point2d(19, 84) + # == Tests ============================ + def test_fromdict(): - point2d = Point2d.fromdict( - [1.5, 222] - ) + point2d = Point2d.fromdict([1.5, 222]) assert point2d.x == 1.5 assert point2d.y == 222 diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index 07fdb12..426a6b2 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -13,10 +13,12 @@ # == Fixtures ========================= + @pytest.fixture def point3d_dict() -> dict: return [420, 3.14, 0] + @pytest.fixture def point3d() -> dict: return Point3d(420, 3.14, 0) @@ -26,16 +28,17 @@ def point3d() -> dict: def point3d_another_dict() -> dict: return [9, 8, 7] + @pytest.fixture def point3d_another() -> dict: return Point3d(9, 8, 7) + # == Tests ============================ + def test_fromdict(): - point3d = Point3d.fromdict( - [420, 3.14, 0] - ) + point3d = Point3d.fromdict([420, 3.14, 0]) assert point3d.x == 420 assert point3d.y == 3.14 diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index 95ce7dd..efc3a8d 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -13,11 +13,10 @@ # == Fixtures ========================= + @pytest.fixture def poly2d_dict( - sensor_camera, - attributes_multiple_types_dict, - point2d_dict, point2d_another_dict + sensor_camera, attributes_multiple_types_dict, point2d_dict, point2d_another_dict ) -> dict: return { "uid": "d73b5988-767B-47ef-979c-022af60c6ab2", @@ -29,12 +28,10 @@ def poly2d_dict( "mode": "MODE_POLY2D_ABSOLUTE", } + @pytest.fixture def poly2d( - point2d, point2d_another, - sensor_camera, - attributes_multiple_types, - object_person + point2d, point2d_another, sensor_camera, attributes_multiple_types, object_person ) -> dict: return Poly2d( uid="d73b5988-767B-47ef-979c-022af60c6ab2", @@ -46,14 +43,20 @@ def poly2d( mode="MODE_POLY2D_ABSOLUTE", ) + # == Tests ============================ + def test_fromdict( - point2d, point2d_dict, - point2d_another, point2d_another_dict, - sensor_camera, sensors, + point2d, + point2d_dict, + point2d_another, + point2d_another_dict, + sensor_camera, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): poly2d = Poly2d.fromdict( { @@ -66,7 +69,7 @@ def test_fromdict( "mode": "MODE_POLY2D_ABSOLUTE", }, sensors, - object_person + object_person, ) assert poly2d.uid == "d73b5988-767B-47ef-979c-022af60c6ab2" @@ -80,10 +83,14 @@ def test_fromdict( def test_asdict( - point2d, point2d_dict, - point2d_another, point2d_another_dict, - sensor_camera, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + point2d, + point2d_dict, + point2d_another, + point2d_another_dict, + sensor_camera, + object_person, + attributes_multiple_types, + attributes_multiple_types_dict, ): poly2d = Poly2d( uid="d73b5988-767B-47ef-979c-022af60c6ab2", diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 7013906..a8da9d2 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -13,11 +13,10 @@ # == Fixtures ========================= + @pytest.fixture def poly3d_dict( - sensor_lidar, - attributes_multiple_types_dict, - point3d_dict, point3d_another_dict + sensor_lidar, attributes_multiple_types_dict, point3d_dict, point3d_another_dict ) -> dict: return { "uid": "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", @@ -28,13 +27,9 @@ def poly3d_dict( "closed": True, } + @pytest.fixture -def poly3d( - point3d, point3d_another, - sensor_lidar, - object_person, - attributes_multiple_types -) -> dict: +def poly3d(point3d, point3d_another, sensor_lidar, object_person, attributes_multiple_types) -> dict: return Poly3d( uid="9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", points=[point3d, point3d_another], @@ -44,14 +39,20 @@ def poly3d( closed=True, ) + # == Tests ============================ + def test_fromdict( - point3d, point3d_dict, - point3d_another, point3d_another_dict, - sensor_lidar, sensors, + point3d, + point3d_dict, + point3d_another, + point3d_another_dict, + sensor_lidar, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): poly3d = Poly3d.fromdict( { @@ -63,7 +64,7 @@ def test_fromdict( "closed": True, }, sensors, - object_person + object_person, ) assert poly3d.uid == "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb" @@ -76,11 +77,14 @@ def test_fromdict( def test_asdict( - point3d, point3d_dict, - point3d_another, point3d_another_dict, + point3d, + point3d_dict, + point3d_another, + point3d_another_dict, sensor_lidar, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): poly3d = Poly3d( uid="9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py index fb6de00..2f545cf 100644 --- a/tests/test_raillabel/format/test_quaternion.py +++ b/tests/test_raillabel/format/test_quaternion.py @@ -13,25 +13,22 @@ # == Fixtures ========================= + @pytest.fixture def quaternion_dict() -> dict: - return [0.75318325, -0.10270147, 0.21430262, -0.61338551] + return [0.75318325, -0.10270147, 0.21430262, -0.61338551] + @pytest.fixture def quaternion() -> dict: - return Quaternion(0.75318325, -0.10270147, 0.21430262, -0.61338551) + return Quaternion(0.75318325, -0.10270147, 0.21430262, -0.61338551) + # == Tests ============================ + def test_fromdict(): - quaternion = Quaternion.fromdict( - [ - 0.75318325, - -0.10270147, - 0.21430262, - -0.61338551 - ] - ) + quaternion = Quaternion.fromdict([0.75318325, -0.10270147, 0.21430262, -0.61338551]) assert quaternion.x == 0.75318325 assert quaternion.y == -0.10270147 @@ -40,19 +37,9 @@ def test_fromdict(): def test_asdict(): - quaternion = Quaternion( - x=0.75318325, - y=-0.10270147, - z=0.21430262, - w=-0.61338551 - ) - - assert quaternion.asdict() == [ - 0.75318325, - -0.10270147, - 0.21430262, - -0.61338551 - ] + quaternion = Quaternion(x=0.75318325, y=-0.10270147, z=0.21430262, w=-0.61338551) + + assert quaternion.asdict() == [0.75318325, -0.10270147, 0.21430262, -0.61338551] if __name__ == "__main__": diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index 884a61f..2490592 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -15,12 +15,17 @@ # == Fixtures ========================= + @pytest.fixture def scene_dict( metadata_full_dict, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, - object_person_dict, object_train_dict, - frame, frame_dict, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, + object_person_dict, + object_train_dict, + frame, + frame_dict, ) -> dict: return { "openlabel": { @@ -38,7 +43,7 @@ def scene_dict( sensor_lidar_dict["uid"], sensor_camera_dict["uid"], sensor_radar_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], @@ -56,16 +61,14 @@ def scene_dict( "frame_start": 0, "frame_end": 0, } - ] + ], } } + @pytest.fixture def scene( - metadata_full, - sensor_camera, sensor_lidar, sensor_radar, - object_person, object_train, - frame + metadata_full, sensor_camera, sensor_lidar, sensor_radar, object_person, object_train, frame ) -> Scene: return Scene( metadata=metadata_full, @@ -78,13 +81,16 @@ def scene( object_person.uid: object_person, object_train.uid: object_train, }, - frames={frame.uid: frame} + frames={frame.uid: frame}, ) + # == Tests ============================ + def test_fromdict_metadata( - metadata_full, metadata_full_dict, + metadata_full, + metadata_full_dict, ): scene = Scene.fromdict( { @@ -99,10 +105,15 @@ def test_fromdict_metadata( assert scene.metadata == metadata_full + def test_fromdict_sensors( metadata_full_dict, - sensor_camera, sensor_lidar, sensor_radar, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, + sensor_camera, + sensor_lidar, + sensor_radar, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, ): scene = Scene.fromdict( { @@ -121,7 +132,7 @@ def test_fromdict_sensors( sensor_lidar_dict["uid"], sensor_camera_dict["uid"], sensor_radar_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], @@ -137,9 +148,12 @@ def test_fromdict_sensors( sensor_radar.uid: sensor_radar, } + def test_fromdict_missing_coordinate_system( metadata_full_dict, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, ): with pytest.raises(exceptions.MissingCoordinateSystemError): Scene.fromdict( @@ -156,7 +170,7 @@ def test_fromdict_missing_coordinate_system( "parent": "", "children": [ sensor_camera_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], }, @@ -164,9 +178,12 @@ def test_fromdict_missing_coordinate_system( } ) + def test_fromdict_missing_stream( metadata_full_dict, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, ): with pytest.raises(exceptions.MissingStreamError): Scene.fromdict( @@ -183,7 +200,7 @@ def test_fromdict_missing_stream( "children": [ sensor_lidar_dict["uid"], sensor_camera_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], @@ -192,9 +209,12 @@ def test_fromdict_missing_stream( } ) + def test_fromdict_unsupported_parent( metadata_full_dict, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, ): sensor_camera_dict["coordinate_system"]["parent"] = "unsupported_parent" @@ -212,7 +232,7 @@ def test_fromdict_unsupported_parent( "parent": "", "children": [ sensor_camera_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], }, @@ -220,10 +240,14 @@ def test_fromdict_unsupported_parent( } ) + def test_fromdict_objects( - metadata_full, metadata_full_dict, - object_person, object_train, - object_person_dict, object_train_dict, + metadata_full, + metadata_full_dict, + object_person, + object_train, + object_person_dict, + object_train_dict, ): scene = Scene.fromdict( { @@ -243,11 +267,15 @@ def test_fromdict_objects( object_train.uid: object_train, } + def test_fromdict_frames( - metadata_full, metadata_full_dict, - streams_dict, coordinate_systems_dict, + metadata_full, + metadata_full_dict, + streams_dict, + coordinate_systems_dict, objects_dict, - frame, frame_dict, + frame, + frame_dict, ): scene = Scene.fromdict( { @@ -264,21 +292,26 @@ def test_fromdict_frames( "frame_start": 0, "frame_end": 0, } - ] + ], } }, subschema_version=metadata_full.subschema_version, ) assert scene.frames == { - frame.uid: frame, + frame.uid: frame, } def test_asdict_sensors( - metadata_full, metadata_full_dict, - sensor_camera, sensor_lidar, sensor_radar, - sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict, + metadata_full, + metadata_full_dict, + sensor_camera, + sensor_lidar, + sensor_radar, + sensor_camera_dict, + sensor_lidar_dict, + sensor_radar_dict, ): scene = Scene( metadata=metadata_full, @@ -286,7 +319,7 @@ def test_asdict_sensors( sensor_lidar.uid: sensor_lidar, sensor_camera.uid: sensor_camera, sensor_radar.uid: sensor_radar, - } + }, ) assert scene.asdict() == { @@ -305,7 +338,7 @@ def test_asdict_sensors( sensor_lidar_dict["uid"], sensor_camera_dict["uid"], sensor_radar_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], @@ -314,10 +347,14 @@ def test_asdict_sensors( } } + def test_asdict_objects( - metadata_full, metadata_full_dict, - object_person, object_train, - object_person_dict, object_train_dict, + metadata_full, + metadata_full_dict, + object_person, + object_train, + object_person_dict, + object_train_dict, ): scene = Scene( metadata=metadata_full, @@ -337,11 +374,17 @@ def test_asdict_objects( } } + def test_asdict_frames( - metadata_full, metadata_full_dict, - sensors, streams_dict, coordinate_systems_dict, - objects, objects_dict, - frame, frame_dict, + metadata_full, + metadata_full_dict, + sensors, + streams_dict, + coordinate_systems_dict, + objects, + objects_dict, + frame, + frame_dict, ): scene = Scene( metadata=metadata_full, @@ -349,7 +392,7 @@ def test_asdict_frames( objects=objects, frames={ frame.uid: frame, - } + }, ) assert scene.asdict(calculate_pointers=False) == { @@ -366,7 +409,7 @@ def test_asdict_frames( "frame_start": 0, "frame_end": 0, } - ] + ], } } @@ -379,7 +422,7 @@ def test_frame_intervals(metadata_minimal): 2: Frame(2), 3: Frame(3), 8: Frame(8), - } + }, ) assert scene.frame_intervals == [ @@ -387,6 +430,7 @@ def test_frame_intervals(metadata_minimal): FrameInterval(8, 8), ] + def test_integration(json_data): scene_dict = json_data["openlabel_v1_short"] @@ -394,14 +438,10 @@ def test_integration(json_data): del actual["openlabel"]["metadata"]["exporter_version"] assert actual == scene_dict - + def test_clean_dict(): - input_dict = { - "non_empty_field": "non_empty_value", - "none_field": None, - "field_with_len_0": [] - } + input_dict = {"non_empty_field": "non_empty_value", "none_field": None, "field_with_len_0": []} assert _clean_dict(input_dict) == { "non_empty_field": "non_empty_value", diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index 1a82408..c9db4c3 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -13,6 +13,7 @@ # == Fixtures ========================= + @pytest.fixture def seg3d_dict(sensor_lidar, attributes_multiple_types_dict) -> dict: return { @@ -20,9 +21,10 @@ def seg3d_dict(sensor_lidar, attributes_multiple_types_dict) -> dict: "name": "lidar__vec__person", "val": [586, 789, 173], "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } + @pytest.fixture def seg3d(sensor_lidar, attributes_multiple_types, object_person) -> dict: return Seg3d( @@ -33,12 +35,16 @@ def seg3d(sensor_lidar, attributes_multiple_types, object_person) -> dict: attributes=attributes_multiple_types, ) + # == Tests ============================ + def test_fromdict( - sensor_lidar, sensors, + sensor_lidar, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): seg3d = Seg3d.fromdict( { @@ -46,10 +52,10 @@ def test_fromdict( "name": "lidar__vec__person", "val": [586, 789, 173], "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, }, sensors, - object_person + object_person, ) assert seg3d.uid == "db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a" @@ -61,9 +67,11 @@ def test_fromdict( def test_asdict( - sensor_lidar, sensors, + sensor_lidar, + sensors, object_person, - attributes_multiple_types, attributes_multiple_types_dict, + attributes_multiple_types, + attributes_multiple_types_dict, ): seg3d = Seg3d( uid="db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", @@ -78,7 +86,7 @@ def test_asdict( "name": "lidar__vec__person", "val": [586, 789, 173], "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict + "attributes": attributes_multiple_types_dict, } diff --git a/tests/test_raillabel/format/test_sensor.py b/tests/test_raillabel/format/test_sensor.py index 30066f7..d3a1bd2 100644 --- a/tests/test_raillabel/format/test_sensor.py +++ b/tests/test_raillabel/format/test_sensor.py @@ -14,6 +14,7 @@ # == Fixtures ========================= + @pytest.fixture def sensors(sensor_lidar, sensor_camera, sensor_radar) -> t.Dict[str, Sensor]: return { @@ -22,6 +23,7 @@ def sensors(sensor_lidar, sensor_camera, sensor_radar) -> t.Dict[str, Sensor]: sensor_radar.uid: sensor_radar, } + @pytest.fixture def streams_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict) -> dict: return { @@ -30,6 +32,7 @@ def streams_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict) -> di sensor_radar_dict["uid"]: sensor_radar_dict["stream"], } + @pytest.fixture def coordinate_systems_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict) -> dict: return { @@ -40,7 +43,7 @@ def coordinate_systems_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_ sensor_lidar_dict["uid"], sensor_camera_dict["uid"], sensor_radar_dict["uid"], - ] + ], }, sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], @@ -60,9 +63,10 @@ def sensor_lidar_dict(transform_dict) -> dict: "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } + @pytest.fixture def sensor_lidar(transform) -> Sensor: return Sensor( @@ -81,17 +85,16 @@ def sensor_camera_dict(transform_dict, intrinsics_pinhole_dict) -> dict: "stream": { "type": "camera", "uri": "/S1206063/image", - "stream_properties": { - "intrinsics_pinhole": intrinsics_pinhole_dict - } + "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, }, "coordinate_system": { "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } + @pytest.fixture def sensor_camera(transform, intrinsics_pinhole) -> Sensor: return Sensor( @@ -110,17 +113,16 @@ def sensor_radar_dict(transform_dict, intrinsics_radar_dict) -> dict: "stream": { "type": "radar", "uri": "/talker1/Nvt/Cartesian", - "stream_properties": { - "intrinsics_radar": intrinsics_radar_dict - } + "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, }, "coordinate_system": { "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } + @pytest.fixture def sensor_radar(transform, intrinsics_radar) -> Sensor: return Sensor( @@ -131,8 +133,10 @@ def sensor_radar(transform, intrinsics_radar) -> Sensor: uri="/talker1/Nvt/Cartesian", ) + # == Tests ============================ + def test_lidar_fromdict(transform, transform_dict): sensor = Sensor.fromdict( uid="lidar", @@ -144,7 +148,7 @@ def test_lidar_fromdict(transform, transform_dict): "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, ) assert sensor.uid == "lidar" @@ -153,6 +157,7 @@ def test_lidar_fromdict(transform, transform_dict): assert sensor.type == SensorType.LIDAR assert sensor.uri == "/lidar_merged" + def test_lidar_asdict(transform, transform_dict): sensor = Sensor( uid="lidar", @@ -171,7 +176,7 @@ def test_lidar_asdict(transform, transform_dict): "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } @@ -181,15 +186,13 @@ def test_camera_fromdict(transform, transform_dict, intrinsics_pinhole, intrinsi stream_data_dict={ "type": "camera", "uri": "/S1206063/image", - "stream_properties": { - "intrinsics_pinhole": intrinsics_pinhole_dict - } + "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, }, cs_data_dict={ "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, ) assert sensor.uid == "rgb_middle" @@ -198,6 +201,7 @@ def test_camera_fromdict(transform, transform_dict, intrinsics_pinhole, intrinsi assert sensor.type == SensorType.CAMERA assert sensor.uri == "/S1206063/image" + def test_camera_asdict(transform, transform_dict, intrinsics_pinhole, intrinsics_pinhole_dict): sensor = Sensor( uid="rgb_middle", @@ -211,15 +215,13 @@ def test_camera_asdict(transform, transform_dict, intrinsics_pinhole, intrinsics "stream": { "type": "camera", "uri": "/S1206063/image", - "stream_properties": { - "intrinsics_pinhole": intrinsics_pinhole_dict - } + "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, }, "coordinate_system": { "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } @@ -229,15 +231,13 @@ def test_radar_fromdict(transform, transform_dict, intrinsics_radar, intrinsics_ stream_data_dict={ "type": "radar", "uri": "/talker1/Nvt/Cartesian", - "stream_properties": { - "intrinsics_radar": intrinsics_radar_dict - } + "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, }, cs_data_dict={ "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, ) assert sensor.uid == "radar" @@ -246,6 +246,7 @@ def test_radar_fromdict(transform, transform_dict, intrinsics_radar, intrinsics_ assert sensor.type == SensorType.RADAR assert sensor.uri == "/talker1/Nvt/Cartesian" + def test_radar_asdict(transform, transform_dict, intrinsics_radar, intrinsics_radar_dict): sensor = Sensor( uid="radar", @@ -259,15 +260,13 @@ def test_radar_asdict(transform, transform_dict, intrinsics_radar, intrinsics_ra "stream": { "type": "radar", "uri": "/talker1/Nvt/Cartesian", - "stream_properties": { - "intrinsics_radar": intrinsics_radar_dict - } + "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, }, "coordinate_system": { "type": "sensor", "parent": "base", "pose_wrt_parent": transform_dict, - } + }, } diff --git a/tests/test_raillabel/format/test_sensor_reference.py b/tests/test_raillabel/format/test_sensor_reference.py index d5a947c..5ed4920 100644 --- a/tests/test_raillabel/format/test_sensor_reference.py +++ b/tests/test_raillabel/format/test_sensor_reference.py @@ -14,38 +14,32 @@ # == Fixtures ========================= + @pytest.fixture def sensor_reference_camera_dict() -> dict: return { - "stream_properties": { - "sync": { - "timestamp": "1632321743.100000072" - } - }, - "uri": "rgb_test0.png" + "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, + "uri": "rgb_test0.png", } + @pytest.fixture def sensor_reference_camera(sensor_camera) -> dict: return SensorReference( - sensor=sensor_camera, - timestamp=Decimal("1632321743.100000072"), - uri="rgb_test0.png" + sensor=sensor_camera, timestamp=Decimal("1632321743.100000072"), uri="rgb_test0.png" ) + # == Tests ============================ + def test_fromdict(sensor_camera): sensor_reference = SensorReference.fromdict( { - "stream_properties": { - "sync": { - "timestamp": "1632321743.100000072" - } - }, - "uri": "rgb_test0.png" + "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, + "uri": "rgb_test0.png", }, - sensor_camera + sensor_camera, ) assert sensor_reference.sensor == sensor_camera @@ -55,18 +49,12 @@ def test_fromdict(sensor_camera): def test_asdict(sensor_camera): sensor_reference = SensorReference( - sensor=sensor_camera, - timestamp=Decimal("1632321743.100000072"), - uri="rgb_test0.png" + sensor=sensor_camera, timestamp=Decimal("1632321743.100000072"), uri="rgb_test0.png" ) assert sensor_reference.asdict() == { - "stream_properties": { - "sync": { - "timestamp": "1632321743.100000072" - } - }, - "uri": "rgb_test0.png" + "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, + "uri": "rgb_test0.png", } diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/test_raillabel/format/test_size2d.py index 62d895e..e79bc6e 100644 --- a/tests/test_raillabel/format/test_size2d.py +++ b/tests/test_raillabel/format/test_size2d.py @@ -13,20 +13,22 @@ # == Fixtures ========================= + @pytest.fixture def size2d_dict() -> dict: return [25, 1.344] + @pytest.fixture def size2d() -> dict: return Size2d(25, 1.344) + # == Tests ============================ + def test_fromdict(): - size2d = Size2d.fromdict( - [25, 1.344] - ) + size2d = Size2d.fromdict([25, 1.344]) assert size2d.x == 25 assert size2d.y == 1.344 diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/test_raillabel/format/test_size3d.py index bf5290e..2c3398a 100644 --- a/tests/test_raillabel/format/test_size3d.py +++ b/tests/test_raillabel/format/test_size3d.py @@ -13,20 +13,22 @@ # == Fixtures ========================= + @pytest.fixture def size3d_dict() -> dict: return [0.35, 0.7, 1.92] + @pytest.fixture def size3d() -> dict: return Size3d(0.35, 0.7, 1.92) + # == Tests ============================ + def test_fromdict(): - size3d = Size3d.fromdict( - [0.35, 0.7, 1.92] - ) + size3d = Size3d.fromdict([0.35, 0.7, 1.92]) assert size3d.x == 0.35 assert size3d.y == 0.7 @@ -34,11 +36,7 @@ def test_fromdict(): def test_asdict(): - size3d = Size3d( - x=0.35, - y=0.7, - z=1.92 - ) + size3d = Size3d(x=0.35, y=0.7, z=1.92) assert size3d.asdict() == [0.35, 0.7, 1.92] diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index 1a57267..c34e2b9 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -13,44 +13,31 @@ # == Fixtures ========================= + @pytest.fixture def transform_dict(point3d_dict, quaternion_dict) -> dict: - return { - "translation": point3d_dict, - "quaternion": quaternion_dict - } + return {"translation": point3d_dict, "quaternion": quaternion_dict} + @pytest.fixture def transform(point3d, quaternion) -> dict: - return Transform( - pos=point3d, - quat=quaternion - ) + return Transform(pos=point3d, quat=quaternion) + # == Tests ============================ + def test_fromdict(point3d, point3d_dict, quaternion, quaternion_dict): - transform = Transform.fromdict( - { - "translation": point3d_dict, - "quaternion": quaternion_dict - } - ) + transform = Transform.fromdict({"translation": point3d_dict, "quaternion": quaternion_dict}) assert transform.pos == point3d assert transform.quat == quaternion def test_asdict(point3d, point3d_dict, quaternion, quaternion_dict): - transform = Transform( - pos=point3d, - quat=quaternion - ) - - assert transform.asdict() == { - "translation": point3d_dict, - "quaternion": quaternion_dict - } + transform = Transform(pos=point3d, quat=quaternion) + + assert transform.asdict() == {"translation": point3d_dict, "quaternion": quaternion_dict} if __name__ == "__main__": diff --git a/tests/test_raillabel/save/test_save.py b/tests/test_raillabel/save/test_save.py index cbe2456..fae68b1 100644 --- a/tests/test_raillabel/save/test_save.py +++ b/tests/test_raillabel/save/test_save.py @@ -17,7 +17,6 @@ def test_save_scene(json_paths): with tempfile.TemporaryDirectory("w") as temp_dir: - scene_orig = raillabel.load(json_paths["openlabel_v1_short"]) raillabel.save(scene_orig, Path(temp_dir) / "test_save_file.json") @@ -28,7 +27,6 @@ def test_save_scene(json_paths): def test_save_json(json_data): with tempfile.TemporaryDirectory("w") as temp_dir: - stripped_input_data = deepcopy(json_data["openlabel_v1_short"]) # Removes the object data pointers from the example file so that it needs to be generated from the data From 1a529cb010a9872f5c53465ee1494d00db33ef20 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:19:55 +0100 Subject: [PATCH 013/190] lint: include unit test passing in pre-commit --- .pre-commit-config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bfeef0f..b2d8c20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -88,3 +88,14 @@ repos: rev: v2.6.7 hooks: - id: conventional-commits + + - repo: local + hooks: + - id: pytest + name: Run Unit Tests + stages: [commit] + types: [python] + entry: ./.venv/bin/pytest + language: system + pass_filenames: false + always_run: true From 3f70924460e07217fd0c8244e4f393fff1b1b1b3 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:28:42 +0100 Subject: [PATCH 014/190] lint: enable ruff E741 rule --- pyproject.toml | 1 - raillabel/format/_object_annotation.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8689255..b3c0e01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,7 +94,6 @@ ignore = [ "RUF012", "ANN204", "SIM110", - "E741", "C901", ] diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 3613841..d69ed6f 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -110,7 +110,7 @@ def _attributes_fromdict( if "attributes" not in data_dict: return {} - return {a["name"]: a["val"] for l in data_dict["attributes"].values() for a in l} + return {a["name"]: a["val"] for type_ in data_dict["attributes"].values() for a in type_} # === Special Methods ==================================================== From 50c9ab6a22b86958af052093019a29c69076390d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:29:57 +0100 Subject: [PATCH 015/190] lint: enable ruff SIM110 rule --- pyproject.toml | 3 +-- raillabel/filter/filter.py | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b3c0e01..ae907c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ ignore = [ "SIM103", # less readable in some cases imo # Will be removed gradually + "C901", "E721", "SLF001", "ISC003", @@ -93,8 +94,6 @@ ignore = [ "A002", "RUF012", "ANN204", - "SIM110", - "C901", ] [tool.mypy] diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index 6acf867..ef5df9c 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -209,11 +209,7 @@ def _remove_unused_sensor_references(frame: format.Frame, used_sensors: t.Set[st def _passes_filters(data, filters): - for f in filters: - if not f.passes_filter(data): - return False - - return True + return all(f.passes_filter(data) for f in filters) def _copy(object): From d56c5210e6070d12f45c1c7ab1f118cb29cc1207 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:31:13 +0100 Subject: [PATCH 016/190] lint: enable ruff ANN204 rule --- pyproject.toml | 1 - raillabel/filter/_filter_classes/_filter_abc.py | 2 +- raillabel/format/_object_annotation.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae907c1..0c3af99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,6 @@ ignore = [ "D417", "A002", "RUF012", - "ANN204", ] [tool.mypy] diff --git a/raillabel/filter/_filter_classes/_filter_abc.py b/raillabel/filter/_filter_classes/_filter_abc.py index 5de6b43..3346d37 100644 --- a/raillabel/filter/_filter_classes/_filter_abc.py +++ b/raillabel/filter/_filter_classes/_filter_abc.py @@ -31,7 +31,7 @@ def PARAMETERS(self) -> t.List[str]: def LEVELS(self) -> t.List[str]: raise NotImplementedError - def __init__(self, kwargs): + def __init__(self, kwargs) -> None: set_parameter = None for param in self.PARAMETERS: if param in kwargs and param is not None: diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index d69ed6f..9a01c59 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -114,7 +114,7 @@ def _attributes_fromdict( # === Special Methods ==================================================== - def __post_init__(self): + def __post_init__(self) -> None: """Check for required arguments after __init__. Inheritance in dataclasses has the flaw, that when the parent class has fields with From 010d5729a424daf103bc6a1e3b13cc136e06504f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:37:14 +0100 Subject: [PATCH 017/190] lint: enable ruff RUF012 rule --- pyproject.toml | 1 - raillabel/filter/_filter_classes/_filter_annotation_ids.py | 5 +++-- .../filter/_filter_classes/_filter_annotation_types.py | 5 +++-- raillabel/filter/_filter_classes/_filter_attributes.py | 5 +++-- raillabel/filter/_filter_classes/_filter_end.py | 5 +++-- raillabel/filter/_filter_classes/_filter_frames.py | 6 ++++-- raillabel/filter/_filter_classes/_filter_object_ids.py | 5 +++-- raillabel/filter/_filter_classes/_filter_object_types.py | 5 +++-- raillabel/filter/_filter_classes/_filter_sensors.py | 5 +++-- raillabel/filter/_filter_classes/_filter_start.py | 5 +++-- raillabel/format/bbox.py | 3 ++- raillabel/format/cuboid.py | 3 ++- raillabel/format/poly2d.py | 3 ++- raillabel/format/poly3d.py | 3 ++- raillabel/format/seg3d.py | 3 ++- 15 files changed, 38 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c3af99..5624541 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,6 @@ ignore = [ "TRY003", "D417", "A002", - "RUF012", ] [tool.mypy] diff --git a/raillabel/filter/_filter_classes/_filter_annotation_ids.py b/raillabel/filter/_filter_classes/_filter_annotation_ids.py index 8ed3738..b460fce 100644 --- a/raillabel/filter/_filter_classes/_filter_annotation_ids.py +++ b/raillabel/filter/_filter_classes/_filter_annotation_ids.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterAnnotationIds(_FilterABC): - PARAMETERS = ["include_annotation_ids", "exclude_annotation_ids"] - LEVELS = ["annotation"] + PARAMETERS: ClassVar = ["include_annotation_ids", "exclude_annotation_ids"] + LEVELS: ClassVar = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_annotation_ids is not None: diff --git a/raillabel/filter/_filter_classes/_filter_annotation_types.py b/raillabel/filter/_filter_classes/_filter_annotation_types.py index 328bbc5..e44d673 100644 --- a/raillabel/filter/_filter_classes/_filter_annotation_types.py +++ b/raillabel/filter/_filter_classes/_filter_annotation_types.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterAnnotationTypes(_FilterABC): - PARAMETERS = ["include_annotation_types", "exclude_annotation_types"] - LEVELS = ["annotation"] + PARAMETERS: ClassVar = ["include_annotation_types", "exclude_annotation_types"] + LEVELS: ClassVar = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_annotation_types is not None: diff --git a/raillabel/filter/_filter_classes/_filter_attributes.py b/raillabel/filter/_filter_classes/_filter_attributes.py index d4f93dd..44a8263 100644 --- a/raillabel/filter/_filter_classes/_filter_attributes.py +++ b/raillabel/filter/_filter_classes/_filter_attributes.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterAttributes(_FilterABC): - PARAMETERS = ["include_attributes", "exclude_attributes"] - LEVELS = ["annotation"] + PARAMETERS: ClassVar = ["include_attributes", "exclude_attributes"] + LEVELS: ClassVar = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_attributes is not None: diff --git a/raillabel/filter/_filter_classes/_filter_end.py b/raillabel/filter/_filter_classes/_filter_end.py index f7edc7b..cc97667 100644 --- a/raillabel/filter/_filter_classes/_filter_end.py +++ b/raillabel/filter/_filter_classes/_filter_end.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 from decimal import Decimal +from typing import ClassVar from ._filter_abc import Frame, _FilterABC class _FilterEnd(_FilterABC): - PARAMETERS = ["end_frame", "end_timestamp"] - LEVELS = ["frame"] + PARAMETERS: ClassVar = ["end_frame", "end_timestamp"] + LEVELS: ClassVar = ["frame"] def passes_filter(self, frame: Frame) -> bool: if self.end_frame is not None: diff --git a/raillabel/filter/_filter_classes/_filter_frames.py b/raillabel/filter/_filter_classes/_filter_frames.py index 02130fc..b2ee895 100644 --- a/raillabel/filter/_filter_classes/_filter_frames.py +++ b/raillabel/filter/_filter_classes/_filter_frames.py @@ -1,12 +1,14 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from typing import ClassVar + from ._filter_abc import Frame, _FilterABC class _FilterFrame(_FilterABC): - PARAMETERS = ["include_frames", "exclude_frames"] - LEVELS = ["frame"] + PARAMETERS: ClassVar = ["include_frames", "exclude_frames"] + LEVELS: ClassVar = ["frame"] def passes_filter(self, frame: Frame) -> bool: if self.include_frames is not None: diff --git a/raillabel/filter/_filter_classes/_filter_object_ids.py b/raillabel/filter/_filter_classes/_filter_object_ids.py index 600ecaa..fe85bc4 100644 --- a/raillabel/filter/_filter_classes/_filter_object_ids.py +++ b/raillabel/filter/_filter_classes/_filter_object_ids.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterObjectIds(_FilterABC): - PARAMETERS = ["include_object_ids", "exclude_object_ids"] - LEVELS = ["annotation"] + PARAMETERS: ClassVar = ["include_object_ids", "exclude_object_ids"] + LEVELS: ClassVar = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_object_ids is not None: diff --git a/raillabel/filter/_filter_classes/_filter_object_types.py b/raillabel/filter/_filter_classes/_filter_object_types.py index 35150d5..01d708d 100644 --- a/raillabel/filter/_filter_classes/_filter_object_types.py +++ b/raillabel/filter/_filter_classes/_filter_object_types.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterObjectTypes(_FilterABC): - PARAMETERS = ["include_object_types", "exclude_object_types"] - LEVELS = ["annotation"] + PARAMETERS: ClassVar = ["include_object_types", "exclude_object_types"] + LEVELS: ClassVar = ["annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_object_types is not None: diff --git a/raillabel/filter/_filter_classes/_filter_sensors.py b/raillabel/filter/_filter_classes/_filter_sensors.py index 5c4f98e..4aa65f9 100644 --- a/raillabel/filter/_filter_classes/_filter_sensors.py +++ b/raillabel/filter/_filter_classes/_filter_sensors.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 import typing as t +from typing import ClassVar from ._filter_abc import _FilterABC, _ObjectAnnotation class _FilterSensors(_FilterABC): - PARAMETERS = ["include_sensors", "exclude_sensors"] - LEVELS = ["frame_data", "annotation"] + PARAMETERS: ClassVar = ["include_sensors", "exclude_sensors"] + LEVELS: ClassVar = ["frame_data", "annotation"] def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: if self.include_sensors is not None: diff --git a/raillabel/filter/_filter_classes/_filter_start.py b/raillabel/filter/_filter_classes/_filter_start.py index 684049b..7a0bd17 100644 --- a/raillabel/filter/_filter_classes/_filter_start.py +++ b/raillabel/filter/_filter_classes/_filter_start.py @@ -2,13 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 from decimal import Decimal +from typing import ClassVar from ._filter_abc import Frame, _FilterABC class _FilterStart(_FilterABC): - PARAMETERS = ["start_frame", "start_timestamp"] - LEVELS = ["frame"] + PARAMETERS: ClassVar = ["start_frame", "start_timestamp"] + LEVELS: ClassVar = ["frame"] def passes_filter(self, frame: Frame) -> bool: if self.start_frame is not None: diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 5f44cb2..df04322 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass +from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object @@ -40,7 +41,7 @@ class Bbox(_ObjectAnnotation): size: Size2d = None OPENLABEL_ID = "bbox" - _REQ_FIELDS = ["pos", "size"] + _REQ_FIELDS: ClassVar = ["pos", "size"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Bbox": diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index e218d05..8f5999c 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass +from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object @@ -45,7 +46,7 @@ class Cuboid(_ObjectAnnotation): size: Size3d = None OPENLABEL_ID = "cuboid" - _REQ_FIELDS = ["pos", "size", "quat"] + _REQ_FIELDS: ClassVar = ["pos", "size", "quat"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Cuboid": diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index a6a19fc..8207445 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -3,6 +3,7 @@ import typing as t from dataclasses import dataclass +from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object @@ -49,7 +50,7 @@ class Poly2d(_ObjectAnnotation): mode: str = "MODE_POLY2D_ABSOLUTE" OPENLABEL_ID = "poly2d" - _REQ_FIELDS = ["points", "closed"] + _REQ_FIELDS: ClassVar = ["points", "closed"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly2d": diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index bc9ae0e..a4334eb 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -3,6 +3,7 @@ import typing as t from dataclasses import dataclass +from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object @@ -41,7 +42,7 @@ class Poly3d(_ObjectAnnotation): closed: bool = None OPENLABEL_ID = "poly3d" - _REQ_FIELDS = ["points", "closed"] + _REQ_FIELDS: ClassVar = ["points", "closed"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly3d": diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 5cdbb3b..872877c 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -3,6 +3,7 @@ import typing as t from dataclasses import dataclass +from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object @@ -36,7 +37,7 @@ class Seg3d(_ObjectAnnotation): point_ids: t.List[int] = None OPENLABEL_ID = "vec" - _REQ_FIELDS = ["point_ids"] + _REQ_FIELDS: ClassVar = ["point_ids"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Seg3d": From b7517efa40575f62deaae874b094cc914af7d983 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:40:15 +0100 Subject: [PATCH 018/190] lint: justify ruff A002 lint --- pyproject.toml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5624541..167cf81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,12 +46,14 @@ line-length = 101 exclude = ["tests/*", "docs/*"] select = ["ALL"] ignore = [ + "A002", # objects from OpenLABEL conflict with a Python builtin, but staying consistent with the domain is more important + "COM812", # conflicts with ruff formatter - "D100", # imo no docstrings are necessary in public modules - "D107", # __init__ docstrings are not necessary - "D203", # incompatible with D211 - "D213", # incompatible with D212 + "D100", # imo no docstrings are necessary in public modules + "D107", # __init__ docstrings are not necessary + "D203", # incompatible with D211 + "D213", # incompatible with D212 "FBT001", # flags in functions are not bad practice "FBT002", # flags in functions are not bad practice @@ -91,7 +93,6 @@ ignore = [ "ANN206", "TRY003", "D417", - "A002", ] [tool.mypy] From 0a7eb8285fceb574ed74b3dcf4af7d101a484ff6 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 17:48:52 +0100 Subject: [PATCH 019/190] lint: justify disabling ruff D417 lint --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 167cf81..53ca8c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ ignore = [ "D107", # __init__ docstrings are not necessary "D203", # incompatible with D211 "D213", # incompatible with D212 + "D417", # kwargs can not be typed for the filter function "FBT001", # flags in functions are not bad practice "FBT002", # flags in functions are not bad practice @@ -92,7 +93,6 @@ ignore = [ "S301", "ANN206", "TRY003", - "D417", ] [tool.mypy] From 3547d491be4c72cf82b6ad28e165ec0064b6e23b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 18:24:26 +0100 Subject: [PATCH 020/190] docs: update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b66ad8c..d28e26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,3 +92,14 @@ Release ## 3.3.0 - Introduce support for Python 3.13 + +# 4.0.0 +New Major changes to RailLabel! Over the time two major use cases for this package have crystalized. The first one are the users of the data sets published by Deutsche Bahn. The second one are the contractors providing the annotations. Since the two groups have vastly different requirements for this package, we decided to split it accordingly. + +If you just want to work with out data, then you are in luck. You can just continue using this package with even more focus on your needs. + +If you are building raillabel scenes yourself or want to manipulate the data in a safe way, then the [raillabel_providerkit](https://github.com/DSD-DBS/raillabel-providerkit) is for you. All functionality you may be missing in this new raillabel version will be provided over there with even better APIs. + +Functionality, that has been **moved** to the `raillabel_providerkit`: +- loading annotations in formats other than raillabel itself +- validating the content of files From 5eff927c07c7ac0c3f1c93006762fea6978f010a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 18:44:48 +0100 Subject: [PATCH 021/190] refactor: remove filter functionality --- raillabel/__init__.py | 1 - raillabel/filter/_filter_classes/__init__.py | 20 - .../filter/_filter_classes/_filter_abc.py | 55 --- .../_filter_classes/_filter_annotation_ids.py | 21 - .../_filter_annotation_types.py | 24 -- .../_filter_classes/_filter_attributes.py | 39 -- .../filter/_filter_classes/_filter_end.py | 21 - .../filter/_filter_classes/_filter_frames.py | 20 - .../_filter_classes/_filter_object_ids.py | 21 - .../_filter_classes/_filter_object_types.py | 21 - .../filter/_filter_classes/_filter_sensors.py | 21 - .../filter/_filter_classes/_filter_start.py | 21 - raillabel/filter/filter.py | 216 ---------- tests/test_raillabel/filter/test_filter.py | 400 ------------------ 14 files changed, 901 deletions(-) delete mode 100644 raillabel/filter/_filter_classes/__init__.py delete mode 100644 raillabel/filter/_filter_classes/_filter_abc.py delete mode 100644 raillabel/filter/_filter_classes/_filter_annotation_ids.py delete mode 100644 raillabel/filter/_filter_classes/_filter_annotation_types.py delete mode 100644 raillabel/filter/_filter_classes/_filter_attributes.py delete mode 100644 raillabel/filter/_filter_classes/_filter_end.py delete mode 100644 raillabel/filter/_filter_classes/_filter_frames.py delete mode 100644 raillabel/filter/_filter_classes/_filter_object_ids.py delete mode 100644 raillabel/filter/_filter_classes/_filter_object_types.py delete mode 100644 raillabel/filter/_filter_classes/_filter_sensors.py delete mode 100644 raillabel/filter/_filter_classes/_filter_start.py delete mode 100644 raillabel/filter/filter.py delete mode 100644 tests/test_raillabel/filter/test_filter.py diff --git a/raillabel/__init__.py b/raillabel/__init__.py index bfcb251..c32f684 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -6,7 +6,6 @@ from . import format from .exceptions import * -from .filter.filter import filter from .format import Scene from .load.load import load from .save.save import save diff --git a/raillabel/filter/_filter_classes/__init__.py b/raillabel/filter/_filter_classes/__init__.py deleted file mode 100644 index 097c89c..0000000 --- a/raillabel/filter/_filter_classes/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Package containing the loader classes for all supported formats.""" - -from importlib import import_module -from inspect import isclass -from pathlib import Path -from pkgutil import iter_modules - -# iterate through the modules in the current package -package_dir = str(Path(__file__).resolve().parent) -for _, module_name, _ in iter_modules([package_dir]): - # import the module and iterate through its attributes - module = import_module(f"{__name__}.{module_name}") - for attribute_name in dir(module): - attribute = getattr(module, attribute_name) - - if isclass(attribute): - # Add the class to this package's variables - globals()[attribute_name] = attribute diff --git a/raillabel/filter/_filter_classes/_filter_abc.py b/raillabel/filter/_filter_classes/_filter_abc.py deleted file mode 100644 index 3346d37..0000000 --- a/raillabel/filter/_filter_classes/_filter_abc.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from abc import ABC, abstractmethod, abstractproperty - -from ...format import Frame, _ObjectAnnotation - - -class _FilterABC(ABC): - """ABC for all filter classes. - - Creating a new filter - --------------------- - To create a new, custom filter create a new class in this dir, that inherits from _FilterABC. - Any class, that inherits from _FilterABC will automatically be loaded by the filter function. - Include the filter arguments (include_[...], exclude_[...], ...) in the PARAMETERS field. These - will be mutually exclusive.Select a level for the filter. The level determines where the filter - is going to be applied (e.g. at the frame level, annotation level, ...). Include the conditions - to pass the filter in the passes_filter() method, which returns True if the filter is passed. - The contents of the filter arguments can optionally be processed by the _process_filter_args(). - """ - - @property - @abstractproperty - def PARAMETERS(self) -> t.List[str]: - raise NotImplementedError - - @property - @abstractproperty - def LEVELS(self) -> t.List[str]: - raise NotImplementedError - - def __init__(self, kwargs) -> None: - set_parameter = None - for param in self.PARAMETERS: - if param in kwargs and param is not None: - if set_parameter is None: - setattr(self, param, self._process_filter_args(kwargs[param])) - set_parameter = param - else: - raise ValueError( - f"{set_parameter} and {param} are mutually exclusive, but were both set." - ) - - else: - setattr(self, param, None) - - @abstractmethod - def passes_filter(self, annotation: t.Union[t.Type[_ObjectAnnotation], Frame]) -> bool: - raise NotImplementedError - - def _process_filter_args(self, filter_args): - """Process filter arguments (optional).""" - return filter_args diff --git a/raillabel/filter/_filter_classes/_filter_annotation_ids.py b/raillabel/filter/_filter_classes/_filter_annotation_ids.py deleted file mode 100644 index b460fce..0000000 --- a/raillabel/filter/_filter_classes/_filter_annotation_ids.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterAnnotationIds(_FilterABC): - PARAMETERS: ClassVar = ["include_annotation_ids", "exclude_annotation_ids"] - LEVELS: ClassVar = ["annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_annotation_ids is not None: - return annotation.uid in self.include_annotation_ids - - if self.exclude_annotation_ids is not None: - return annotation.uid not in self.exclude_annotation_ids - - return True diff --git a/raillabel/filter/_filter_classes/_filter_annotation_types.py b/raillabel/filter/_filter_classes/_filter_annotation_types.py deleted file mode 100644 index e44d673..0000000 --- a/raillabel/filter/_filter_classes/_filter_annotation_types.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterAnnotationTypes(_FilterABC): - PARAMETERS: ClassVar = ["include_annotation_types", "exclude_annotation_types"] - LEVELS: ClassVar = ["annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_annotation_types is not None: - return annotation.__class__.__name__.lower() in self.include_annotation_types - - if self.exclude_annotation_types is not None: - return annotation.__class__.__name__.lower() not in self.exclude_annotation_types - - return True - - def _process_filter_args(self, filter_args): - return [arg.lower() for arg in filter_args] diff --git a/raillabel/filter/_filter_classes/_filter_attributes.py b/raillabel/filter/_filter_classes/_filter_attributes.py deleted file mode 100644 index 44a8263..0000000 --- a/raillabel/filter/_filter_classes/_filter_attributes.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterAttributes(_FilterABC): - PARAMETERS: ClassVar = ["include_attributes", "exclude_attributes"] - LEVELS: ClassVar = ["annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_attributes is not None: - for attribute_id, attribute_val in self.include_attributes.items(): - if attribute_val is None: - if attribute_id not in annotation.attributes: - return False - - elif ( - attribute_id not in annotation.attributes - or annotation.attributes[attribute_id] != attribute_val - ): - return False - - elif self.exclude_attributes is not None: - for attribute_id, attribute_val in self.exclude_attributes.items(): - if attribute_val is None: - if attribute_id in annotation.attributes: - return False - - elif ( - attribute_id in annotation.attributes - and attribute_val == annotation.attributes[attribute_id] - ): - return False - - return True diff --git a/raillabel/filter/_filter_classes/_filter_end.py b/raillabel/filter/_filter_classes/_filter_end.py deleted file mode 100644 index cc97667..0000000 --- a/raillabel/filter/_filter_classes/_filter_end.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from decimal import Decimal -from typing import ClassVar - -from ._filter_abc import Frame, _FilterABC - - -class _FilterEnd(_FilterABC): - PARAMETERS: ClassVar = ["end_frame", "end_timestamp"] - LEVELS: ClassVar = ["frame"] - - def passes_filter(self, frame: Frame) -> bool: - if self.end_frame is not None: - return frame.uid <= self.end_frame - - if self.end_timestamp is not None: - return frame.timestamp <= Decimal(self.end_timestamp) - - return True diff --git a/raillabel/filter/_filter_classes/_filter_frames.py b/raillabel/filter/_filter_classes/_filter_frames.py deleted file mode 100644 index b2ee895..0000000 --- a/raillabel/filter/_filter_classes/_filter_frames.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from typing import ClassVar - -from ._filter_abc import Frame, _FilterABC - - -class _FilterFrame(_FilterABC): - PARAMETERS: ClassVar = ["include_frames", "exclude_frames"] - LEVELS: ClassVar = ["frame"] - - def passes_filter(self, frame: Frame) -> bool: - if self.include_frames is not None: - return int(frame.uid) in self.include_frames - - if self.exclude_frames is not None: - return int(frame.uid) not in self.exclude_frames - - return True diff --git a/raillabel/filter/_filter_classes/_filter_object_ids.py b/raillabel/filter/_filter_classes/_filter_object_ids.py deleted file mode 100644 index fe85bc4..0000000 --- a/raillabel/filter/_filter_classes/_filter_object_ids.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterObjectIds(_FilterABC): - PARAMETERS: ClassVar = ["include_object_ids", "exclude_object_ids"] - LEVELS: ClassVar = ["annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_object_ids is not None: - return annotation.object.uid in self.include_object_ids - - if self.exclude_object_ids is not None: - return annotation.object.uid not in self.exclude_object_ids - - return True diff --git a/raillabel/filter/_filter_classes/_filter_object_types.py b/raillabel/filter/_filter_classes/_filter_object_types.py deleted file mode 100644 index 01d708d..0000000 --- a/raillabel/filter/_filter_classes/_filter_object_types.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterObjectTypes(_FilterABC): - PARAMETERS: ClassVar = ["include_object_types", "exclude_object_types"] - LEVELS: ClassVar = ["annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_object_types is not None: - return annotation.object.type in self.include_object_types - - if self.exclude_object_types is not None: - return annotation.object.type not in self.exclude_object_types - - return True diff --git a/raillabel/filter/_filter_classes/_filter_sensors.py b/raillabel/filter/_filter_classes/_filter_sensors.py deleted file mode 100644 index 4aa65f9..0000000 --- a/raillabel/filter/_filter_classes/_filter_sensors.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from typing import ClassVar - -from ._filter_abc import _FilterABC, _ObjectAnnotation - - -class _FilterSensors(_FilterABC): - PARAMETERS: ClassVar = ["include_sensors", "exclude_sensors"] - LEVELS: ClassVar = ["frame_data", "annotation"] - - def passes_filter(self, annotation: t.Type[_ObjectAnnotation]) -> bool: - if self.include_sensors is not None: - return annotation.sensor.uid in self.include_sensors - - if self.exclude_sensors is not None: - return annotation.sensor.uid not in self.exclude_sensors - - return True diff --git a/raillabel/filter/_filter_classes/_filter_start.py b/raillabel/filter/_filter_classes/_filter_start.py deleted file mode 100644 index 7a0bd17..0000000 --- a/raillabel/filter/_filter_classes/_filter_start.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from decimal import Decimal -from typing import ClassVar - -from ._filter_abc import Frame, _FilterABC - - -class _FilterStart(_FilterABC): - PARAMETERS: ClassVar = ["start_frame", "start_timestamp"] - LEVELS: ClassVar = ["frame"] - - def passes_filter(self, frame: Frame) -> bool: - if self.start_frame is not None: - return frame.uid >= self.start_frame - - if self.start_timestamp is not None: - return frame.timestamp >= Decimal(self.start_timestamp) - - return True diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py deleted file mode 100644 index ef5df9c..0000000 --- a/raillabel/filter/filter.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import pickle -import typing as t - -from .. import format -from . import _filter_classes - - -def filter(scene: format.Scene, **kwargs) -> format.Scene: - """Return a copy of the scene with the annotations filtered. - - Parameters - ---------- - scene: raillabel.Scene - Scene, which should be copied and filtered. - include_object_types: str or list of str, optional - List of class/type names that should be included in the filtered scene. If set, no - other classes/types will be copied. Mutually exclusive with exclude_object_types. - exclude_object_types: str or list of str, optional - List of class/type names that should be excluded in the filtered scene. If set, all - other classes/types will be copied. Mutually exclusive with include_object_types. - include_annotation_types: str or list of str, optional - List of annotation types (i.e. bboxs, cuboids, poly2ds, seg3ds) that should be included - in the filtered scene. If set, no other annotation types will be copied. Mutually - exclusive with exclude_annotation_types. - exclude_annotation_types: str or list of str, optional - List of annotation types (i.e. bboxs, cuboids, poly2ds, seg3ds) that should be excluded - in the filtered scene. If set, all other annotation types will be copied. Mutually - exclusive with include_annotation_types. - include_annotation_ids: str or list of str, optional - List of annotation UIDs that should be included in the filtered scene. If set, no other - annotation UIDs will be copied. Mutually exclusive with exclude_annotation_ids. - exclude_annotation_ids: str or list of str, optional - List of annotation UIDs that should be excluded in the filtered scene. If set, all - other annotation UIDs will be copied. Mutually exclusive with include_annotation_ids. - include_object_ids: str or list of str, optional - List of object UIDs that should be included in the filtered scene. If set, no other - objects will be copied. Mutually exclusive with exclude_object_ids. - exclude_object_ids: str or list of str, optional - List of object UIDs that should be excluded in the filtered scene. If set, all other - objects will be copied. Mutually exclusive with include_object_ids. - include_sensors: str or list of str - List of sensors that should be included in the filtered scene. If set, no other - sensors will be copied. Mutually exclusive with exclude_sensors. - exclude_sensors: str or list of str, optional - List of sensors that should be excluded in the filtered scene. If set, all other - sensors will be copied. Mutually exclusive with include_sensors. - include_attributes: dict, optional - Dict of attributes that should be included in the filtered scene. Dict keys are the - attribute names, values are the specific values that should be included. If the - value is set so None, all annotations with the attribute are included regardless of - value. Mutually exclusive with exclude_attributes. - exclude_attributes: dict, optional - Dict of attributes that should be excluded in the filtered scene. Dict keys are the - attribute names, values are the specific values that should be excluded. If the value - is set so None, all annotations with the attribute are excluded regardless of value. - Mutually exclusive with include_attributes. - include_frames: int or list of int, optional - List of frame UIDs that should be included in the filtered scene. If set, no other - frames will be copied. Mutually exclusive with exclude_frames. - exclude_frames: int or list of int, optional - List of frame UIDs that should be excluded in the filtered scene. If set, all other - frames will be copied. Mutually exclusive with include_frames. - start_frame: int, optional - Frame at which the filtered scene should start. Mutually exclusive with s - tart_timestamp. - end_frame: int, optional - Frame at which the filtered scene should end (inclusive). Mutually exclusive with - end_timestamp. - start_timestamp: decimal.Decimal, optional - Unix timestamp at which the filtered scene should start (inclusive). Mutually exclusive - with start_frame. - end_timestamp: decimal.Decimal, optional - Unix timestamp at which the filtered scene should end (inclusive). Mutually exclusive - with end_frame. - - Raises - ------ - ValueError - if two mutually exclusive parameters are set. - TypeError - if an unexpected keyword argument has been set. - - """ - filters_by_level = _collect_filter_classes(kwargs) - filtered_scene, used_sensors, used_objects = _filter_scene(_copy(scene), filters_by_level) - filtered_scene = _remove_unused(filtered_scene, used_sensors, used_objects) - - return filtered_scene - - -# --- Prepare filter classes - - -def _collect_filter_classes(kwargs) -> t.Tuple[t.List[t.Type], t.List[str]]: - filters = [] - supported_kwargs = [] - for cls in _filter_classes.__dict__.values(): - if ( - isinstance(cls, type) - and issubclass(cls, _filter_classes._FilterABC) - and cls != _filter_classes._FilterABC - ): - filters.append(cls(kwargs)) - supported_kwargs.extend(cls.PARAMETERS) - - _check_for_unsupported_arg(kwargs, supported_kwargs) - - return _seperate_filters_by_level(filters) - - -def _check_for_unsupported_arg(kwargs: t.List[str], supported_kwargs: t.List[str]): - for arg in kwargs: - if arg not in supported_kwargs: - raise TypeError( - f"filter() got an unexpected keyword argument '{arg}'. Supported keyword " - + f"arguments: {sorted(supported_kwargs)}" - ) - - -def _seperate_filters_by_level(filters: t.List[t.Type]) -> t.Dict[str, t.List[t.Type]]: - all_filter_levels = [level for f in filters for level in f.LEVELS] - - filters_by_level = {level: [] for level in all_filter_levels} - for level in filters_by_level: - for filter_class in filters: - if level in filter_class.LEVELS: - filters_by_level[level].append(filter_class) - - return filters_by_level - - -# --- Filter scene - - -def _filter_scene( - scene: format.Scene, filters_by_level: t.Dict[str, t.List[t.Type]] -) -> t.Tuple[format.Scene, t.Set[str], t.Set[str]]: - used_sensors = set() - used_objects = set() - - for frame_id, frame in list(scene.frames.items()): - if not _passes_filters(frame, filters_by_level["frame"]): - del scene.frames[frame_id] - continue - - for frame_data_id, frame_data in list(frame.frame_data.items()): - if _passes_filters(frame_data, filters_by_level["frame_data"]): - used_sensors.add(frame_data.sensor.uid) - - else: - del scene.frames[frame_id].frame_data[frame_data_id] - - for annotation_id, annotation in list(frame.annotations.items()): - if _passes_filters(annotation, filters_by_level["annotation"]): - used_objects.add(annotation.object.uid) - used_sensors.add(annotation.sensor.uid) - - else: - del scene.frames[frame_id].annotations[annotation_id] - - return scene, used_sensors, used_objects - - -# --- Remove unused - - -def _remove_unused( - scene: format.Scene, used_sensors: t.Set[str], used_objects: t.Set[str] -) -> format.Scene: - scene = _remove_unused_sensors(scene, used_sensors) - scene = _remove_unused_objects(scene, used_objects) - - for frame_id in scene.frames: - scene.frames[frame_id] = _remove_unused_sensor_references( - scene.frames[frame_id], used_sensors - ) - - return scene - - -def _remove_unused_sensors(scene: format.Scene, used_sensors: t.Set[str]) -> format.Scene: - for sensor_id in list(scene.sensors): - if sensor_id not in used_sensors: - del scene.sensors[sensor_id] - - return scene - - -def _remove_unused_objects(scene: format.Scene, used_objects: t.Set[str]) -> format.Scene: - for object_id in list(scene.objects): - if object_id not in used_objects: - del scene.objects[object_id] - - return scene - - -def _remove_unused_sensor_references(frame: format.Frame, used_sensors: t.Set[str]) -> format.Frame: - for sensor_id in list(frame.sensors): - if sensor_id not in used_sensors: - del frame.sensors[sensor_id] - - return frame - - -# --- Helper functions - - -def _passes_filters(data, filters): - return all(f.passes_filter(data) for f in filters) - - -def _copy(object): - return pickle.loads(pickle.dumps(object, -1)) diff --git a/tests/test_raillabel/filter/test_filter.py b/tests/test_raillabel/filter/test_filter.py deleted file mode 100644 index 9259abf..0000000 --- a/tests/test_raillabel/filter/test_filter.py +++ /dev/null @@ -1,400 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent)) - -import raillabel - - -def delete_sensor_from_data(data: dict, sensor_id: str) -> dict: - del data["openlabel"]["streams"][sensor_id] - del data["openlabel"]["coordinate_systems"][sensor_id] - del data["openlabel"]["coordinate_systems"]["base"]["children"][ - data["openlabel"]["coordinate_systems"]["base"]["children"].index(sensor_id) - ] - - for frame_id in data["openlabel"]["frames"]: - if sensor_id not in data["openlabel"]["frames"][frame_id]["frame_properties"]["streams"]: - continue - - del data["openlabel"]["frames"][frame_id]["frame_properties"]["streams"][sensor_id] - - return data - - -def test_filter_unexpected_kwarg(json_paths): - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - with pytest.raises(TypeError): - raillabel.filter(scene, unsupported_kwarg=[]) - - -def test_mutual_exclusivity(json_paths): - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - with pytest.raises(ValueError): - raillabel.filter(scene, include_frames=[0], exclude_frames=[1, 2]) - - -def test_filter_frames(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["1"] - del data["openlabel"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter(scene, include_frames=[0]) - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_frames=[1]) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_start(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["0"] - del data["openlabel"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for frame filter - scene_filtered = raillabel.filter(scene, start_frame=1) - assert scene_filtered == scene_filtered_ground_truth - - # Tests for timestamp filter - scene_filtered = raillabel.filter(scene, start_timestamp="1632321743.134150") - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_end(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["1"] - del data["openlabel"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for frame filter - scene_filtered = raillabel.filter(scene, end_frame=0) - assert scene_filtered == scene_filtered_ground_truth - - # Tests for timestamp filter - scene_filtered = raillabel.filter(scene, end_timestamp="1632321743.233250") - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_object_ids(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter( - scene, - include_object_ids=[ - "6fe55546-0dd7-4e40-b6b4-bb7ea3445772", - "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - ], - ) - - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter( - scene, exclude_object_ids=["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - ) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_object_types(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter(scene, include_object_types=["person"]) - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_object_types=["train"]) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_annotation_ids(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter( - scene, - include_annotation_ids=[ - "78f0ad89-2750-4a30-9d66-44c9da73a714", - "68b4e02c-40c8-4de0-89ad-bc00ed05a043", - "bebfbae4-61a2-4758-993c-efa846b050a5", - "3f63201c-fb33-4487-aff6-ae0aa5fa976c", - "dc2be700-8ee4-45c4-9256-920b5d55c917", - "c1087f1d-7271-4dee-83ad-519a4e3b78a8", - "50be7fe3-1f43-47ca-b65a-930e6cfacfeb", - "6ba42cbc-484e-4b8d-a022-b23c2bb6643c", - "5f28fa18-8f2a-4a40-a0b6-c0bbedc00f2e", - "e2503c5d-9fe4-4666-b510-ef644c5a766b", - "450ceb81-9778-4e63-bf89-42f3ed9f6747", - ], - ) - - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter( - scene, - exclude_annotation_ids=[ - "14f58fb0-add7-4ed9-85b3-74615986d854", - "536ac83a-32c8-4fce-8499-ef32716c64a6", - "e53bd5e3-980a-4fa7-a0f9-5a2e59ba663c", - "550df2c3-0e66-483e-bcc6-f3013b7e581b", - "12b21c52-06ea-4269-9805-e7167e7a74ed", - ], - ) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_annotation_types(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["cuboid"] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["vec"] - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter(scene, include_annotation_types=["bbox", "poly2d", "Num"]) - - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_annotation_types=["cuboid", "Poly3d", "seg3d"]) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_sensors(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - - data = delete_sensor_from_data(data, "lidar") - data = delete_sensor_from_data(data, "radar") - - del data["openlabel"]["frames"]["0"]["frame_properties"]["frame_data"]["num"][-1] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["cuboid"] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["vec"] - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - - del data["openlabel"]["frames"]["1"]["frame_properties"]["frame_data"]["num"][-1] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter(scene, include_sensors=["rgb_middle", "ir_middle"]) - - assert scene_filtered == scene_filtered_ground_truth - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_sensors=["lidar"]) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_include_attribute_ids(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["bbox"][1] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["poly2d"][1] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["cuboid"][1] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["vec"][1] - - del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ - "object_data" - ]["bbox"][1] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ - "object_data" - ]["cuboid"][1] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ - "object_data" - ]["vec"][1] - - data = delete_sensor_from_data(data, "ir_middle") - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for include filter - scene_filtered = raillabel.filter(scene, include_attributes={"test_text_attr0": None}) - - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_exclude_attribute_ids(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["bbox"][0] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["poly2d"][0] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["cuboid"][0] - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["vec"][0] - - del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ - "object_data" - ]["bbox"][0] - del data["openlabel"]["frames"]["1"]["objects"]["6fe55546-0dd7-4e40-b6b4-bb7ea3445772"][ - "object_data" - ]["poly2d"][0] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ - "object_data" - ]["cuboid"][0] - del data["openlabel"]["frames"]["1"]["objects"]["22dedd49-6dcb-413b-87ef-00ccfb532e98"][ - "object_data" - ]["vec"][0] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_attributes={"test_text_attr0": None}) - assert scene_filtered == scene_filtered_ground_truth - - -def test_filter_exclude_attribute_values(json_paths, json_data): - data = json_data["openlabel_v1_short"] - - # Loads scene - scene = raillabel.load(json_paths["openlabel_v1_short"]) - - # Deletes the excluded data - del data["openlabel"]["frames"]["0"]["objects"]["b40ba3ad-0327-46ff-9c28-2506cfd6d934"][ - "object_data" - ]["poly2d"][0] - data = delete_sensor_from_data(data, "radar") - - # Loads the ground truth filtered data - scene_filtered_ground_truth = raillabel.Scene.fromdict(data) - - # Tests for exclude filter - scene_filtered = raillabel.filter(scene, exclude_attributes={"test_num_attr0": 2}) - assert scene_filtered == scene_filtered_ground_truth - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) From 4f60a017ec977cc61e14f7a7fe2bf301925beaa8 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:01:25 +0100 Subject: [PATCH 022/190] refactor: remove _REQ_FIELD boilerplate and make sensor for annotation classes mandatory --- raillabel/format/_object_annotation.py | 35 ++----------------- raillabel/format/bbox.py | 7 ++-- raillabel/format/cuboid.py | 9 +++-- raillabel/format/num.py | 2 +- raillabel/format/poly2d.py | 7 ++-- raillabel/format/poly3d.py | 7 ++-- raillabel/format/seg3d.py | 5 ++- .../format/test_object_annotation.py | 18 ---------- 8 files changed, 18 insertions(+), 72 deletions(-) diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 9a01c59..0d692aa 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -18,24 +18,13 @@ class _ObjectAnnotation(ABC): uid: str object: Object - sensor: t.Optional[Sensor] = None - attributes: t.Dict[str, t.Union[int, float, bool, str, list]] = field(default_factory=dict) + sensor: Sensor + attributes: t.Dict[str, t.Union[int, float, bool, str, list]] @property def name(self) -> str: - if self.sensor is None: - raise AttributeError( - f"Annotation {self.uid} does not have a 'sensor', which is required " - + "to create the name." - ) - return f"{self.sensor.uid}__{self.OPENLABEL_ID}__{self.object.type}" - @property - @abstractproperty - def _REQ_FIELDS(self) -> t.List[str]: - raise NotImplementedError - @property @abstractproperty def OPENLABEL_ID(self) -> t.List[str]: @@ -112,26 +101,6 @@ def _attributes_fromdict( return {a["name"]: a["val"] for type_ in data_dict["attributes"].values() for a in type_} - # === Special Methods ==================================================== - - def __post_init__(self) -> None: - """Check for required arguments after __init__. - - Inheritance in dataclasses has the flaw, that when the parent class has fields with - defaults and the child class has fields without, a TypeError is raised due to required - fields following optional ones. This is solved by making all fields in the child - optional and checking for required fields in __post_init__(). - - Raises - ------ - TypeError - If a required field has not been set. - - """ - for f in self._REQ_FIELDS: - if getattr(self, f) is None: - raise TypeError(f"{f} is a required argument for {self.__class__.__name__}") - def annotation_classes() -> t.Dict[str, t.Type[_ObjectAnnotation]]: """Return dictionary with _Annotation child classes.""" diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index df04322..3c6ba95 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -24,7 +24,7 @@ class Bbox(_ObjectAnnotation): The dimensions of the bbox in pixels from the top left corner to the bottom right corner. object: raillabel.format.Object A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this annotation is labeled in. Default is None. attributes: dict, optional Attributes of the annotation. Dict keys are the name str of the attribute, values are the @@ -37,11 +37,10 @@ class Bbox(_ObjectAnnotation): """ - pos: Point2d = None - size: Size2d = None + pos: Point2d + size: Size2d OPENLABEL_ID = "bbox" - _REQ_FIELDS: ClassVar = ["pos", "size"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Bbox": diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 8f5999c..5d0ce15 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -28,7 +28,7 @@ class Cuboid(_ObjectAnnotation): The size of the cuboid in meters. object: raillabel.format.Object A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this annotation is labeled in. Default is None. attributes: dict, optional Attributes of the annotation. Dict keys are the name str of the attribute, values are the @@ -41,12 +41,11 @@ class Cuboid(_ObjectAnnotation): """ - pos: Point3d = None - quat: Quaternion = None - size: Size3d = None + pos: Point3d + quat: Quaternion + size: Size3d OPENLABEL_ID = "cuboid" - _REQ_FIELDS: ClassVar = ["pos", "size", "quat"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Cuboid": diff --git a/raillabel/format/num.py b/raillabel/format/num.py index a71e396..537910b 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -22,7 +22,7 @@ class Num: attributes: dict, optional Attributes of the annotation. Dict keys are the uid str of the attribute, values are the attribute values. Default is {}. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this value is represented in. Default is None. """ diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 8207445..5eb7e43 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -32,7 +32,7 @@ class Poly2d(_ObjectAnnotation): is 'MODE_POLY2D_ABSOLUTE'. object: raillabel.format.Object A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this annotation is labeled in. Default is None. attributes: dict, optional Attributes of the annotation. Dict keys are the name str of the attribute, values are the @@ -45,12 +45,11 @@ class Poly2d(_ObjectAnnotation): """ - points: t.List[Point2d] = None - closed: bool = None + points: t.List[Point2d] + closed: bool mode: str = "MODE_POLY2D_ABSOLUTE" OPENLABEL_ID = "poly2d" - _REQ_FIELDS: ClassVar = ["points", "closed"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly2d": diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index a4334eb..272f2a8 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -25,7 +25,7 @@ class Poly3d(_ObjectAnnotation): open line. object: raillabel.format.Object A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this annotation is labeled in. Default is None. attributes: dict, optional Attributes of the annotation. Dict keys are the name str of the attribute, values are the @@ -38,11 +38,10 @@ class Poly3d(_ObjectAnnotation): """ - points: t.List[Point3d] = None - closed: bool = None + points: t.List[Point3d] + closed: bool OPENLABEL_ID = "poly3d" - _REQ_FIELDS: ClassVar = ["points", "closed"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly3d": diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 872877c..daf35ef 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -21,7 +21,7 @@ class Seg3d(_ObjectAnnotation): The list of point indices. object: raillabel.format.Object A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor, optional + sensor: raillabel.format.Sensor A reference to the sensor, this annotation is labeled in. Default is None. attributes: dict, optional Attributes of the annotation. Dict keys are the name str of the attribute, values are the @@ -34,10 +34,9 @@ class Seg3d(_ObjectAnnotation): """ - point_ids: t.List[int] = None + point_ids: t.List[int] OPENLABEL_ID = "vec" - _REQ_FIELDS: ClassVar = ["point_ids"] @classmethod def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Seg3d": diff --git a/tests/test_raillabel/format/test_object_annotation.py b/tests/test_raillabel/format/test_object_annotation.py index 9f72bbb..7af241e 100644 --- a/tests/test_raillabel/format/test_object_annotation.py +++ b/tests/test_raillabel/format/test_object_annotation.py @@ -37,24 +37,6 @@ def all_annotations( # == Tests ============================ -def test_post_init_happy(object_person, point2d, size2d): - raillabel.format.Bbox( - uid="d2764400-8560-4991-a491-ada598b345c8", - object=object_person, - pos=point2d, - size=size2d, - ) - - -def test_post_init_unhappy(object_person, point2d): - with pytest.raises(TypeError): - raillabel.format.Bbox( - uid="d2764400-8560-4991-a491-ada598b345c8", - object=object_person, - pos=point2d, - ) - - def test_annotation_classes(): assert annotation_classes() == { "bbox": raillabel.format.Bbox, From d9700970f9dbbf6a3710125d3762182f31d8346e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:04:15 +0100 Subject: [PATCH 023/190] lint: justify disabling ruff TRY003 lint --- pyproject.toml | 1 - raillabel/__init__.py | 1 - raillabel/exceptions.py | 20 ----- raillabel/format/metadata.py | 5 -- raillabel/format/scene.py | 26 ------ tests/test_raillabel/format/test_metadata.py | 5 -- tests/test_raillabel/format/test_scene.py | 93 -------------------- 7 files changed, 151 deletions(-) delete mode 100644 raillabel/exceptions.py diff --git a/pyproject.toml b/pyproject.toml index 53ca8c5..3c644bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,6 @@ ignore = [ "ANN201", "S301", "ANN206", - "TRY003", ] [tool.mypy] diff --git a/raillabel/__init__.py b/raillabel/__init__.py index c32f684..109c356 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -5,7 +5,6 @@ from importlib import metadata from . import format -from .exceptions import * from .format import Scene from .load.load import load from .save.save import save diff --git a/raillabel/exceptions.py b/raillabel/exceptions.py deleted file mode 100644 index 99515bd..0000000 --- a/raillabel/exceptions.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - - -class MissingStreamError(Exception): - """Raised when a coordinate system has no corresponding stream.""" - - __module__ = "raillabel" - - -class MissingCoordinateSystemError(Exception): - """Raised when a stream has no corresponding coordinate system.""" - - __module__ = "raillabel" - - -class UnsupportedParentError(Exception): - """Raised when a coordinate system does not have 'base' as parent.""" - - __module__ = "raillabel" diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index 09522f2..ad54f16 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -115,11 +115,6 @@ def _set_additional_attributes(cls, metadata: "Metadata", data_dict: dict) -> "M if key in PRESET_KEYS: continue - is_key_a_valid_python_attribute = isinstance(key, str) and key.isidentifier() - - if not is_key_a_valid_python_attribute: - raise KeyError(f"'{key}' is not a valid python attribute") - setattr(metadata, key, value) return metadata diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 793cd48..7b34032 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -4,7 +4,6 @@ import typing as t from dataclasses import dataclass, field -from .. import exceptions from .frame import Frame from .frame_interval import FrameInterval from .metadata import Metadata @@ -151,8 +150,6 @@ def _prepare_data(cls, data: dict) -> dict: def _sensors_fromdict( cls, streams_dict: dict, coordinate_systems_dict: dict ) -> t.Dict[str, Sensor]: - cls._check_sensor_completeness(streams_dict, coordinate_systems_dict) - sensors = {} for stream_id in streams_dict: @@ -164,29 +161,6 @@ def _sensors_fromdict( return sensors - @classmethod - def _check_sensor_completeness(cls, streams_dict: dict, coordinate_systems_dict: dict): - for stream_uid in streams_dict: - if stream_uid not in coordinate_systems_dict: - raise exceptions.MissingCoordinateSystemError( - f"Stream {stream_uid} has no corresponding coordinate system." - ) - - for cs_uid in coordinate_systems_dict: - if cs_uid == "base": - continue - - if coordinate_systems_dict[cs_uid]["parent"] != "base": - raise exceptions.UnsupportedParentError( - f"Only 'base' is permitted as a parent for coordinate system {cs_uid}, " - + f"not {coordinate_systems_dict[cs_uid]['parent']}." - ) - - if cs_uid not in streams_dict: - raise exceptions.MissingStreamError( - f"Coordinate sytem {cs_uid} has no corresponding stream." - ) - @classmethod def _objects_fromdict(cls, object_dict: dict) -> t.Dict[str, Object]: return {uid: Object.fromdict(object, uid) for uid, object in object_dict.items()} diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py index 2ca9d66..2b6e289 100644 --- a/tests/test_raillabel/format/test_metadata.py +++ b/tests/test_raillabel/format/test_metadata.py @@ -88,11 +88,6 @@ def test_fromdict_additional_arg_valid(): assert metadata.additional_argument == "Some Value" -def test_fromdict_additional_arg_invalid(): - with pytest.raises(KeyError): - Metadata.fromdict({"schema_version": "1.0.0", "invalid python variable": "Some Value"}) - - def test_asdict_minimal(): metadata_dict = Metadata(schema_version="1.0.0").asdict() diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index 2490592..d0705a6 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -9,7 +9,6 @@ sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) -from raillabel import exceptions from raillabel.format import Frame, FrameInterval, Scene from raillabel.format.scene import _clean_dict @@ -149,98 +148,6 @@ def test_fromdict_sensors( } -def test_fromdict_missing_coordinate_system( - metadata_full_dict, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, -): - with pytest.raises(exceptions.MissingCoordinateSystemError): - Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_camera_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - }, - } - } - ) - - -def test_fromdict_missing_stream( - metadata_full_dict, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, -): - with pytest.raises(exceptions.MissingStreamError): - Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_lidar_dict["uid"], - sensor_camera_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], - }, - } - } - ) - - -def test_fromdict_unsupported_parent( - metadata_full_dict, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, -): - sensor_camera_dict["coordinate_system"]["parent"] = "unsupported_parent" - - with pytest.raises(exceptions.UnsupportedParentError): - Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_camera_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - }, - } - } - ) - - def test_fromdict_objects( metadata_full, metadata_full_dict, From b807076481836addf36e03017085744060222cc4 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:04:44 +0100 Subject: [PATCH 024/190] lint: justify disabling ruff ANN206 lint --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c644bd..6e5d5f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,6 @@ ignore = [ "ANN001", "ANN201", "S301", - "ANN206", ] [tool.mypy] From 8fac6db6135d49da92c319fba9e022231c9512b8 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:06:23 +0100 Subject: [PATCH 025/190] lint: justify disabling ruff S301 lint --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e5d5f6..ec488de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,6 @@ ignore = [ "ARG003", "ANN001", "ANN201", - "S301", ] [tool.mypy] From 4b5b42f99f863486542a3e7305fccf60bf012109 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:07:02 +0100 Subject: [PATCH 026/190] lint: justify disabling ruff ANN201 lint --- pyproject.toml | 1 - raillabel/save/save.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ec488de..ca54350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,6 @@ ignore = [ "PERF401", "ARG003", "ANN001", - "ANN201", ] [tool.mypy] diff --git a/raillabel/save/save.py b/raillabel/save/save.py index 8778441..14d2901 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -7,7 +7,7 @@ from ..format import Scene -def save(scene: Scene, path: str, prettify_json: bool = False): +def save(scene: Scene, path: str, prettify_json: bool = False) -> None: """Save a raillabel.Scene in a JSON file. Parameters From 63257d29c01bb8f0fa3d4aaf034eafcf6c301c70 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:08:56 +0100 Subject: [PATCH 027/190] lint: enable ruff ANN001 rule --- pyproject.toml | 1 - raillabel/format/frame.py | 18 ------------------ raillabel/format/sensor.py | 6 +++--- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ca54350..632339e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,6 @@ ignore = [ "N802", "PERF401", "ARG003", - "ANN001", ] [tool.mypy] diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 7ec9068..3b8f8fb 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -225,21 +225,3 @@ def _annotations_asdict(self) -> dict: ) return annotations_dict - - def __eq__(self, other) -> bool: - """Handel equal comparisons.""" - if not hasattr(other, "__dict__"): - return False - - if len(self.__dict__) != len(other.__dict__): - return False - - for attr in self.__dict__: - if type(getattr(self, attr)) == type(self): - if getattr(self, attr).uid != getattr(other, attr).uid: - return False - - elif getattr(self, attr) != getattr(other, attr): - return False - - return True diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index c757b8e..f52dac7 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -119,7 +119,7 @@ def _as_stream_dict(self) -> dict: return stream_repr @classmethod - def _extrinsics_fromdict(cls, data_dict) -> t.Optional[Transform]: + def _extrinsics_fromdict(cls, data_dict: t.Dict) -> t.Optional[Transform]: if "pose_wrt_parent" not in data_dict: return None @@ -139,7 +139,7 @@ def _extrinsics_fromdict(cls, data_dict) -> t.Optional[Transform]: @classmethod def _intrinsics_fromdict( - cls, data_dict, sensor_type: t.Optional["SensorType"] + cls, data_dict: t.Dict, sensor_type: t.Optional["SensorType"] ) -> t.Optional[IntrinsicsPinhole]: if "stream_properties" not in data_dict: return None @@ -157,7 +157,7 @@ def _intrinsics_fromdict( return None @classmethod - def _type_fromdict(cls, data_dict) -> t.Optional["SensorType"]: + def _type_fromdict(cls, data_dict: t.Dict) -> t.Optional["SensorType"]: if "type" not in data_dict: return None From 0cf34f966250d4fe63aa51a0ee889c2d94693438 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:10:30 +0100 Subject: [PATCH 028/190] lint: enable ruff ARG003 rule --- pyproject.toml | 1 - raillabel/format/frame.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 632339e..8b9490b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,6 @@ ignore = [ "PLW0602", "N802", "PERF401", - "ARG003", ] [tool.mypy] diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 3b8f8fb..2c56a41 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -97,9 +97,9 @@ def fromdict( frame = Frame( uid=int(uid), timestamp=cls._timestamp_fromdict(data_dict), - sensors=cls._sensors_fromdict(data_dict, int(uid), sensors), + sensors=cls._sensors_fromdict(data_dict, sensors), frame_data=cls._frame_data_fromdict(data_dict, sensors), - annotations=cls._objects_fromdict(data_dict, int(uid), objects, sensors), + annotations=cls._objects_fromdict(data_dict, objects, sensors), ) return frame @@ -149,7 +149,7 @@ def _timestamp_fromdict(cls, data_dict: dict) -> t.Optional[decimal.Decimal]: @classmethod def _sensors_fromdict( - cls, data_dict: dict, frame_uid: int, scene_sensors: t.Dict[str, Sensor] + cls, data_dict: dict, scene_sensors: t.Dict[str, Sensor] ) -> t.Dict[str, SensorReference]: if "frame_properties" not in data_dict or "streams" not in data_dict["frame_properties"]: return {} @@ -179,7 +179,6 @@ def _frame_data_fromdict(cls, data_dict: dict, sensors: t.Dict[str, Sensor]) -> def _objects_fromdict( cls, data_dict: dict, - frame_id: int, objects: t.Dict[str, Object], sensors: t.Dict[str, Sensor], ) -> t.Dict[uuid.UUID, t.Type[_ObjectAnnotation]]: From ea273c0e5ae2d30e9de5681ea940ed2a4e65b6af Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:12:37 +0100 Subject: [PATCH 029/190] lint: enable ruff PERF401 rule --- pyproject.toml | 1 - raillabel/format/frame_interval.py | 9 ++++----- raillabel/format/poly2d.py | 8 ++++---- raillabel/format/poly3d.py | 10 ++++------ 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8b9490b..75f6124 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ ignore = [ "A001", "PLW0602", "N802", - "PERF401", ] [tool.mypy] diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 8243c87..576c586 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -63,11 +63,10 @@ def from_frame_uids(cls, frame_uids: t.List[int]) -> t.List["FrameInterval"]: sorted_frame_uids = sorted(frame_uids) frame_uid_intervals = cls._slice_into_intervals(sorted_frame_uids) - frame_intervals = [] - for interval in frame_uid_intervals: - frame_intervals.append(FrameInterval(frame_start=interval[0], frame_end=interval[-1])) - - return frame_intervals + return [ + FrameInterval(frame_start=interval[0], frame_end=interval[-1]) + for interval in frame_uid_intervals + ] def asdict(self) -> dict: """Export self as a dict compatible with the OpenLABEL schema. diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 5eb7e43..1a73d8a 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -108,7 +108,7 @@ def asdict(self) -> dict: @classmethod def _points_fromdict(cls, data_dict: dict) -> t.List[Point2d]: - points = [] - for i in range(0, len(data_dict["val"]), 2): - points.append(Point2d(x=data_dict["val"][i], y=data_dict["val"][i + 1])) - return points + return [ + Point2d(x=data_dict["val"][i], y=data_dict["val"][i + 1]) + for i in range(0, len(data_dict["val"]), 2) + ] diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 272f2a8..504e7e5 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -98,9 +98,7 @@ def asdict(self) -> dict: @classmethod def _points_fromdict(cls, data_dict: dict) -> t.List[Point3d]: - points = [] - for i in range(0, len(data_dict["val"]), 3): - points.append( - Point3d(x=data_dict["val"][i], y=data_dict["val"][i + 1], z=data_dict["val"][i + 2]) - ) - return points + return [ + Point3d(x=data_dict["val"][i], y=data_dict["val"][i + 1], z=data_dict["val"][i + 2]) + for i in range(0, len(data_dict["val"]), 3) + ] From 2c8a11aa99700b1d0cb57cca051328849d990046 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:14:28 +0100 Subject: [PATCH 030/190] lint: justify disabling ruff N802 lint --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 75f6124..a180849 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,8 @@ ignore = [ "ISC001", # conflicts with ruff formatter + "N802", # does not allow constant abstractproperties + "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects "TCH002", # same as TCH001 "TCH003", # same as TCH001 @@ -85,7 +87,6 @@ ignore = [ "ANN202", "A001", "PLW0602", - "N802", ] [tool.mypy] From 95020c52ebd5cddbc73e345884169af21d3f6562 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:16:21 +0100 Subject: [PATCH 031/190] lint: enable ruff PLW0602 rule --- pyproject.toml | 1 - raillabel/format/_object_annotation.py | 13 +++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a180849..7417866 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ ignore = [ "ANN003", "ANN202", "A001", - "PLW0602", ] [tool.mypy] diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 0d692aa..63d765e 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -104,12 +104,7 @@ def _attributes_fromdict( def annotation_classes() -> t.Dict[str, t.Type[_ObjectAnnotation]]: """Return dictionary with _Annotation child classes.""" - return ANNOTATION_CLASSES - - -def _collect_annotation_classes(): - """Collect annotation child classes and store them.""" - global ANNOTATION_CLASSES + out = {} package_dir = str(Path(__file__).resolve().parent) for _, module_name, _ in iter_modules([package_dir]): @@ -122,8 +117,6 @@ def _collect_annotation_classes(): and issubclass(attribute, _ObjectAnnotation) and attribute != _ObjectAnnotation ): - ANNOTATION_CLASSES[attribute.OPENLABEL_ID] = attribute - + out[attribute.OPENLABEL_ID] = attribute -ANNOTATION_CLASSES = {} -_collect_annotation_classes() + return out From 33a5649ad944a169b0f6566d8f3f282ed5456891 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:19:00 +0100 Subject: [PATCH 032/190] lint: enable ruff A001 rule --- pyproject.toml | 1 - raillabel/format/scene.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7417866..cb268ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,6 @@ ignore = [ "INP001", "ANN003", "ANN202", - "A001", ] [tool.mypy] diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 7b34032..b1ba070 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -163,7 +163,7 @@ def _sensors_fromdict( @classmethod def _objects_fromdict(cls, object_dict: dict) -> t.Dict[str, Object]: - return {uid: Object.fromdict(object, uid) for uid, object in object_dict.items()} + return {uid: Object.fromdict(object_, uid) for uid, object_ in object_dict.items()} @classmethod def _frames_fromdict( @@ -194,8 +194,8 @@ def _coordinate_systems_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: def _objects_asdict(self, objects: t.Dict[str, Object], calculate_pointers: bool) -> dict: if calculate_pointers: - return {str(uid): object.asdict(self.frames) for uid, object in objects.items()} - return {str(uid): object.asdict() for uid, object in objects.items()} + return {str(uid): object_.asdict(self.frames) for uid, object_ in objects.items()} + return {str(uid): object_.asdict() for uid, object_ in objects.items()} def _frames_asdict(self, frames: t.Dict[int, Frame]) -> dict: return {str(uid): frame.asdict() for uid, frame in frames.items()} From b378f709b41371251b5a53ade2569e159d99aa92 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:19:16 +0100 Subject: [PATCH 033/190] lint: enable ruff ANN202 rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb268ed..fb708eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,6 @@ ignore = [ "RET504", "INP001", "ANN003", - "ANN202", ] [tool.mypy] From 6f560cd9219f1a5a1320fd4e0aeb25e3e537ecab Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:19:38 +0100 Subject: [PATCH 034/190] lint: enable ruff ANN003 rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fb708eb..f18d56c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,6 @@ ignore = [ "SIM102", "RET504", "INP001", - "ANN003", ] [tool.mypy] From e78c29a99c3b3f1c596bdcf09574aa28b92887d4 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:21:51 +0100 Subject: [PATCH 035/190] lint: justify disabling ruff INP001 lint --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f18d56c..83e6ea0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,8 @@ ignore = [ "FBT001", # flags in functions are not bad practice "FBT002", # flags in functions are not bad practice + "INP001", # load and save are not meant as packages and are only folders for consistency + "ISC001", # conflicts with ruff formatter "N802", # does not allow constant abstractproperties @@ -82,7 +84,6 @@ ignore = [ "F401", "SIM102", "RET504", - "INP001", ] [tool.mypy] From b48ff162d7b9c77230998c7d381bc91630ff8368 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:23:14 +0100 Subject: [PATCH 036/190] lint: enable ruff RET504 rule --- pyproject.toml | 1 - raillabel/format/frame.py | 3 +-- raillabel/format/intrinsics_pinhole.py | 4 +--- raillabel/format/transform.py | 4 +--- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83e6ea0..9c4158d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,6 @@ ignore = [ "EM102", "F401", "SIM102", - "RET504", ] [tool.mypy] diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 2c56a41..43590eb 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -94,14 +94,13 @@ def fromdict( Converted Frame object. """ - frame = Frame( + return Frame( uid=int(uid), timestamp=cls._timestamp_fromdict(data_dict), sensors=cls._sensors_fromdict(data_dict, sensors), frame_data=cls._frame_data_fromdict(data_dict, sensors), annotations=cls._objects_fromdict(data_dict, objects, sensors), ) - return frame def asdict(self) -> dict: """Export self as a dict compatible with the OpenLABEL schema. diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 2e59cf4..e781dde 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -68,11 +68,9 @@ def asdict(self) -> dict: if an attribute can not be converted to the type required by the OpenLabel schema. """ - dict_repr = { + return { "camera_matrix": list(self.camera_matrix), "distortion_coeffs": list(self.distortion), "width_px": int(self.width_px), "height_px": int(self.height_px), } - - return dict_repr diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 36f19e6..b7c4f13 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -52,9 +52,7 @@ def asdict(self) -> dict: if an attribute can not be converted to the type required by the OpenLabel schema. """ - dict_repr = { + return { "translation": self.pos.asdict(), "quaternion": self.quat.asdict(), } - - return dict_repr From 27e7ac11260442225b2b8ab90430dbd9fdacd13b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:24:08 +0100 Subject: [PATCH 037/190] lint: enable ruff SIM102 rule --- pyproject.toml | 1 - raillabel/format/sensor.py | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c4158d..e829afa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,6 @@ ignore = [ "F403", "EM102", "F401", - "SIM102", ] [tool.mypy] diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index f52dac7..b3b01b2 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -150,9 +150,10 @@ def _intrinsics_fromdict( data_dict["stream_properties"]["intrinsics_pinhole"] ) - elif sensor_type == SensorType.RADAR: - if "intrinsics_radar" in data_dict["stream_properties"]: - return IntrinsicsRadar.fromdict(data_dict["stream_properties"]["intrinsics_radar"]) + elif ( + sensor_type == SensorType.RADAR and "intrinsics_radar" in data_dict["stream_properties"] + ): + return IntrinsicsRadar.fromdict(data_dict["stream_properties"]["intrinsics_radar"]) return None From 042e15960d5018cbe51a415824c81a1744e9eb81 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:26:29 +0100 Subject: [PATCH 038/190] lint: enable ruff F401 rule --- pyproject.toml | 1 - raillabel/__init__.py | 7 +++++++ raillabel/format/__init__.py | 28 ++++++++++++++++++++++++++ raillabel/format/_object_annotation.py | 2 +- raillabel/format/bbox.py | 1 - raillabel/format/cuboid.py | 1 - raillabel/format/poly2d.py | 1 - raillabel/format/poly3d.py | 1 - raillabel/format/seg3d.py | 1 - 9 files changed, 36 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e829afa..a4258cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,6 @@ ignore = [ "FA100", "F403", "EM102", - "F401", ] [tool.mypy] diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 109c356..5b141b9 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -9,6 +9,13 @@ from .load.load import load from .save.save import save +__all__ = [ + "format", + "Scene", + "load", + "save", +] + try: __version__ = metadata.version("raillabel") except metadata.PackageNotFoundError: diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 4ca2d6b..5761cc8 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -25,3 +25,31 @@ from .size2d import Size2d from .size3d import Size3d from .transform import Transform + +__all__ = [ + "_ObjectAnnotation", + "annotation_classes", + "Bbox", + "Cuboid", + "ElementDataPointer", + "Frame", + "FrameInterval", + "IntrinsicsPinhole", + "IntrinsicsRadar", + "Metadata", + "Num", + "Object", + "Point2d", + "Point3d", + "Poly2d", + "Poly3d", + "Quaternion", + "Scene", + "Seg3d", + "Sensor", + "SensorType", + "SensorReference", + "Size2d", + "Size3d", + "Transform", +] diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 63d765e..d197e76 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -3,7 +3,7 @@ import typing as t from abc import ABC, abstractmethod, abstractproperty -from dataclasses import dataclass, field +from dataclasses import dataclass from importlib import import_module from inspect import isclass from pathlib import Path diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 3c6ba95..55a2a31 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass -from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 5d0ce15..e3b086b 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass -from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 1a73d8a..adc1d06 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -3,7 +3,6 @@ import typing as t from dataclasses import dataclass -from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 504e7e5..e350801 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -3,7 +3,6 @@ import typing as t from dataclasses import dataclass -from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index daf35ef..588933f 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -3,7 +3,6 @@ import typing as t from dataclasses import dataclass -from typing import ClassVar from ._object_annotation import _ObjectAnnotation from .object import Object From 1ee8d6468c70a045551fd90bfb23cd631ff980ef Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:26:50 +0100 Subject: [PATCH 039/190] lint: enable ruff EM102 rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a4258cd..0b09016 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,6 @@ ignore = [ "TID252", "FA100", "F403", - "EM102", ] [tool.mypy] From 02e86980161d94719f5e5a32fbbc468308b52ef8 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:27:06 +0100 Subject: [PATCH 040/190] lint: enable ruff F403 rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0b09016..5eb720a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ ignore = [ "N806", "TID252", "FA100", - "F403", ] [tool.mypy] From 5e6daeb5ddd0a3bd4ca04c9c0fa46178b8ae282e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:41:29 +0100 Subject: [PATCH 041/190] lint: enable ruff FA100 rule --- pyproject.toml | 1 - raillabel/format/_attribute_type.py | 5 ++- raillabel/format/_object_annotation.py | 25 ++++++------ raillabel/format/bbox.py | 4 +- raillabel/format/cuboid.py | 4 +- raillabel/format/element_data_pointer.py | 11 +++--- raillabel/format/frame.py | 40 ++++++++++---------- raillabel/format/frame_interval.py | 9 +++-- raillabel/format/intrinsics_pinhole.py | 9 +++-- raillabel/format/intrinsics_radar.py | 4 +- raillabel/format/metadata.py | 23 +++++------ raillabel/format/num.py | 9 +++-- raillabel/format/object.py | 44 +++++++++++----------- raillabel/format/point2d.py | 4 +- raillabel/format/point3d.py | 4 +- raillabel/format/poly2d.py | 9 +++-- raillabel/format/poly3d.py | 9 +++-- raillabel/format/quaternion.py | 4 +- raillabel/format/scene.py | 31 +++++++-------- raillabel/format/seg3d.py | 7 ++-- raillabel/format/sensor.py | 23 +++++------ raillabel/format/sensor_reference.py | 7 ++-- raillabel/format/size2d.py | 4 +- raillabel/format/size3d.py | 4 +- raillabel/format/transform.py | 4 +- tests/conftest.py | 10 ++--- tests/test_raillabel/format/test_object.py | 4 +- tests/test_raillabel/format/test_sensor.py | 2 +- 28 files changed, 173 insertions(+), 141 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5eb720a..fb3f8dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,6 @@ ignore = [ "ISC003", "N806", "TID252", - "FA100", ] [tool.mypy] diff --git a/raillabel/format/_attribute_type.py b/raillabel/format/_attribute_type.py index 9238131..bfae6f2 100644 --- a/raillabel/format/_attribute_type.py +++ b/raillabel/format/_attribute_type.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from enum import Enum @@ -14,7 +15,7 @@ class AttributeType(Enum): VEC = "vec" @classmethod - def from_value(cls, attribute_value_class: t.Type) -> "AttributeType": + def from_value(cls, attribute_value_class: type) -> AttributeType: """Return AttributeType based on class of attribute value. Parameters diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index d197e76..2c0a877 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from abc import ABC, abstractmethod, abstractproperty from dataclasses import dataclass from importlib import import_module @@ -19,7 +20,7 @@ class _ObjectAnnotation(ABC): uid: str object: Object sensor: Sensor - attributes: t.Dict[str, t.Union[int, float, bool, str, list]] + attributes: dict[str, int | float | bool | str | list] @property def name(self) -> str: @@ -27,35 +28,35 @@ def name(self) -> str: @property @abstractproperty - def OPENLABEL_ID(self) -> t.List[str]: + def OPENLABEL_ID(self) -> list[str]: raise NotImplementedError # === Public Methods ===================================================== @abstractmethod - def asdict(self) -> t.Dict: + def asdict(self) -> dict: raise NotImplementedError @classmethod @abstractmethod def fromdict( cls, - data_dict: t.Dict, - sensors: t.Dict, + data_dict: dict, + sensors: dict, object: Object, - ) -> t.Type["_ObjectAnnotation"]: + ) -> type[_ObjectAnnotation]: raise NotImplementedError # === Private Methods ==================================================== - def _annotation_required_fields_asdict(self) -> t.Dict: + def _annotation_required_fields_asdict(self) -> dict: """Return the required fields from the parent class to dict.""" return { "uid": str(self.uid), "name": str(self.name), } - def _annotation_optional_fields_asdict(self) -> t.Dict: + def _annotation_optional_fields_asdict(self) -> dict: """Return the optional fields from the parent class to dict.""" dict_repr = {} @@ -81,7 +82,7 @@ def _attributes_asdict(self, attributes: dict) -> dict: return attributes_dict @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Optional[Sensor]: + def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor | None: is_coordinate_system_in_data = ( "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" ) @@ -95,14 +96,14 @@ def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Option def _attributes_fromdict( cls, data_dict: dict, - ) -> t.Dict[str, t.Union[int, float, bool, str, list]]: + ) -> dict[str, int | float | bool | str | list]: if "attributes" not in data_dict: return {} return {a["name"]: a["val"] for type_ in data_dict["attributes"].values() for a in type_} -def annotation_classes() -> t.Dict[str, t.Type[_ObjectAnnotation]]: +def annotation_classes() -> dict[str, type[_ObjectAnnotation]]: """Return dictionary with _Annotation child classes.""" out = {} diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 55a2a31..67406e8 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation @@ -42,7 +44,7 @@ class Bbox(_ObjectAnnotation): OPENLABEL_ID = "bbox" @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Bbox": + def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Bbox: """Generate a Bbox object from a dict. Parameters diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index e3b086b..36bcf2d 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation @@ -47,7 +49,7 @@ class Cuboid(_ObjectAnnotation): OPENLABEL_ID = "cuboid" @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Cuboid": + def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Cuboid: """Generate a Cuboid object from a dict. Parameters diff --git a/raillabel/format/element_data_pointer.py b/raillabel/format/element_data_pointer.py index d88a8e8..f2c3e07 100644 --- a/raillabel/format/element_data_pointer.py +++ b/raillabel/format/element_data_pointer.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from ._attribute_type import AttributeType @@ -31,8 +32,8 @@ class ElementDataPointer: """ uid: str - frame_intervals: t.List[FrameInterval] - attribute_pointers: t.Dict[str, AttributeType] + frame_intervals: list[FrameInterval] + attribute_pointers: dict[str, AttributeType] @property def annotation_type(self) -> str: @@ -59,10 +60,10 @@ def asdict(self) -> dict: "attribute_pointers": self._attribute_pointers_asdict(), } - def _frame_intervals_asdict(self) -> t.List[t.Dict[str, int]]: + def _frame_intervals_asdict(self) -> list[dict[str, int]]: return [fi.asdict() for fi in self.frame_intervals] - def _attribute_pointers_asdict(self) -> t.Dict[str, str]: + def _attribute_pointers_asdict(self) -> dict[str, str]: return { attr_name: attr_type.value for attr_name, attr_type in self.attribute_pointers.items() } diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 43590eb..abe32b4 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import decimal import typing as t import uuid @@ -42,13 +44,13 @@ class Frame: """ uid: int - timestamp: t.Optional[decimal.Decimal] = None - sensors: t.Dict[str, SensorReference] = field(default_factory=dict) - frame_data: t.Dict[str, Num] = field(default_factory=dict) - annotations: t.Dict[str, t.Type[_ObjectAnnotation]] = field(default_factory=dict) + timestamp: decimal.Decimal | None = None + sensors: dict[str, SensorReference] = field(default_factory=dict) + frame_data: dict[str, Num] = field(default_factory=dict) + annotations: dict[str, type[_ObjectAnnotation]] = field(default_factory=dict) @property - def object_data(self) -> t.Dict[str, t.Dict[str, t.Type[_ObjectAnnotation]]]: + def object_data(self) -> dict[str, dict[str, type[_ObjectAnnotation]]]: """Return annotations categorized by Object-Id. Returns @@ -72,9 +74,9 @@ def fromdict( cls, uid: str, data_dict: dict, - objects: t.Dict[str, Object], - sensors: t.Dict[str, Sensor], - ) -> "Frame": + objects: dict[str, Object], + sensors: dict[str, Sensor], + ) -> Frame: """Generate a Frame object from a dict. Parameters @@ -140,7 +142,7 @@ def asdict(self) -> dict: return dict_repr @classmethod - def _timestamp_fromdict(cls, data_dict: dict) -> t.Optional[decimal.Decimal]: + def _timestamp_fromdict(cls, data_dict: dict) -> decimal.Decimal | None: if "frame_properties" not in data_dict or "timestamp" not in data_dict["frame_properties"]: return None @@ -148,8 +150,8 @@ def _timestamp_fromdict(cls, data_dict: dict) -> t.Optional[decimal.Decimal]: @classmethod def _sensors_fromdict( - cls, data_dict: dict, scene_sensors: t.Dict[str, Sensor] - ) -> t.Dict[str, SensorReference]: + cls, data_dict: dict, scene_sensors: dict[str, Sensor] + ) -> dict[str, SensorReference]: if "frame_properties" not in data_dict or "streams" not in data_dict["frame_properties"]: return {} @@ -163,7 +165,7 @@ def _sensors_fromdict( return sensors @classmethod - def _frame_data_fromdict(cls, data_dict: dict, sensors: t.Dict[str, Sensor]) -> t.Dict[str, Num]: + def _frame_data_fromdict(cls, data_dict: dict, sensors: dict[str, Sensor]) -> dict[str, Num]: if "frame_properties" not in data_dict or "frame_data" not in data_dict["frame_properties"]: return {} @@ -178,9 +180,9 @@ def _frame_data_fromdict(cls, data_dict: dict, sensors: t.Dict[str, Sensor]) -> def _objects_fromdict( cls, data_dict: dict, - objects: t.Dict[str, Object], - sensors: t.Dict[str, Sensor], - ) -> t.Dict[uuid.UUID, t.Type[_ObjectAnnotation]]: + objects: dict[str, Object], + sensors: dict[str, Sensor], + ) -> dict[uuid.UUID, type[_ObjectAnnotation]]: if "objects" not in data_dict: return {} @@ -203,18 +205,18 @@ def _object_annotations_fromdict( cls, data_dict: dict, object: Object, - sensors: t.Dict[str, Sensor], - ) -> t.Iterator[t.Type[_ObjectAnnotation]]: + sensors: dict[str, Sensor], + ) -> t.Iterator[type[_ObjectAnnotation]]: for ann_type, annotations_raw in data_dict.items(): for ann_raw in annotations_raw: yield annotation_classes()[ann_type].fromdict(ann_raw, sensors, object) def _annotations_asdict(self) -> dict: annotations_dict = {} - for object_id, annotations in self.object_data.items(): + for object_id, annotations_ in self.object_data.items(): annotations_dict[object_id] = {"object_data": {}} - for annotation in annotations.values(): + for annotation in annotations_.values(): if annotation.OPENLABEL_ID not in annotations_dict[object_id]["object_data"]: annotations_dict[object_id]["object_data"][annotation.OPENLABEL_ID] = [] diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 576c586..22f10f5 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass @@ -22,7 +23,7 @@ class FrameInterval: frame_end: int @classmethod - def fromdict(cls, data_dict: dict) -> "FrameInterval": + def fromdict(cls, data_dict: dict) -> FrameInterval: """Generate a FrameInterval object from a dict. Parameters @@ -37,7 +38,7 @@ def fromdict(cls, data_dict: dict) -> "FrameInterval": ) @classmethod - def from_frame_uids(cls, frame_uids: t.List[int]) -> t.List["FrameInterval"]: + def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: """Convert a list of frame uids into FrameIntervals. Parameters @@ -92,7 +93,7 @@ def __len__(self) -> int: return abs(self.frame_start - self.frame_end) + 1 @classmethod - def _slice_into_intervals(cls, sorted_frame_uids: t.List[int]) -> t.List[t.List[int]]: + def _slice_into_intervals(cls, sorted_frame_uids: list[int]) -> list[list[int]]: if len(sorted_frame_uids) == 0: return [] diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index e781dde..76e03aa 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass @@ -27,13 +28,13 @@ class IntrinsicsPinhole: """ - camera_matrix: t.Tuple[float, ...] - distortion: t.Tuple[float, ...] + camera_matrix: tuple[float, ...] + distortion: tuple[float, ...] width_px: int height_px: int @classmethod - def fromdict(cls, data_dict: dict) -> "IntrinsicsPinhole": + def fromdict(cls, data_dict: dict) -> IntrinsicsPinhole: """Generate a IntrinsicsPinhole object from a dict. Parameters diff --git a/raillabel/format/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py index 2097871..380d91f 100644 --- a/raillabel/format/intrinsics_radar.py +++ b/raillabel/format/intrinsics_radar.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -25,7 +27,7 @@ class IntrinsicsRadar: height_px: int @classmethod - def fromdict(cls, data_dict: dict) -> "IntrinsicsRadar": + def fromdict(cls, data_dict: dict) -> IntrinsicsRadar: """Generate a IntrinsicsRadar object from a dict. Parameters diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index ad54f16..ea6c322 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from importlib import metadata as importlib_metadata @@ -52,16 +53,16 @@ class Metadata: """ schema_version: str - annotator: t.Optional[str] = None - comment: t.Optional[str] = None - exporter_version: t.Optional[str] = None - file_version: t.Optional[str] = None - name: t.Optional[str] = None - subschema_version: t.Optional[str] = None - tagged_file: t.Optional[str] = None + annotator: str | None = None + comment: str | None = None + exporter_version: str | None = None + file_version: str | None = None + name: str | None = None + subschema_version: str | None = None + tagged_file: str | None = None @classmethod - def fromdict(cls, data_dict: dict, subschema_version: t.Optional[str] = None) -> "Metadata": + def fromdict(cls, data_dict: dict, subschema_version: str | None = None) -> Metadata: """Generate a Metadata object from a dict. Parameters @@ -98,7 +99,7 @@ def asdict(self) -> dict: return self._remove_empty_fields(vars(self)) @classmethod - def _collect_exporter_version(cls) -> t.Optional[str]: + def _collect_exporter_version(cls) -> str | None: try: exporter_version = importlib_metadata.version("raillabel") except importlib_metadata.PackageNotFoundError: @@ -108,7 +109,7 @@ def _collect_exporter_version(cls) -> t.Optional[str]: return exporter_version[: version_number_length - 1] @classmethod - def _set_additional_attributes(cls, metadata: "Metadata", data_dict: dict) -> "Metadata": + def _set_additional_attributes(cls, metadata: Metadata, data_dict: dict) -> Metadata: PRESET_KEYS = ["schema_version", "subschema_version", "exporter_version"] for key, value in data_dict.items(): diff --git a/raillabel/format/num.py b/raillabel/format/num.py index 537910b..d053cf8 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from .sensor import Sensor @@ -29,11 +30,11 @@ class Num: uid: str name: str - val: t.Union[int, float] + val: int | float sensor: Sensor = None @classmethod - def fromdict(cls, data_dict: dict, sensors: dict) -> "Num": + def fromdict(cls, data_dict: dict, sensors: dict) -> Num: """Generate a Num object from a dict. Parameters @@ -78,7 +79,7 @@ def asdict(self) -> dict: } @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> t.Optional[Sensor]: + def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor | None: is_coordinate_system_in_data = ( "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" ) diff --git a/raillabel/format/object.py b/raillabel/format/object.py index 702e2f6..0a37eba 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import typing as t from _collections_abc import dict_values from dataclasses import dataclass @@ -35,7 +37,7 @@ class Object: # --- Public Methods -------------- @classmethod - def fromdict(cls, data_dict: dict, object_uid: str) -> "Object": + def fromdict(cls, data_dict: dict, object_uid: str) -> Object: """Generate a Object from a dict. Parameters @@ -53,7 +55,7 @@ def fromdict(cls, data_dict: dict, object_uid: str) -> "Object": """ return Object(uid=object_uid, type=data_dict["type"], name=data_dict["name"]) - def asdict(self, frames: t.Optional[t.Dict[int, "Frame"]] = None) -> dict: + def asdict(self, frames: dict[int, Frame] | None = None) -> dict: """Export self as a dict compatible with the OpenLABEL schema. Returns @@ -82,7 +84,7 @@ def asdict(self, frames: t.Optional[t.Dict[int, "Frame"]] = None) -> dict: ), } - def frame_intervals(self, frames: t.Dict[int, "Frame"]) -> t.List[FrameInterval]: + def frame_intervals(self, frames: dict[int, Frame]) -> list[FrameInterval]: """Return frame intervals in which this object is present. Parameters @@ -102,7 +104,7 @@ def frame_intervals(self, frames: t.Dict[int, "Frame"]) -> t.List[FrameInterval] return FrameInterval.from_frame_uids(frame_uids_containing_object) - def object_data_pointers(self, frames: t.Dict[int, "Frame"]) -> t.Dict[str, ElementDataPointer]: + def object_data_pointers(self, frames: dict[int, Frame]) -> dict[str, ElementDataPointer]: """Create object data pointers used in WebLABEL visualization. Parameters @@ -131,23 +133,21 @@ def object_data_pointers(self, frames: t.Dict[int, "Frame"]) -> t.Dict[str, Elem # --- Private Methods ------------- - def _frame_intervals_asdict(self, frame_intervals: t.List[FrameInterval]) -> dict: + def _frame_intervals_asdict(self, frame_intervals: list[FrameInterval]) -> dict: return [fi.asdict() for fi in frame_intervals] def _object_data_pointers_asdict( - self, object_data_pointers: t.Dict[str, ElementDataPointer] + self, object_data_pointers: dict[str, ElementDataPointer] ) -> dict: return {pointer_id: pointer.asdict() for pointer_id, pointer in object_data_pointers.items()} - def _is_object_in_frame(self, frame: "Frame") -> bool: + def _is_object_in_frame(self, frame: Frame) -> bool: return self.uid in frame.object_data - def _filtered_annotations(self, frame: "Frame") -> dict_values: + def _filtered_annotations(self, frame: Frame) -> dict_values: return [ann for ann in frame.annotations.values() if ann.object.uid == self.uid] - def _collect_pointer_ids_per_frame( - self, frames: t.Dict[int, "Frame"] - ) -> t.Dict[int, t.Set[str]]: + def _collect_pointer_ids_per_frame(self, frames: dict[int, Frame]) -> dict[int, set[str]]: pointer_ids_per_frame = {} for frame in frames.values(): pointer_ids_per_frame[frame.uid] = set() @@ -158,8 +158,8 @@ def _collect_pointer_ids_per_frame( return pointer_ids_per_frame def _reverse_frame_pointer_ids( - self, pointer_ids_per_frame: t.Dict[int, t.Set[str]] - ) -> t.Dict[str, t.Set[int]]: + self, pointer_ids_per_frame: dict[int, set[str]] + ) -> dict[str, set[int]]: frame_uids_per_pointer_id = {} for frame_uid, pointer_ids in pointer_ids_per_frame.items(): for pointer_id in pointer_ids: @@ -171,8 +171,8 @@ def _reverse_frame_pointer_ids( return frame_uids_per_pointer_id def _convert_to_intervals( - self, frame_uids_per_pointer_id: t.Dict[str, t.Set[int]] - ) -> t.Dict[str, t.List[FrameInterval]]: + self, frame_uids_per_pointer_id: dict[str, set[int]] + ) -> dict[str, list[FrameInterval]]: frame_intervals = {} for pointer_id, frame_uids in frame_uids_per_pointer_id.items(): frame_intervals[pointer_id] = FrameInterval.from_frame_uids(list(frame_uids)) @@ -180,8 +180,8 @@ def _convert_to_intervals( return frame_intervals def _collect_attributes_per_pointer_id( - self, frames: t.Dict[int, "Frame"] - ) -> t.Dict[str, t.Dict[str, t.Any]]: + self, frames: dict[int, Frame] + ) -> dict[str, dict[str, t.Any]]: attributes_per_pointer_id = {} for frame in frames.values(): for annotation in self._filtered_annotations(frame): @@ -193,8 +193,8 @@ def _collect_attributes_per_pointer_id( return attributes_per_pointer_id def _convert_to_attribute_pointers( - self, attributes_per_pointer_id: t.Dict[str, t.Dict[str, t.Any]] - ) -> t.Dict[str, t.Dict[str, AttributeType]]: + self, attributes_per_pointer_id: dict[str, dict[str, t.Any]] + ) -> dict[str, dict[str, AttributeType]]: for attributes in attributes_per_pointer_id.values(): for attribute_name, attribute_value in attributes.items(): attributes[attribute_name] = AttributeType.from_value(type(attribute_value)) @@ -203,9 +203,9 @@ def _convert_to_attribute_pointers( def _create_object_data_pointers( self, - frame_intervals_per_pointer_id: t.Dict[str, t.List[FrameInterval]], - attribute_pointers_per_pointer_id: t.Dict[str, t.Dict[str, AttributeType]], - ) -> t.Dict[str, ElementDataPointer]: + frame_intervals_per_pointer_id: dict[str, list[FrameInterval]], + attribute_pointers_per_pointer_id: dict[str, dict[str, AttributeType]], + ) -> dict[str, ElementDataPointer]: object_data_pointers = {} for pointer_id in frame_intervals_per_pointer_id: object_data_pointers[pointer_id] = ElementDataPointer( diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 57efe30..0b9f7c0 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -21,7 +23,7 @@ class Point2d: y: float @classmethod - def fromdict(cls, data_dict: dict) -> "Point2d": + def fromdict(cls, data_dict: dict) -> Point2d: """Generate a Point2d object from a dict. Parameters diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index 2a4d5f4..ccb0182 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -24,7 +26,7 @@ class Point3d: z: float @classmethod - def fromdict(cls, data_dict: dict) -> "Point3d": + def fromdict(cls, data_dict: dict) -> Point3d: """Generate a Point3d object from a dict. Parameters diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index adc1d06..1952714 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation @@ -44,14 +45,14 @@ class Poly2d(_ObjectAnnotation): """ - points: t.List[Point2d] + points: list[Point2d] closed: bool mode: str = "MODE_POLY2D_ABSOLUTE" OPENLABEL_ID = "poly2d" @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly2d": + def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Poly2d: """Generate a Poly2d object from a dict. Parameters @@ -106,7 +107,7 @@ def asdict(self) -> dict: return dict_repr @classmethod - def _points_fromdict(cls, data_dict: dict) -> t.List[Point2d]: + def _points_fromdict(cls, data_dict: dict) -> list[Point2d]: return [ Point2d(x=data_dict["val"][i], y=data_dict["val"][i + 1]) for i in range(0, len(data_dict["val"]), 2) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index e350801..d993adc 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation @@ -37,13 +38,13 @@ class Poly3d(_ObjectAnnotation): """ - points: t.List[Point3d] + points: list[Point3d] closed: bool OPENLABEL_ID = "poly3d" @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Poly3d": + def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Poly3d: """Generate a Poly3d object from a dict. Parameters @@ -96,7 +97,7 @@ def asdict(self) -> dict: return dict_repr @classmethod - def _points_fromdict(cls, data_dict: dict) -> t.List[Point3d]: + def _points_fromdict(cls, data_dict: dict) -> list[Point3d]: return [ Point3d(x=data_dict["val"][i], y=data_dict["val"][i + 1], z=data_dict["val"][i + 2]) for i in range(0, len(data_dict["val"]), 3) diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index 4462a17..b6210ed 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -27,7 +29,7 @@ class Quaternion: w: float @classmethod - def fromdict(cls, data_dict: dict) -> "Quaternion": + def fromdict(cls, data_dict: dict) -> Quaternion: """Generate a Quaternion object from a dict. Parameters diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index b1ba070..ffdc3cd 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass, field from .frame import Frame @@ -34,19 +35,19 @@ class Scene: """ metadata: Metadata - sensors: t.Dict[str, Sensor] = field(default_factory=dict) - objects: t.Dict[str, Object] = field(default_factory=dict) - frames: t.Dict[int, Frame] = field(default_factory=dict) + sensors: dict[str, Sensor] = field(default_factory=dict) + objects: dict[str, Object] = field(default_factory=dict) + frames: dict[int, Frame] = field(default_factory=dict) @property - def frame_intervals(self) -> t.List[FrameInterval]: + def frame_intervals(self) -> list[FrameInterval]: """Return frame intervals of the present frames.""" return FrameInterval.from_frame_uids(list(self.frames.keys())) # === Public Methods ========================================================================== @classmethod - def fromdict(cls, data_dict: dict, subschema_version: t.Optional[str] = None) -> "Scene": + def fromdict(cls, data_dict: dict, subschema_version: str | None = None) -> Scene: """Generate a Scene object from a RailLABEL-dict. Parameters @@ -149,7 +150,7 @@ def _prepare_data(cls, data: dict) -> dict: @classmethod def _sensors_fromdict( cls, streams_dict: dict, coordinate_systems_dict: dict - ) -> t.Dict[str, Sensor]: + ) -> dict[str, Sensor]: sensors = {} for stream_id in streams_dict: @@ -162,13 +163,13 @@ def _sensors_fromdict( return sensors @classmethod - def _objects_fromdict(cls, object_dict: dict) -> t.Dict[str, Object]: + def _objects_fromdict(cls, object_dict: dict) -> dict[str, Object]: return {uid: Object.fromdict(object_, uid) for uid, object_ in object_dict.items()} @classmethod def _frames_fromdict( - cls, frames_dict: dict, sensors: t.Dict[str, Sensor], objects: t.Dict[str, Object] - ) -> t.Dict[int, Frame]: + cls, frames_dict: dict, sensors: dict[str, Sensor], objects: dict[str, Object] + ) -> dict[int, Frame]: frames = {} for frame_uid, frame_dict in frames_dict.items(): frames[int(frame_uid)] = Frame.fromdict(frame_uid, frame_dict, objects, sensors) @@ -177,10 +178,10 @@ def _frames_fromdict( # --- asdict() ------------------------------ - def _streams_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: + def _streams_asdict(self, sensors: dict[str, Sensor]) -> dict: return {uid: sensor.asdict()["stream"] for uid, sensor in sensors.items()} - def _coordinate_systems_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: + def _coordinate_systems_asdict(self, sensors: dict[str, Sensor]) -> dict: if len(sensors) == 0: return None @@ -192,15 +193,15 @@ def _coordinate_systems_asdict(self, sensors: t.Dict[str, Sensor]) -> dict: return coordinate_systems - def _objects_asdict(self, objects: t.Dict[str, Object], calculate_pointers: bool) -> dict: + def _objects_asdict(self, objects: dict[str, Object], calculate_pointers: bool) -> dict: if calculate_pointers: return {str(uid): object_.asdict(self.frames) for uid, object_ in objects.items()} return {str(uid): object_.asdict() for uid, object_ in objects.items()} - def _frames_asdict(self, frames: t.Dict[int, Frame]) -> dict: + def _frames_asdict(self, frames: dict[int, Frame]) -> dict: return {str(uid): frame.asdict() for uid, frame in frames.items()} - def _frame_intervals_asdict(self, frame_intervals: t.List[FrameInterval]) -> t.List[dict]: + def _frame_intervals_asdict(self, frame_intervals: list[FrameInterval]) -> list[dict]: return [fi.asdict() for fi in frame_intervals] diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 588933f..369976f 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation @@ -33,12 +34,12 @@ class Seg3d(_ObjectAnnotation): """ - point_ids: t.List[int] + point_ids: list[int] OPENLABEL_ID = "vec" @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> "Seg3d": + def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Seg3d: """Generate a Seg3d object from a dict. Parameters diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index b3b01b2..b60b8d5 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -1,7 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -import typing as t +from __future__ import annotations + from dataclasses import dataclass from enum import Enum @@ -40,14 +41,14 @@ class Sensor: """ uid: str - extrinsics: t.Optional[Transform] = None - intrinsics: t.Optional[t.Union[IntrinsicsPinhole, IntrinsicsRadar]] = None - type: t.Optional["SensorType"] = None - uri: t.Optional[str] = None - description: t.Optional[str] = None + extrinsics: Transform | None = None + intrinsics: IntrinsicsPinhole | IntrinsicsRadar | None = None + type: SensorType | None = None + uri: str | None = None + description: str | None = None @classmethod - def fromdict(cls, uid: str, cs_data_dict: dict, stream_data_dict: dict) -> "Sensor": + def fromdict(cls, uid: str, cs_data_dict: dict, stream_data_dict: dict) -> Sensor: """Generate a Sensor object from a dict. Parameters @@ -119,7 +120,7 @@ def _as_stream_dict(self) -> dict: return stream_repr @classmethod - def _extrinsics_fromdict(cls, data_dict: t.Dict) -> t.Optional[Transform]: + def _extrinsics_fromdict(cls, data_dict: dict) -> Transform | None: if "pose_wrt_parent" not in data_dict: return None @@ -139,8 +140,8 @@ def _extrinsics_fromdict(cls, data_dict: t.Dict) -> t.Optional[Transform]: @classmethod def _intrinsics_fromdict( - cls, data_dict: t.Dict, sensor_type: t.Optional["SensorType"] - ) -> t.Optional[IntrinsicsPinhole]: + cls, data_dict: dict, sensor_type: SensorType | None + ) -> IntrinsicsPinhole | None: if "stream_properties" not in data_dict: return None @@ -158,7 +159,7 @@ def _intrinsics_fromdict( return None @classmethod - def _type_fromdict(cls, data_dict: t.Dict) -> t.Optional["SensorType"]: + def _type_fromdict(cls, data_dict: dict) -> SensorType | None: if "type" not in data_dict: return None diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index 02093ab..0e043f1 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -1,8 +1,9 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import decimal -import typing as t from dataclasses import dataclass from .sensor import Sensor @@ -27,10 +28,10 @@ class SensorReference: sensor: Sensor timestamp: decimal.Decimal - uri: t.Optional[str] = None + uri: str | None = None @classmethod - def fromdict(cls, data_dict: dict, sensor: Sensor) -> "SensorReference": + def fromdict(cls, data_dict: dict, sensor: Sensor) -> SensorReference: """Generate a SensorReference object from a dict. Parameters diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index bcd3ea4..4b78b0e 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -21,7 +23,7 @@ class Size2d: y: float @classmethod - def fromdict(cls, data_dict: dict) -> "Size2d": + def fromdict(cls, data_dict: dict) -> Size2d: """Generate a Size2d object from a dict. Parameters diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index e9016f3..04e8cbb 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass @@ -24,7 +26,7 @@ class Size3d: z: float @classmethod - def fromdict(cls, data_dict: dict) -> "Size3d": + def fromdict(cls, data_dict: dict) -> Size3d: """Generate a Size3d object from a dict. Parameters diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index b7c4f13..378fd7c 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from dataclasses import dataclass from .point3d import Point3d @@ -24,7 +26,7 @@ class Transform: quat: Quaternion @classmethod - def fromdict(cls, data_dict: dict) -> "Transform": + def fromdict(cls, data_dict: dict) -> Transform: """Generate a Transform object from a dict. Parameters diff --git a/tests/conftest.py b/tests/conftest.py index e5045a0..1c8b505 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ def compile_uncommented_test_file(): @pytest.fixture -def json_paths(request) -> t.Dict[str, Path]: +def json_paths(request) -> dict[str, Path]: json_paths = _fetch_json_paths_from_cache(request) if json_paths is None: @@ -41,11 +41,11 @@ def json_paths(request) -> t.Dict[str, Path]: return json_paths -def _fetch_json_paths_from_cache(request) -> t.Optional[t.Dict[str, Path]]: +def _fetch_json_paths_from_cache(request) -> dict[str, Path] | None: return request.config.cache.get("json_paths", None) -def _collect_json_paths() -> t.List[Path]: +def _collect_json_paths() -> list[Path]: json_paths = [] for dir in json_data_directories: @@ -72,7 +72,7 @@ def _get_file_identifier(path: Path) -> str: @pytest.fixture -def json_data(request) -> t.Dict[str, dict]: +def json_data(request) -> dict[str, dict]: json_data = _fetch_json_data_from_cache(request) if json_data is None: @@ -81,7 +81,7 @@ def json_data(request) -> t.Dict[str, dict]: return json_data -def _fetch_json_data_from_cache(request) -> t.Optional[t.Dict[str, Path]]: +def _fetch_json_data_from_cache(request) -> dict[str, Path] | None: return request.config.cache.get("json_data", None) diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index ad6b466..6c95b29 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -39,7 +39,7 @@ def objects_dict(object_person_dict, object_train_dict) -> dict: @pytest.fixture -def objects(object_person, object_train) -> t.Dict[str, Object]: +def objects(object_person, object_train) -> dict[str, Object]: return { object_person.uid: object_person, object_train.uid: object_train, @@ -401,7 +401,7 @@ def build_annotation(name: str, object: Object, attributes: dict = {}) -> t.Unio raise ValueError() -def build_frame(uid: int, raw_object_data: t.Dict[Object, t.List[t.Union[Bbox, Cuboid]]]) -> Frame: +def build_frame(uid: int, raw_object_data: dict[Object, list[t.Union[Bbox, Cuboid]]]) -> Frame: annotations = {} for object, object_data in raw_object_data.items(): for annotation in object_data: diff --git a/tests/test_raillabel/format/test_sensor.py b/tests/test_raillabel/format/test_sensor.py index d3a1bd2..941624b 100644 --- a/tests/test_raillabel/format/test_sensor.py +++ b/tests/test_raillabel/format/test_sensor.py @@ -16,7 +16,7 @@ @pytest.fixture -def sensors(sensor_lidar, sensor_camera, sensor_radar) -> t.Dict[str, Sensor]: +def sensors(sensor_lidar, sensor_camera, sensor_radar) -> dict[str, Sensor]: return { sensor_lidar.uid: sensor_lidar, sensor_camera.uid: sensor_camera, From ae1b4305ff1e7855d41e9a7a7602197d0840565e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:44:00 +0100 Subject: [PATCH 042/190] lint: enable ruff TID252 rule --- pyproject.toml | 1 - raillabel/load/load.py | 2 +- raillabel/save/save.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fb3f8dc..548170e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,6 @@ ignore = [ "SLF001", "ISC003", "N806", - "TID252", ] [tool.mypy] diff --git a/raillabel/load/load.py b/raillabel/load/load.py index e0c5ce3..7f7ad9b 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -3,7 +3,7 @@ import json -from ..format import Scene +from raillabel.format import Scene def load(path: str) -> Scene: diff --git a/raillabel/save/save.py b/raillabel/save/save.py index 14d2901..db26583 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from ..format import Scene +from raillabel.format import Scene def save(scene: Scene, path: str, prettify_json: bool = False) -> None: From f22c709031fc7ff594e115063e4a934b260013b7 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:44:44 +0100 Subject: [PATCH 043/190] lint: enable ruff N806 rule --- pyproject.toml | 1 - raillabel/format/metadata.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 548170e..980ae9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,6 @@ ignore = [ "E721", "SLF001", "ISC003", - "N806", ] [tool.mypy] diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index ea6c322..b863b97 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -110,10 +110,10 @@ def _collect_exporter_version(cls) -> str | None: @classmethod def _set_additional_attributes(cls, metadata: Metadata, data_dict: dict) -> Metadata: - PRESET_KEYS = ["schema_version", "subschema_version", "exporter_version"] + preset_keys = ["schema_version", "subschema_version", "exporter_version"] for key, value in data_dict.items(): - if key in PRESET_KEYS: + if key in preset_keys: continue setattr(metadata, key, value) From d8a87f03eca8f326a27f5699aa2a867338a76aba Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:47:50 +0100 Subject: [PATCH 044/190] lint: enable ruff ISC003 rule --- pyproject.toml | 1 - raillabel/format/_attribute_type.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 980ae9f..e1d28a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,6 @@ ignore = [ "C901", "E721", "SLF001", - "ISC003", ] [tool.mypy] diff --git a/raillabel/format/_attribute_type.py b/raillabel/format/_attribute_type.py index bfae6f2..550189d 100644 --- a/raillabel/format/_attribute_type.py +++ b/raillabel/format/_attribute_type.py @@ -30,7 +30,7 @@ def from_value(cls, attribute_value_class: type) -> AttributeType: Raises ------ - ValueError + UnsupportedAttributeTypeError if attribute value class does not correspond to an Attribute Type. """ @@ -46,7 +46,12 @@ def from_value(cls, attribute_value_class: type) -> AttributeType: if attribute_value_class in [list, tuple]: return AttributeType.VEC - raise ValueError( + raise UnsupportedAttributeTypeError(attribute_value_class) + + +class UnsupportedAttributeTypeError(ValueError): + def __init__(self, attribute_value_class: type) -> None: + super().__init__( f"Type {attribute_value_class} does not correspond to a valid RailLabel attribute " - + "type. Supported types are str, float, int, bool, list, tuple." + "type. Supported types are str, float, int, bool, list, tuple." ) From a9e50f2742f84d8475fd2f544d90dc9b74d3c676 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:48:06 +0100 Subject: [PATCH 045/190] lint: enable ruff SLF001 rule --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1d28a7..c8401d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ ignore = [ # Will be removed gradually "C901", "E721", - "SLF001", ] [tool.mypy] From 62cecdeaf77f3bd07383701d86bc2741285d9868 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:48:34 +0100 Subject: [PATCH 046/190] lint: enable ruff E721 rule --- pyproject.toml | 1 - raillabel/format/_attribute_type.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c8401d6..5ea94ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,6 @@ ignore = [ # Will be removed gradually "C901", - "E721", ] [tool.mypy] diff --git a/raillabel/format/_attribute_type.py b/raillabel/format/_attribute_type.py index 550189d..526c5e2 100644 --- a/raillabel/format/_attribute_type.py +++ b/raillabel/format/_attribute_type.py @@ -34,13 +34,13 @@ def from_value(cls, attribute_value_class: type) -> AttributeType: if attribute value class does not correspond to an Attribute Type. """ - if attribute_value_class == str: + if attribute_value_class is str: return AttributeType.TEXT if attribute_value_class in [float, int]: return AttributeType.NUM - if attribute_value_class == bool: + if attribute_value_class is bool: return AttributeType.BOOLEAN if attribute_value_class in [list, tuple]: From 207c621b675e60bb76acd9777b429083268eeb78 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 19:49:17 +0100 Subject: [PATCH 047/190] lint: enable ruff C901 rule --- pyproject.toml | 57 ++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ea94ea..673091b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,17 +16,17 @@ license = { text = "Apache-2.0" } authors = [{ name = "DB InfraGO AG" }] keywords = [] classifiers = [ - "Development Status :: 1 - Planning", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", + "Development Status :: 1 - Planning", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = ["jsonschema>=4.4.0", "fastjsonschema>=2.16.2"] @@ -46,33 +46,30 @@ line-length = 101 exclude = ["tests/*", "docs/*"] select = ["ALL"] ignore = [ - "A002", # objects from OpenLABEL conflict with a Python builtin, but staying consistent with the domain is more important + "A002", # objects from OpenLABEL conflict with a Python builtin, but staying consistent with the domain is more important - "COM812", # conflicts with ruff formatter + "COM812", # conflicts with ruff formatter - "D100", # imo no docstrings are necessary in public modules - "D107", # __init__ docstrings are not necessary - "D203", # incompatible with D211 - "D213", # incompatible with D212 - "D417", # kwargs can not be typed for the filter function + "D100", # imo no docstrings are necessary in public modules + "D107", # __init__ docstrings are not necessary + "D203", # incompatible with D211 + "D213", # incompatible with D212 + "D417", # kwargs can not be typed for the filter function - "FBT001", # flags in functions are not bad practice - "FBT002", # flags in functions are not bad practice + "FBT001", # flags in functions are not bad practice + "FBT002", # flags in functions are not bad practice - "INP001", # load and save are not meant as packages and are only folders for consistency + "INP001", # load and save are not meant as packages and are only folders for consistency - "ISC001", # conflicts with ruff formatter + "ISC001", # conflicts with ruff formatter - "N802", # does not allow constant abstractproperties + "N802", # does not allow constant abstractproperties - "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects - "TCH002", # same as TCH001 - "TCH003", # same as TCH001 + "TCH001", # adds hard to understand compexity without providing a benefit for smaller projects + "TCH002", # same as TCH001 + "TCH003", # same as TCH001 - "SIM103", # less readable in some cases imo - - # Will be removed gradually - "C901", + "SIM103", # less readable in some cases imo ] [tool.mypy] From 3ecfd0fbefc6f25a2753acba3a6b009a4399182f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:33:49 +0100 Subject: [PATCH 048/190] lint: add mypy type checking --- .pre-commit-config.yaml | 31 +++++++++++++++++--------- pyproject.toml | 9 ++++---- raillabel/format/_object_annotation.py | 22 +++++++----------- raillabel/format/frame.py | 15 ++++++------- raillabel/format/num.py | 11 ++------- raillabel/format/object.py | 21 ++++++++--------- raillabel/format/point2d.py | 16 ++----------- raillabel/format/point3d.py | 16 ++----------- raillabel/format/quaternion.py | 16 ++----------- raillabel/format/scene.py | 7 ++++-- raillabel/format/sensor.py | 11 ++++----- raillabel/format/sensor_reference.py | 7 ++++-- raillabel/format/size2d.py | 16 ++----------- raillabel/format/size3d.py | 16 ++----------- raillabel/format/transform.py | 2 +- raillabel/load/load.py | 7 ++++-- raillabel/save/save.py | 6 +++-- 17 files changed, 88 insertions(+), 141 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2d8c20..5409439 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,26 @@ repos: name: Run Linter args: [ --fix ] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.12.1 + hooks: + - id: mypy + name: Run Type Checker + types_or: [python, pyi, toml, yaml] + pass_filenames: false + args: [raillabel] + + - repo: local + hooks: + - id: pytest + name: Run Unit Tests + stages: [commit] + types: [python] + entry: ./.venv/bin/pytest + language: system + pass_filenames: false + always_run: true + - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: @@ -88,14 +108,3 @@ repos: rev: v2.6.7 hooks: - id: conventional-commits - - - repo: local - hooks: - - id: pytest - name: Run Unit Tests - stages: [commit] - types: [python] - entry: ./.venv/bin/pytest - language: system - pass_filenames: false - always_run: true diff --git a/pyproject.toml b/pyproject.toml index 673091b..0cda5de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,9 @@ ignore = [ "TCH003", # same as TCH001 "SIM103", # less readable in some cases imo + + # Should be removed in the future + "PGH003", ] [tool.mypy] @@ -79,11 +82,7 @@ show_error_codes = true warn_redundant_casts = true warn_unreachable = true python_version = "3.12" - -[[tool.mypy.overrides]] -module = ["tests.*"] -allow_incomplete_defs = true -allow_untyped_defs = true +disable_error_code = ["override"] [tool.pytest.ini_options] addopts = """ diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 2c0a877..4487cf2 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -9,6 +9,7 @@ from inspect import isclass from pathlib import Path from pkgutil import iter_modules +from typing import Any from ._attribute_type import AttributeType from .object import Object @@ -28,7 +29,7 @@ def name(self) -> str: @property @abstractproperty - def OPENLABEL_ID(self) -> list[str]: + def OPENLABEL_ID(self) -> list[str] | str: raise NotImplementedError # === Public Methods ===================================================== @@ -56,9 +57,9 @@ def _annotation_required_fields_asdict(self) -> dict: "name": str(self.name), } - def _annotation_optional_fields_asdict(self) -> dict: + def _annotation_optional_fields_asdict(self) -> dict[str, Any]: """Return the optional fields from the parent class to dict.""" - dict_repr = {} + dict_repr: dict[str, Any] = {} if self.sensor is not None: dict_repr["coordinate_system"] = str(self.sensor.uid) @@ -68,8 +69,8 @@ def _annotation_optional_fields_asdict(self) -> dict: return dict_repr - def _attributes_asdict(self, attributes: dict) -> dict: - attributes_dict = {} + def _attributes_asdict(self, attributes: dict[str, Any]) -> dict[str, Any]: + attributes_dict: dict[str, Any] = {} for attr_name, attr_value in attributes.items(): attr_type = AttributeType.from_value(type(attr_value)).value @@ -82,14 +83,7 @@ def _attributes_asdict(self, attributes: dict) -> dict: return attributes_dict @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor | None: - is_coordinate_system_in_data = ( - "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" - ) - - if not is_coordinate_system_in_data: - return None - + def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor: return sensors[data_dict["coordinate_system"]] @classmethod @@ -120,4 +114,4 @@ def annotation_classes() -> dict[str, type[_ObjectAnnotation]]: ): out[attribute.OPENLABEL_ID] = attribute - return out + return out # type: ignore diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index abe32b4..bb4a618 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -5,7 +5,6 @@ import decimal import typing as t -import uuid from dataclasses import dataclass, field from ._object_annotation import _ObjectAnnotation, annotation_classes @@ -60,7 +59,7 @@ def object_data(self) -> dict[str, dict[str, type[_ObjectAnnotation]]]: contained in the object. """ - object_data = {} + object_data: dict[str, dict[str, type[_ObjectAnnotation]]] = {} for ann_id, annotation in self.annotations.items(): if annotation.object.uid not in object_data: object_data[annotation.object.uid] = {} @@ -104,7 +103,7 @@ def fromdict( annotations=cls._objects_fromdict(data_dict, objects, sensors), ) - def asdict(self) -> dict: + def asdict(self) -> dict[str, t.Any]: """Export self as a dict compatible with the OpenLABEL schema. Returns @@ -118,7 +117,7 @@ def asdict(self) -> dict: if an attribute can not be converted to the type required by the OpenLabel schema. """ - dict_repr = {} + dict_repr: dict[str, t.Any] = {} if self.timestamp is not None or self.sensors != {} or self.frame_data != {}: dict_repr["frame_properties"] = {} @@ -182,7 +181,7 @@ def _objects_fromdict( data_dict: dict, objects: dict[str, Object], sensors: dict[str, Sensor], - ) -> dict[uuid.UUID, type[_ObjectAnnotation]]: + ) -> dict[str, type[_ObjectAnnotation]]: if "objects" not in data_dict: return {} @@ -211,8 +210,8 @@ def _object_annotations_fromdict( for ann_raw in annotations_raw: yield annotation_classes()[ann_type].fromdict(ann_raw, sensors, object) - def _annotations_asdict(self) -> dict: - annotations_dict = {} + def _annotations_asdict(self) -> dict[str, t.Any]: + annotations_dict: dict[str, t.Any] = {} for object_id, annotations_ in self.object_data.items(): annotations_dict[object_id] = {"object_data": {}} @@ -221,7 +220,7 @@ def _annotations_asdict(self) -> dict: annotations_dict[object_id]["object_data"][annotation.OPENLABEL_ID] = [] annotations_dict[object_id]["object_data"][annotation.OPENLABEL_ID].append( - annotation.asdict() + annotation.asdict() # type: ignore ) return annotations_dict diff --git a/raillabel/format/num.py b/raillabel/format/num.py index d053cf8..94c538f 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -31,7 +31,7 @@ class Num: uid: str name: str val: int | float - sensor: Sensor = None + sensor: Sensor @classmethod def fromdict(cls, data_dict: dict, sensors: dict) -> Num: @@ -79,12 +79,5 @@ def asdict(self) -> dict: } @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor | None: - is_coordinate_system_in_data = ( - "coordinate_system" in data_dict and data_dict["coordinate_system"] != "" - ) - - if not is_coordinate_system_in_data: - return None - + def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor: return sensors[data_dict["coordinate_system"]] diff --git a/raillabel/format/object.py b/raillabel/format/object.py index 0a37eba..06dfa13 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -4,7 +4,6 @@ from __future__ import annotations import typing as t -from _collections_abc import dict_values from dataclasses import dataclass from .element_data_pointer import AttributeType, ElementDataPointer @@ -133,7 +132,9 @@ def object_data_pointers(self, frames: dict[int, Frame]) -> dict[str, ElementDat # --- Private Methods ------------- - def _frame_intervals_asdict(self, frame_intervals: list[FrameInterval]) -> dict: + def _frame_intervals_asdict( + self, frame_intervals: list[FrameInterval] + ) -> list[dict[str, t.Any]]: return [fi.asdict() for fi in frame_intervals] def _object_data_pointers_asdict( @@ -144,23 +145,23 @@ def _object_data_pointers_asdict( def _is_object_in_frame(self, frame: Frame) -> bool: return self.uid in frame.object_data - def _filtered_annotations(self, frame: Frame) -> dict_values: + def _filtered_annotations(self, frame: Frame) -> list[t.Any]: return [ann for ann in frame.annotations.values() if ann.object.uid == self.uid] def _collect_pointer_ids_per_frame(self, frames: dict[int, Frame]) -> dict[int, set[str]]: - pointer_ids_per_frame = {} + pointer_ids_per_frame: dict[int, set[str]] = {} for frame in frames.values(): pointer_ids_per_frame[frame.uid] = set() for annotation in self._filtered_annotations(frame): - pointer_ids_per_frame[frame.uid].add(annotation.name) + pointer_ids_per_frame[frame.uid].add(annotation.name) # type: ignore return pointer_ids_per_frame def _reverse_frame_pointer_ids( self, pointer_ids_per_frame: dict[int, set[str]] ) -> dict[str, set[int]]: - frame_uids_per_pointer_id = {} + frame_uids_per_pointer_id: dict[str, set[int]] = {} for frame_uid, pointer_ids in pointer_ids_per_frame.items(): for pointer_id in pointer_ids: if pointer_id not in frame_uids_per_pointer_id: @@ -182,13 +183,13 @@ def _convert_to_intervals( def _collect_attributes_per_pointer_id( self, frames: dict[int, Frame] ) -> dict[str, dict[str, t.Any]]: - attributes_per_pointer_id = {} + attributes_per_pointer_id: dict[str, dict[str, t.Any]] = {} for frame in frames.values(): for annotation in self._filtered_annotations(frame): - if annotation.name not in attributes_per_pointer_id: - attributes_per_pointer_id[annotation.name] = {} + if annotation.name not in attributes_per_pointer_id: # type: ignore + attributes_per_pointer_id[annotation.name] = {} # type: ignore - attributes_per_pointer_id[annotation.name].update(annotation.attributes) + attributes_per_pointer_id[annotation.name].update(annotation.attributes) # type: ignore return attributes_per_pointer_id diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 0b9f7c0..edeb043 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -37,18 +37,6 @@ def fromdict(cls, data_dict: dict) -> Point2d: y=data_dict[1], ) - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ + def asdict(self) -> list[float]: + """Export self as a dict compatible with the OpenLABEL schema.""" return [float(self.x), float(self.y)] diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index ccb0182..e9afcf8 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -41,18 +41,6 @@ def fromdict(cls, data_dict: dict) -> Point3d: z=data_dict[2], ) - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ + def asdict(self) -> list[float]: + """Export self as a dict compatible with the OpenLABEL schema.""" return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index b6210ed..ecff8d0 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -45,18 +45,6 @@ def fromdict(cls, data_dict: dict) -> Quaternion: w=data_dict[3], ) - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ + def asdict(self) -> list[float]: + """Export self as a dict compatible with the OpenLABEL schema.""" return [float(self.x), float(self.y), float(self.z), float(self.w)] diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index ffdc3cd..170f5ea 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -4,6 +4,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from typing import Any from .frame import Frame from .frame_interval import FrameInterval @@ -181,11 +182,13 @@ def _frames_fromdict( def _streams_asdict(self, sensors: dict[str, Sensor]) -> dict: return {uid: sensor.asdict()["stream"] for uid, sensor in sensors.items()} - def _coordinate_systems_asdict(self, sensors: dict[str, Sensor]) -> dict: + def _coordinate_systems_asdict(self, sensors: dict[str, Sensor]) -> dict[str, Any] | None: if len(sensors) == 0: return None - coordinate_systems = {"base": {"type": "local", "parent": "", "children": []}} + coordinate_systems: dict[str, Any] = { + "base": {"type": "local", "parent": "", "children": []} + } for uid, sensor in sensors.items(): coordinate_systems[uid] = sensor.asdict()["coordinate_system"] diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index b60b8d5..9f5c6e4 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from enum import Enum +from typing import Any from .intrinsics_pinhole import IntrinsicsPinhole from .intrinsics_radar import IntrinsicsRadar @@ -91,16 +92,16 @@ def asdict(self) -> dict: "stream": self._as_stream_dict(), } - def _as_coordinate_system_dict(self) -> dict: - coordinate_system_repr = {"type": "sensor", "parent": "base"} + def _as_coordinate_system_dict(self) -> dict[str, Any]: + coordinate_system_repr: dict[str, Any] = {"type": "sensor", "parent": "base"} if self.extrinsics is not None: coordinate_system_repr["pose_wrt_parent"] = self.extrinsics.asdict() return coordinate_system_repr - def _as_stream_dict(self) -> dict: - stream_repr = {} + def _as_stream_dict(self) -> dict[str, Any]: + stream_repr: dict[str, Any] = {} if self.type is not None: stream_repr["type"] = str(self.type.value) @@ -141,7 +142,7 @@ def _extrinsics_fromdict(cls, data_dict: dict) -> Transform | None: @classmethod def _intrinsics_fromdict( cls, data_dict: dict, sensor_type: SensorType | None - ) -> IntrinsicsPinhole | None: + ) -> IntrinsicsPinhole | IntrinsicsRadar | None: if "stream_properties" not in data_dict: return None diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index 0e043f1..fa4d1ca 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -5,6 +5,7 @@ import decimal from dataclasses import dataclass +from typing import Any from .sensor import Sensor @@ -53,7 +54,7 @@ def fromdict(cls, data_dict: dict, sensor: Sensor) -> SensorReference: uri=data_dict.get("uri"), ) - def asdict(self) -> dict: + def asdict(self) -> dict[str, Any]: """Export self as a dict compatible with the OpenLABEL schema. Returns @@ -67,7 +68,9 @@ def asdict(self) -> dict: if an attribute can not be converted to the type required by the OpenLabel schema. """ - dict_repr = {"stream_properties": {"sync": {"timestamp": str(self.timestamp)}}} + dict_repr: dict[str, Any] = { + "stream_properties": {"sync": {"timestamp": str(self.timestamp)}} + } if self.uri is not None: dict_repr["uri"] = self.uri diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index 4b78b0e..dab987c 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -37,18 +37,6 @@ def fromdict(cls, data_dict: dict) -> Size2d: y=data_dict[1], ) - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ + def asdict(self) -> list[float]: + """Export self as a dict compatible with the OpenLABEL schema.""" return [float(self.x), float(self.y)] diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index 04e8cbb..f98a35b 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -41,18 +41,6 @@ def fromdict(cls, data_dict: dict) -> Size3d: z=data_dict[2], ) - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ + def asdict(self) -> list[float]: + """Export self as a dict compatible with the OpenLABEL schema.""" return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 378fd7c..46e2282 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -40,7 +40,7 @@ def fromdict(cls, data_dict: dict) -> Transform: quat=Quaternion.fromdict(data_dict["quaternion"]), ) - def asdict(self) -> dict: + def asdict(self) -> dict[str, list[float]]: """Export self as a dict compatible with the OpenLABEL schema. Returns diff --git a/raillabel/load/load.py b/raillabel/load/load.py index 7f7ad9b..f45105d 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -1,12 +1,15 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import json +from pathlib import Path from raillabel.format import Scene -def load(path: str) -> Scene: +def load(path: Path | str) -> Scene: """Load an annotation file of any supported type. Parameters @@ -20,7 +23,7 @@ def load(path: str) -> Scene: Scene with the loaded data. """ - with path.open() as scene_file: + with Path(path).open() as scene_file: raw_scene = json.load(scene_file) return Scene.fromdict(raw_scene) diff --git a/raillabel/save/save.py b/raillabel/save/save.py index db26583..3adfef5 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -1,13 +1,15 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import json from pathlib import Path from raillabel.format import Scene -def save(scene: Scene, path: str, prettify_json: bool = False) -> None: +def save(scene: Scene, path: Path | str, prettify_json: bool = False) -> None: """Save a raillabel.Scene in a JSON file. Parameters @@ -27,7 +29,7 @@ def save(scene: Scene, path: str, prettify_json: bool = False) -> None: data = scene.asdict() - with path.open("w") as save_file: + with Path(path).open("w") as save_file: if prettify_json: json.dump(data, save_file, indent=4) else: From 021fa7a0a3aa3a123a588f52e3dc5ce38ec812d5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:40:57 +0100 Subject: [PATCH 049/190] chore: set up python venv in lint pipeline --- .github/workflows/lint.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9654df4..73f4505 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,6 +15,10 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.12" + - name: Set up virtual environment + run: |- + python -m venv .venv + source .venv/bin/activate - name: Upgrade pip run: |- python -m pip install -U pip From 38114c03ba4462b183d00bfb236c54c6a804e3aa Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:41:52 +0100 Subject: [PATCH 050/190] test: fix type annotations for python 3.8 and 3.9 --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 1c8b505..f9503ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import glob import json import sys From b9becfc9dc63597cd89117aa22df6a6025cd17aa Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:45:57 +0100 Subject: [PATCH 051/190] test: add python 3.8 type annotation support --- tests/test_raillabel/format/test_attributes.py | 2 ++ tests/test_raillabel/format/test_bbox.py | 2 ++ tests/test_raillabel/format/test_cuboid.py | 2 ++ tests/test_raillabel/format/test_element_data_pointer.py | 2 ++ tests/test_raillabel/format/test_frame.py | 2 ++ tests/test_raillabel/format/test_frame_interval.py | 2 ++ tests/test_raillabel/format/test_intrinsics_pinhole.py | 2 ++ tests/test_raillabel/format/test_intrinsics_radar.py | 2 ++ tests/test_raillabel/format/test_metadata.py | 2 ++ tests/test_raillabel/format/test_num.py | 2 ++ tests/test_raillabel/format/test_object.py | 2 ++ tests/test_raillabel/format/test_object_annotation.py | 2 ++ tests/test_raillabel/format/test_object_data.py | 2 ++ tests/test_raillabel/format/test_point2d.py | 2 ++ tests/test_raillabel/format/test_point3d.py | 2 ++ tests/test_raillabel/format/test_poly2d.py | 2 ++ tests/test_raillabel/format/test_poly3d.py | 2 ++ tests/test_raillabel/format/test_quaternion.py | 2 ++ tests/test_raillabel/format/test_scene.py | 2 ++ tests/test_raillabel/format/test_seg3d.py | 2 ++ tests/test_raillabel/format/test_sensor.py | 2 ++ tests/test_raillabel/format/test_sensor_reference.py | 2 ++ tests/test_raillabel/format/test_size2d.py | 2 ++ tests/test_raillabel/format/test_size3d.py | 2 ++ tests/test_raillabel/format/test_transform.py | 2 ++ 25 files changed, 50 insertions(+) diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/test_raillabel/format/test_attributes.py index 1fb7699..b72fac8 100644 --- a/tests/test_raillabel/format/test_attributes.py +++ b/tests/test_raillabel/format/test_attributes.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index 27d0e57..040df09 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index 81e7fbc..96af51d 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_element_data_pointer.py b/tests/test_raillabel/format/test_element_data_pointer.py index f673651..1611ea6 100644 --- a/tests/test_raillabel/format/test_element_data_pointer.py +++ b/tests/test_raillabel/format/test_element_data_pointer.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index de0adf6..904d69b 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from decimal import Decimal diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index 6de42a1..321fd04 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py index fa860f1..2f07c4f 100644 --- a/tests/test_raillabel/format/test_intrinsics_pinhole.py +++ b/tests/test_raillabel/format/test_intrinsics_pinhole.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py index 9a53fba..a264192 100644 --- a/tests/test_raillabel/format/test_intrinsics_radar.py +++ b/tests/test_raillabel/format/test_intrinsics_radar.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py index 2b6e289..5634297 100644 --- a/tests/test_raillabel/format/test_metadata.py +++ b/tests/test_raillabel/format/test_metadata.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py index cc7e164..7c3d0c1 100644 --- a/tests/test_raillabel/format/test_num.py +++ b/tests/test_raillabel/format/test_num.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 6c95b29..a3657a3 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import random import sys diff --git a/tests/test_raillabel/format/test_object_annotation.py b/tests/test_raillabel/format/test_object_annotation.py index 7af241e..143ca47 100644 --- a/tests/test_raillabel/format/test_object_annotation.py +++ b/tests/test_raillabel/format/test_object_annotation.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_object_data.py b/tests/test_raillabel/format/test_object_data.py index be79441..244f930 100644 --- a/tests/test_raillabel/format/test_object_data.py +++ b/tests/test_raillabel/format/test_object_data.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index e156e65..54c2ffd 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index 426a6b2..c3d6225 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index efc3a8d..24baa98 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index a8da9d2..87d8dec 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py index 2f545cf..8291881 100644 --- a/tests/test_raillabel/format/test_quaternion.py +++ b/tests/test_raillabel/format/test_quaternion.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index d0705a6..e342dbe 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index c9db4c3..1ff15e3 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_sensor.py b/tests/test_raillabel/format/test_sensor.py index 941624b..59b2259 100644 --- a/tests/test_raillabel/format/test_sensor.py +++ b/tests/test_raillabel/format/test_sensor.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys import typing as t diff --git a/tests/test_raillabel/format/test_sensor_reference.py b/tests/test_raillabel/format/test_sensor_reference.py index 5ed4920..886b41c 100644 --- a/tests/test_raillabel/format/test_sensor_reference.py +++ b/tests/test_raillabel/format/test_sensor_reference.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from decimal import Decimal diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/test_raillabel/format/test_size2d.py index e79bc6e..04f1f76 100644 --- a/tests/test_raillabel/format/test_size2d.py +++ b/tests/test_raillabel/format/test_size2d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/test_raillabel/format/test_size3d.py index 2c3398a..8626da3 100644 --- a/tests/test_raillabel/format/test_size3d.py +++ b/tests/test_raillabel/format/test_size3d.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index c34e2b9..2bf2302 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -1,6 +1,8 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import os import sys from pathlib import Path From faea76b5936a8d3d17d8e984d80245e7f28bed40 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:47:32 +0100 Subject: [PATCH 052/190] chore: install pytest for pipelines --- .github/workflows/lint.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73f4505..019a00b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,10 +21,13 @@ jobs: source .venv/bin/activate - name: Upgrade pip run: |- - python -m pip install -U pip + pip install -U pip - name: Install pre-commit run: |- - python -m pip install pre-commit types-docutils + pip install pre-commit types-docutils + - name: Install pytest + run: |- + pip install pytest - name: Run Pre-Commit run: |- pre-commit run --all-files From 18f02f952e30cbbc0c36cfc3e8fa7fc9492adb96 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:51:56 +0100 Subject: [PATCH 053/190] chore: identify pipeline issues with pytest --- .github/workflows/lint.yml | 7 ++++--- .pre-commit-config.yaml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 019a00b..e316379 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,12 +22,13 @@ jobs: - name: Upgrade pip run: |- pip install -U pip + - name: Install packages + run: |- + pip install '.[test]' + ls .venv/bin - name: Install pre-commit run: |- pip install pre-commit types-docutils - - name: Install pytest - run: |- - pip install pytest - name: Run Pre-Commit run: |- pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5409439..0827eee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: name: Run Unit Tests stages: [commit] types: [python] - entry: ./.venv/bin/pytest + entry: .venv/bin/pytest language: system pass_filenames: false always_run: true From b69c5d1d3d4e5c9301a3340a6ec6603d8546ea2a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 20:56:45 +0100 Subject: [PATCH 054/190] chore: invoke pre-commit pytest via python --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0827eee..caf8bd3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: name: Run Unit Tests stages: [commit] types: [python] - entry: .venv/bin/pytest + entry: .venv/bin/python -m pytest language: system pass_filenames: false always_run: true From afda380ebf655c77730062f7393696ea5b7839aa Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 29 Oct 2024 21:02:19 +0100 Subject: [PATCH 055/190] chore: remove pytest from pre-commit --- .pre-commit-config.yaml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index caf8bd3..d22a66e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,17 +44,6 @@ repos: pass_filenames: false args: [raillabel] - - repo: local - hooks: - - id: pytest - name: Run Unit Tests - stages: [commit] - types: [python] - entry: .venv/bin/python -m pytest - language: system - pass_filenames: false - always_run: true - - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: From 35cad802d445ccbdba32c06a5832be910b080dfc Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 09:42:37 +0100 Subject: [PATCH 056/190] feat: add JSONTransform --- .pre-commit-config.yaml | 2 ++ pyproject.toml | 4 +++- raillabel/json_format/__init__.py | 3 +++ raillabel/json_format/transform_data.py | 13 +++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 raillabel/json_format/__init__.py create mode 100644 raillabel/json_format/transform_data.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d22a66e..1d07227 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,6 +43,8 @@ repos: types_or: [python, pyi, toml, yaml] pass_filenames: false args: [raillabel] + additional_dependencies: + - "pydantic<3.0.0" - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 diff --git a/pyproject.toml b/pyproject.toml index 0cda5de..7bda5fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] -dependencies = ["jsonschema>=4.4.0", "fastjsonschema>=2.16.2"] +dependencies = ["jsonschema>=4.4.0", "fastjsonschema>=2.16.2", "pydantic<3.0.0"] [project.urls] Homepage = "https://github.com/DSD-DBS/raillabel" @@ -83,6 +83,8 @@ warn_redundant_casts = true warn_unreachable = true python_version = "3.12" disable_error_code = ["override"] +plugins = "pydantic.mypy" +ignore_missing_imports = true [tool.pytest.ini_options] addopts = """ diff --git a/raillabel/json_format/__init__.py b/raillabel/json_format/__init__.py new file mode 100644 index 0000000..fc6aa44 --- /dev/null +++ b/raillabel/json_format/__init__.py @@ -0,0 +1,3 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +"""Package for classes representing the direct RailLabel JSON objects.""" diff --git a/raillabel/json_format/transform_data.py b/raillabel/json_format/transform_data.py new file mode 100644 index 0000000..b86f9f0 --- /dev/null +++ b/raillabel/json_format/transform_data.py @@ -0,0 +1,13 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONTransformData(BaseModel): + """The translation and rotation of one coordinate system to another.""" + + translation: tuple[float, float, float] + quaternion: tuple[float, float, float, float] From 4a0979fe67dd5971e2ea4944267d6df021f8a443 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 09:43:40 +0100 Subject: [PATCH 057/190] feat: add JSONCoordinateSystem --- raillabel/json_format/coordinate_system.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 raillabel/json_format/coordinate_system.py diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py new file mode 100644 index 0000000..d8815a0 --- /dev/null +++ b/raillabel/json_format/coordinate_system.py @@ -0,0 +1,18 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel +from transform_data import JSONTransformData + + +class JSONCoordinateSystem(BaseModel): + """A 3D reference frame.""" + + children: list[str] + parent: Literal["base", ""] + pose_wrt_parent: JSONTransformData + type: Literal["sensor", "local"] From 1ef7536e763d2c796862d484778f0d9c19109fcb Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 09:48:31 +0100 Subject: [PATCH 058/190] feat: add JSONFrameInterval --- raillabel/json_format/frame_interval.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 raillabel/json_format/frame_interval.py diff --git a/raillabel/json_format/frame_interval.py b/raillabel/json_format/frame_interval.py new file mode 100644 index 0000000..1c5e51c --- /dev/null +++ b/raillabel/json_format/frame_interval.py @@ -0,0 +1,16 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONFrameInterval(BaseModel): + """A frame interval defines a starting and ending frame number as a closed interval. + + That means the interval includes the limit frame numbers. + """ + + start: int + end: int From cf37c176c89e6eddd61a3b718c516cdcc639181e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 09:53:59 +0100 Subject: [PATCH 059/190] feat: add JSONElementDataPointer --- raillabel/json_format/coordinate_system.py | 3 ++- raillabel/json_format/element_data_pointer.py | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 raillabel/json_format/element_data_pointer.py diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py index d8815a0..7af85e1 100644 --- a/raillabel/json_format/coordinate_system.py +++ b/raillabel/json_format/coordinate_system.py @@ -6,7 +6,8 @@ from typing import Literal from pydantic import BaseModel -from transform_data import JSONTransformData + +from .transform_data import JSONTransformData class JSONCoordinateSystem(BaseModel): diff --git a/raillabel/json_format/element_data_pointer.py b/raillabel/json_format/element_data_pointer.py new file mode 100644 index 0000000..0ac6623 --- /dev/null +++ b/raillabel/json_format/element_data_pointer.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel + +from .frame_interval import JSONFrameInterval + + +class JSONElementDataPointer(BaseModel): + """A pointer to element data of elements. + + It is indexed by 'name', and containing information about the element data type, for example, + bounding box, cuboid, and the frame intervals in which this element_data exists within an + element. That means, these pointers can be used to explore element data dynamic information + within the JSON content. + """ + + attribute_pointers: dict[str, Literal["num", "text", "boolean", "vec"]] + frame_intervals: list[JSONFrameInterval] + type: Literal["bbox", "num", "poly2d", "poly3d", "cuboid", "vec"] From fb1880b4a6d395c5940b411c86546474fbfe974f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 10:00:26 +0100 Subject: [PATCH 060/190] docs: add field documentation for JSON classes --- raillabel/json_format/coordinate_system.py | 8 ++++++++ raillabel/json_format/element_data_pointer.py | 7 +++++++ raillabel/json_format/frame_interval.py | 3 +++ raillabel/json_format/transform_data.py | 3 +++ 4 files changed, 21 insertions(+) diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py index 7af85e1..fbaa241 100644 --- a/raillabel/json_format/coordinate_system.py +++ b/raillabel/json_format/coordinate_system.py @@ -14,6 +14,14 @@ class JSONCoordinateSystem(BaseModel): """A 3D reference frame.""" children: list[str] + "List of children of this coordinate system." + parent: Literal["base", ""] + "This is the string UID of the parent coordinate system this coordinate system is referring to." + pose_wrt_parent: JSONTransformData + "The transformation with regards to the parent coordinate system." + type: Literal["sensor", "local"] + """This is a string that describes the type of the coordinate system, for example, 'local', + 'geo').""" diff --git a/raillabel/json_format/element_data_pointer.py b/raillabel/json_format/element_data_pointer.py index 0ac6623..48cfa14 100644 --- a/raillabel/json_format/element_data_pointer.py +++ b/raillabel/json_format/element_data_pointer.py @@ -20,5 +20,12 @@ class JSONElementDataPointer(BaseModel): """ attribute_pointers: dict[str, Literal["num", "text", "boolean", "vec"]] + """This is a JSON object which contains pointers to the attributes of the element data pointed by + this pointer. The attributes pointer keys shall be the 'name' of the attribute of the element + data this pointer points to.""" + frame_intervals: list[JSONFrameInterval] + "List of frame intervals of the element data pointed by this pointer." + type: Literal["bbox", "num", "poly2d", "poly3d", "cuboid", "vec"] + "Type of the element data pointed by this pointer." diff --git a/raillabel/json_format/frame_interval.py b/raillabel/json_format/frame_interval.py index 1c5e51c..cacd837 100644 --- a/raillabel/json_format/frame_interval.py +++ b/raillabel/json_format/frame_interval.py @@ -13,4 +13,7 @@ class JSONFrameInterval(BaseModel): """ start: int + "Initial frame number of the interval." + end: int + "Ending frame number of the interval." diff --git a/raillabel/json_format/transform_data.py b/raillabel/json_format/transform_data.py index b86f9f0..edf7c87 100644 --- a/raillabel/json_format/transform_data.py +++ b/raillabel/json_format/transform_data.py @@ -10,4 +10,7 @@ class JSONTransformData(BaseModel): """The translation and rotation of one coordinate system to another.""" translation: tuple[float, float, float] + "List of 4 values encoding a quaternion (x, y, z, w)." + quaternion: tuple[float, float, float, float] + "List of 3 values encoding the translation vector (x, y, z)" From 2df4b77d49dc6d1f28af01123e42520924619f18 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 30 Oct 2024 17:15:56 +0100 Subject: [PATCH 061/190] feat: add JSONObject --- raillabel/json_format/object.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 raillabel/json_format/object.py diff --git a/raillabel/json_format/object.py b/raillabel/json_format/object.py new file mode 100644 index 0000000..796cc22 --- /dev/null +++ b/raillabel/json_format/object.py @@ -0,0 +1,30 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + +from .element_data_pointer import JSONElementDataPointer +from .frame_interval import JSONFrameInterval + + +class JSONObject(BaseModel): + """An object is the main type of annotation element. + + Object is designed to represent spatiotemporal entities, such as physical objects in the real + world. Objects shall have a name and type. Objects may have static and dynamic data. Objects + are the only type of elements that may have geometric data, such as bounding boxes, cuboids, + polylines, images, etc. + """ + + name: str + "Name of the object. It is a friendly name and not used for indexing." + + type: str + "The type of an object defines the class the object corresponds to." + + frame_intervals: list[JSONFrameInterval] | None + "The array of frame intervals where this object exists or is defined." + + object_data_pointers: dict[str, JSONElementDataPointer] | None From ee79bca29eda6e5af45982a472c5f2dc56934ff8 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 31 Oct 2024 10:28:02 +0100 Subject: [PATCH 062/190] feat: add JSONAttributes --- raillabel/json_format/attributes.py | 24 ++++++++++++++++++++++ raillabel/json_format/boolean_attribute.py | 17 +++++++++++++++ raillabel/json_format/num_attribute.py | 17 +++++++++++++++ raillabel/json_format/text_attribute.py | 17 +++++++++++++++ raillabel/json_format/vec_attribute.py | 17 +++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 raillabel/json_format/attributes.py create mode 100644 raillabel/json_format/boolean_attribute.py create mode 100644 raillabel/json_format/num_attribute.py create mode 100644 raillabel/json_format/text_attribute.py create mode 100644 raillabel/json_format/vec_attribute.py diff --git a/raillabel/json_format/attributes.py b/raillabel/json_format/attributes.py new file mode 100644 index 0000000..448a895 --- /dev/null +++ b/raillabel/json_format/attributes.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + +from .boolean_attribute import JSONBooleanAttribute +from .num_attribute import JSONNumAttribute +from .text_attribute import JSONTextAttribute +from .vec_attribute import JSONVecAttribute + + +class JSONAttributes(BaseModel): + """Attributes is the alias of element data that can be nested inside geometric object data. + + For example, a certain bounding box can have attributes related to its score, visibility, etc. + These values can be nested inside the bounding box as attributes. + """ + + boolean: list[JSONBooleanAttribute] | None + num: list[JSONNumAttribute] | None + text: list[JSONTextAttribute] | None + vec: list[JSONVecAttribute] | None diff --git a/raillabel/json_format/boolean_attribute.py b/raillabel/json_format/boolean_attribute.py new file mode 100644 index 0000000..c7b34a1 --- /dev/null +++ b/raillabel/json_format/boolean_attribute.py @@ -0,0 +1,17 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONBooleanAttribute(BaseModel): + """A boolean attribute.""" + + name: str | None + """Friendly identifier describing the attribute. Used to track the attribute throughout + annotations and frames.""" + + val: bool + "The boolean value." diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py new file mode 100644 index 0000000..a74a4f5 --- /dev/null +++ b/raillabel/json_format/num_attribute.py @@ -0,0 +1,17 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONNumAttribute(BaseModel): + """A number attribute.""" + + name: str | None + """Friendly identifier describing the attribute. Used to track the attribute throughout + annotations and frames.""" + + val: int | float + "The number value." diff --git a/raillabel/json_format/text_attribute.py b/raillabel/json_format/text_attribute.py new file mode 100644 index 0000000..010df5a --- /dev/null +++ b/raillabel/json_format/text_attribute.py @@ -0,0 +1,17 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONTextAttribute(BaseModel): + """A text attribute.""" + + name: str | None + """Friendly identifier describing the attribute. Used to track the attribute throughout + annotations and frames.""" + + val: str + "The text value." diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py new file mode 100644 index 0000000..3c25081 --- /dev/null +++ b/raillabel/json_format/vec_attribute.py @@ -0,0 +1,17 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + + +class JSONVecAttribute(BaseModel): + """A vec attribute.""" + + name: str | None + """Friendly identifier describing the attribute. Used to track the attribute throughout + annotations and frames.""" + + val: list[int | float | str] + "The value vector of the attribute." From 6b1161e676b95b631206761c7a9dae37f874d645 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 4 Nov 2024 08:08:14 +0100 Subject: [PATCH 063/190] feat: add JSON annotations --- raillabel/json_format/bbox.py | 33 ++++++++++++++++++++++++++++ raillabel/json_format/cuboid.py | 35 +++++++++++++++++++++++++++++ raillabel/json_format/num.py | 29 ++++++++++++++++++++++++ raillabel/json_format/poly2d.py | 39 +++++++++++++++++++++++++++++++++ raillabel/json_format/poly3d.py | 33 ++++++++++++++++++++++++++++ raillabel/json_format/vec.py | 34 ++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+) create mode 100644 raillabel/json_format/bbox.py create mode 100644 raillabel/json_format/cuboid.py create mode 100644 raillabel/json_format/num.py create mode 100644 raillabel/json_format/poly2d.py create mode 100644 raillabel/json_format/poly3d.py create mode 100644 raillabel/json_format/vec.py diff --git a/raillabel/json_format/bbox.py b/raillabel/json_format/bbox.py new file mode 100644 index 0000000..bcccbbd --- /dev/null +++ b/raillabel/json_format/bbox.py @@ -0,0 +1,33 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONBbox(BaseModel): + """A 2D bounding box is defined as a 4-dimensional vector [x, y, w, h]. + + [x, y] is the center of the bounding box and [w, h] represent the width (horizontal, + x-coordinate dimension) and height (vertical, y-coordinate dimension), respectively. + """ + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: tuple[float, float, float, float] + "The array of 4 values that define the [x, y, w, h] values of the bbox." + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + attributes: JSONAttributes | None diff --git a/raillabel/json_format/cuboid.py b/raillabel/json_format/cuboid.py new file mode 100644 index 0000000..991013e --- /dev/null +++ b/raillabel/json_format/cuboid.py @@ -0,0 +1,35 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONCuboid(BaseModel): + """A cuboid or 3D bounding box. + + It is defined by the position of its center, the rotation in 3D, and its dimensions. + """ + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: tuple[float, float, float, float, float, float, float, float, float, float] + """List of values encoding the position, rotation and dimensions. It is + (x, y, z, qx, qy, qz, qw, sx, sy, sz) where (x, y, z) encodes the position, (qx, qy, qz, qw) + encodes the quaternion that encode the rotation, and (sx, sy, sz) are the dimensions of the + cuboid in its object coordinate system""" + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + attributes: JSONAttributes | None diff --git a/raillabel/json_format/num.py b/raillabel/json_format/num.py new file mode 100644 index 0000000..2898e2f --- /dev/null +++ b/raillabel/json_format/num.py @@ -0,0 +1,29 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONNum(BaseModel): + """A number.""" + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: list[int] + "The numerical value of the number." + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + attributes: JSONAttributes | None diff --git a/raillabel/json_format/poly2d.py b/raillabel/json_format/poly2d.py new file mode 100644 index 0000000..3519d0e --- /dev/null +++ b/raillabel/json_format/poly2d.py @@ -0,0 +1,39 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONPoly2d(BaseModel): + """A 2D polyline defined as a sequence of 2D points.""" + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: list[float | str] + "List of numerical values of the polyline, according to its mode." + + closed: bool + """A boolean that defines whether the polyline is closed or not. In case it is closed, it is + assumed that the last point of the sequence is connected with the first one.""" + + mode: Literal["MODE_POLY2D_ABSOLUTE"] + """Mode of the polyline describes how the points should be arranged in the images. + MODE_POLY2D_ABSOLUTE means that any point defined by an x-value followed by a y-value is the + absolute position.""" + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + attributes: JSONAttributes | None diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py new file mode 100644 index 0000000..6c6e15f --- /dev/null +++ b/raillabel/json_format/poly3d.py @@ -0,0 +1,33 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONPoly2d(BaseModel): + """A 3D polyline defined as a sequence of 3D points.""" + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: list[float] + "List of numerical values of the polyline, according to its mode." + + closed: bool + """A boolean that defines whether the polyline is closed or not. In case it is closed, it is + assumed that the last point of the sequence is connected with the first one.""" + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + attributes: JSONAttributes | None diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py new file mode 100644 index 0000000..842c00f --- /dev/null +++ b/raillabel/json_format/vec.py @@ -0,0 +1,34 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal +from uuid import UUID + +from pydantic import BaseModel + +from .attributes import JSONAttributes + + +class JSONPoly2d(BaseModel): + """A vector (list) of numbers.""" + + name: str + """This is a string encoding the name of this object data. It is used as index inside the + corresponding object data pointers.""" + + val: list[float] + "The numerical values of the vector (list) of numbers." + + coordinate_system: str | None + "Name of the coordinate system in respect of which this object data is expressed." + + uid: UUID | None + "This is a string encoding the Universal Unique identifyer of the annotation." + + type: Literal["values", "range"] | None + """This attribute specifies whether the vector shall be considered as a descriptor of individual + values or as a definition of a range.""" + + attributes: JSONAttributes | None From 4f6e0e3ff08acecfcf22520852e7eee903dc8732 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 08:46:17 +0100 Subject: [PATCH 064/190] feat: add JSONStreamCamera --- raillabel/json_format/stream_camera.py | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 raillabel/json_format/stream_camera.py diff --git a/raillabel/json_format/stream_camera.py b/raillabel/json_format/stream_camera.py new file mode 100644 index 0000000..c268acf --- /dev/null +++ b/raillabel/json_format/stream_camera.py @@ -0,0 +1,55 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel + + +class JSONStreamCamera(BaseModel): + """A stream describes the source of a data sequence, usually a sensor. + + This specific object contains the intrinsics of a camera sensor. + """ + + type: Literal["camera"] + "A string encoding the type of the stream." + + stream_properties: JSONStreamCameraProperties + "Intrinsic calibration of the stream." + + uri: str + "A string encoding the subdirectory containing the sensor files." + + description: str | None + "Description of the stream." + + +class JSONStreamCameraProperties(BaseModel): + """Intrinsic calibration of the stream.""" + + intrinsics_pinhole: JSONIntrinsicsPinhole + + +class JSONIntrinsicsPinhole(BaseModel): + """JSON object defining an instance of the intrinsic parameters of a pinhole camera.""" + + camera_matrix: tuple[ + float, float, float, float, float, float, float, float, float, float, float, float + ] + """This is a 3x4 camera matrix which projects 3D homogeneous points (4x1) from a camera + coordinate system into the image plane (3x1). This is the usual K matrix for camera projection as + in OpenCV. It is extended from 3x3 to 3x4 to enable its direct utilisation to project 4x1 + homogeneous 3D points. The matrix is defined to follow the camera model: x-to-right, y-down, + z-forward. The following equation applies: x_img = camera_matrix * X_ccs.""" + + distortion_coeffs: tuple[float, float, float, float, float] + "This is the array 1x5 radial and tangential distortion coefficients." + + height_px: int + "Height of the camera output in pixel." + + width_px: int + "Width of the camera output in pixel." From 3b864275e8b44b8be04d119cd15054df39f5bcd8 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 08:54:34 +0100 Subject: [PATCH 065/190] feat: add JSONStreamRadar --- raillabel/json_format/stream_radar.py | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 raillabel/json_format/stream_radar.py diff --git a/raillabel/json_format/stream_radar.py b/raillabel/json_format/stream_radar.py new file mode 100644 index 0000000..6724e49 --- /dev/null +++ b/raillabel/json_format/stream_radar.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel + + +class JSONStreamRadar(BaseModel): + """A stream describes the source of a data sequence, usually a sensor. + + This specific object contains the intrinsics of a radar sensor. + """ + + type: Literal["radar"] + "A string encoding the type of the stream." + + stream_properties: JSONStreamRadarProperties + "Intrinsic calibration of the stream." + + uri: str + "A string encoding the subdirectory containing the sensor files." + + description: str | None + "Description of the stream." + + +class JSONStreamRadarProperties(BaseModel): + """Intrinsic calibration of the stream.""" + + intrinsics_radar: JSONIntrinsicsRadar + + +class JSONIntrinsicsRadar(BaseModel): + """JSON object defining an instance of the intrinsic parameters of a radar.""" + + resolution_px_per_m: float + "Number correlating pixel in the output image to a position relative to the sensor in meters." + + height_px: int + "Height of the radar output in pixel." + + width_px: int + "Width of the radar output in pixel." From 183729acda0e5642f0dee9e8d6ed5704070e4346 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 08:56:59 +0100 Subject: [PATCH 066/190] feat: add JSONStreamOther --- raillabel/json_format/stream_other.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 raillabel/json_format/stream_other.py diff --git a/raillabel/json_format/stream_other.py b/raillabel/json_format/stream_other.py new file mode 100644 index 0000000..6c5da7d --- /dev/null +++ b/raillabel/json_format/stream_other.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel + + +class JSONStreamRadar(BaseModel): + """A stream describes the source of a data sequence, usually a sensor. + + This specific object describes a sensor without intrinsic calibration. + """ + + type: Literal["lidar", "gps_imu", "other"] + "A string encoding the type of the stream." + + uri: str | None + "A string encoding the subdirectory containing the sensor files." + + description: str | None + "Description of the stream." From 4e261d6f48ce63652da90949d7525771aff19c06 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 09:05:57 +0100 Subject: [PATCH 067/190] feat: add JSONStreamSync --- raillabel/json_format/stream_sync.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 raillabel/json_format/stream_sync.py diff --git a/raillabel/json_format/stream_sync.py b/raillabel/json_format/stream_sync.py new file mode 100644 index 0000000..0be704c --- /dev/null +++ b/raillabel/json_format/stream_sync.py @@ -0,0 +1,27 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from decimal import Decimal + +from pydantic import BaseModel + + +class JSONStreamSync(BaseModel): + """Syncronization information of a stream in a frame.""" + + stream_properties: JSONStreamSyncProperties + uri: str | None + + +class JSONStreamSyncProperties(BaseModel): + """The sync information.""" + + sync: JSONStreamSyncTimestamp + + +class JSONStreamSyncTimestamp(BaseModel): + """The timestamp of a stream sync.""" + + timestamp: Decimal | str From 5cf8d781a503f02c23a2aa8e0487529c608205fe Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 09:49:29 +0100 Subject: [PATCH 068/190] feat: add JSONObjectData --- raillabel/json_format/object_data.py | 22 ++++++++++++++++++++++ raillabel/json_format/poly3d.py | 2 +- raillabel/json_format/vec.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 raillabel/json_format/object_data.py diff --git a/raillabel/json_format/object_data.py b/raillabel/json_format/object_data.py new file mode 100644 index 0000000..78cc13a --- /dev/null +++ b/raillabel/json_format/object_data.py @@ -0,0 +1,22 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from pydantic import BaseModel + +from .bbox import JSONBbox +from .cuboid import JSONCuboid +from .poly2d import JSONPoly2d +from .poly3d import JSONPoly3d +from .vec import JSONVec + + +class JSONObjectData(BaseModel): + """Container of annotations of an object in a frame.""" + + bbox: list[JSONBbox] | None + cuboid: list[JSONCuboid] | None + poly2d: list[JSONPoly2d] | None + poly3d: list[JSONPoly3d] | None + vec: list[JSONVec] | None diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py index 6c6e15f..488df30 100644 --- a/raillabel/json_format/poly3d.py +++ b/raillabel/json_format/poly3d.py @@ -10,7 +10,7 @@ from .attributes import JSONAttributes -class JSONPoly2d(BaseModel): +class JSONPoly3d(BaseModel): """A 3D polyline defined as a sequence of 3D points.""" name: str diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py index 842c00f..925956e 100644 --- a/raillabel/json_format/vec.py +++ b/raillabel/json_format/vec.py @@ -11,7 +11,7 @@ from .attributes import JSONAttributes -class JSONPoly2d(BaseModel): +class JSONVec(BaseModel): """A vector (list) of numbers.""" name: str From 38df9a02ab5b2ea57877623ad5812941a8301462 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 10:10:00 +0100 Subject: [PATCH 069/190] feat: add JSONFrame --- raillabel/json_format/frame.py | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 raillabel/json_format/frame.py diff --git a/raillabel/json_format/frame.py b/raillabel/json_format/frame.py new file mode 100644 index 0000000..dfd6140 --- /dev/null +++ b/raillabel/json_format/frame.py @@ -0,0 +1,48 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from decimal import Decimal +from uuid import UUID + +from pydantic import BaseModel + +from .num import JSONNum +from .object_data import JSONObjectData +from .stream_sync import JSONStreamSync + + +class JSONFrame(BaseModel): + """A frame interval defines a starting and ending frame number as a closed interval. + + That means the interval includes the limit frame numbers. + """ + + frame_properties: JSONFrameProperties | None + "Container of frame information other than annotations." + + objects: dict[UUID, JSONObjectData] + """This is a JSON object that contains dynamic information on RailLabel objects. Object keys are + strings containing 32 bytes UUIDs. Object values contain an 'object_data' JSON object.""" + + +class JSONFrameProperties(BaseModel): + """Container of frame information other than annotations.""" + + timestamp: Decimal | str | None + """The timestamp indicates a time instant as a UTC string or numerical value to describe this + frame.""" + + streams: dict[str, JSONStreamSync] | None + """Stream timestamps for synchronization.""" + + frame_data: JSONFrameData | None + "Additional data to describe attributes of the frame (like GPS position)." + + +class JSONFrameData(BaseModel): + """Additional data to describe attributes of the frame (like GPS position).""" + + num: list[JSONNum] | None + "List of 'num' that describe this frame." From 274ba733900bec8f378eb3eb14f0d147c0991a44 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 10:28:14 +0100 Subject: [PATCH 070/190] docs: fix JSONFrame docstring --- raillabel/json_format/frame.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/raillabel/json_format/frame.py b/raillabel/json_format/frame.py index dfd6140..49a3108 100644 --- a/raillabel/json_format/frame.py +++ b/raillabel/json_format/frame.py @@ -14,10 +14,7 @@ class JSONFrame(BaseModel): - """A frame interval defines a starting and ending frame number as a closed interval. - - That means the interval includes the limit frame numbers. - """ + """A frame is a container of dynamic, timewise, information.""" frame_properties: JSONFrameProperties | None "Container of frame information other than annotations." From 7b735442f4ac086f1c59c0a2d679f246425c457e Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 10:36:52 +0100 Subject: [PATCH 071/190] feat: add JSONMetadata --- raillabel/json_format/metadata.py | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 raillabel/json_format/metadata.py diff --git a/raillabel/json_format/metadata.py b/raillabel/json_format/metadata.py new file mode 100644 index 0000000..4c7cb6e --- /dev/null +++ b/raillabel/json_format/metadata.py @@ -0,0 +1,33 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel + + +class JSONMetadata(BaseModel): + """Metadata about the annotation file itself.""" + + schema_version: Literal["1.0.0"] + "Version number of the OpenLABEL schema this annotation file follows." + + name: str | None + "Name of the OpenLABEL annotation content." + + subschema_version: str | None + "Version number of the RailLabel subschema this annotation file follows." + + exporter_version: str | None + "Version identifyer of the exporter software." + + file_version: str | None + "Version number of the OpenLABEL annotation content." + + tagged_file: str | None + "File name or URI of the data file being tagged." + + comment: str | None + "Additional information or description about the annotation content." From f6b6b713c65d16c452383baa29d76fee6acd47dd Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 10:43:48 +0100 Subject: [PATCH 072/190] feat: add JSONScene --- raillabel/json_format/scene.py | 34 +++++++++++++++++++++++++++ raillabel/json_format/stream_other.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 raillabel/json_format/scene.py diff --git a/raillabel/json_format/scene.py b/raillabel/json_format/scene.py new file mode 100644 index 0000000..3de4e9a --- /dev/null +++ b/raillabel/json_format/scene.py @@ -0,0 +1,34 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +from pydantic import BaseModel + +from .coordinate_system import JSONCoordinateSystem +from .frame import JSONFrame +from .frame_interval import JSONFrameInterval +from .metadata import JSONMetadata +from .object import JSONObject +from .stream_camera import JSONStreamCamera +from .stream_other import JSONStreamOther +from .stream_radar import JSONStreamRadar + + +class JSONScene(BaseModel): + """Root RailLabel object.""" + + openlabel: JSONSceneContent + + +class JSONSceneContent(BaseModel): + """Container of all scene content.""" + + metadata: JSONMetadata + coordinate_systems: dict[str, JSONCoordinateSystem] | None + streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None + objects: dict[UUID, JSONObject] | None + frames: dict[int, JSONFrame] | None + frame_intervals: list[JSONFrameInterval] | None diff --git a/raillabel/json_format/stream_other.py b/raillabel/json_format/stream_other.py index 6c5da7d..e6260b5 100644 --- a/raillabel/json_format/stream_other.py +++ b/raillabel/json_format/stream_other.py @@ -8,7 +8,7 @@ from pydantic import BaseModel -class JSONStreamRadar(BaseModel): +class JSONStreamOther(BaseModel): """A stream describes the source of a data sequence, usually a sensor. This specific object describes a sensor without intrinsic calibration. From 8a4cb4a0067ffb99a0fe829b1ca090e9b481c878 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 10:51:31 +0100 Subject: [PATCH 073/190] feat: make json classes more accessible --- raillabel/json_format/__init__.py | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/raillabel/json_format/__init__.py b/raillabel/json_format/__init__.py index fc6aa44..4093a69 100644 --- a/raillabel/json_format/__init__.py +++ b/raillabel/json_format/__init__.py @@ -1,3 +1,62 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Package for classes representing the direct RailLabel JSON objects.""" + +from .attributes import JSONAttributes +from .bbox import JSONBbox +from .boolean_attribute import JSONBooleanAttribute +from .coordinate_system import JSONCoordinateSystem +from .cuboid import JSONCuboid +from .element_data_pointer import JSONElementDataPointer +from .frame import JSONFrame +from .frame_interval import JSONFrameInterval +from .metadata import JSONMetadata +from .num import JSONNum +from .num_attribute import JSONNumAttribute +from .object import JSONObject +from .object_data import JSONObjectData +from .poly2d import JSONPoly2d +from .poly3d import JSONPoly3d +from .scene import JSONScene, JSONSceneContent +from .stream_camera import JSONIntrinsicsPinhole, JSONStreamCamera, JSONStreamCameraProperties +from .stream_other import JSONStreamOther +from .stream_radar import JSONIntrinsicsRadar, JSONStreamRadar, JSONStreamRadarProperties +from .stream_sync import JSONStreamSync, JSONStreamSyncProperties, JSONStreamSyncTimestamp +from .text_attribute import JSONTextAttribute +from .transform_data import JSONTransformData +from .vec import JSONVec +from .vec_attribute import JSONVecAttribute + +__all__ = [ + "JSONAttributes", + "JSONBbox", + "JSONBooleanAttribute", + "JSONCoordinateSystem", + "JSONCuboid", + "JSONElementDataPointer", + "JSONFrameInterval", + "JSONFrame", + "JSONMetadata", + "JSONNumAttribute", + "JSONNum", + "JSONObjectData", + "JSONObject", + "JSONPoly2d", + "JSONPoly3d", + "JSONScene", + "JSONSceneContent", + "JSONStreamCamera", + "JSONStreamCameraProperties", + "JSONIntrinsicsPinhole", + "JSONStreamOther", + "JSONStreamRadar", + "JSONStreamRadarProperties", + "JSONIntrinsicsRadar", + "JSONStreamSync", + "JSONStreamSyncProperties", + "JSONStreamSyncTimestamp", + "JSONTextAttribute", + "JSONTransformData", + "JSONVecAttribute", + "JSONVec", +] From 3beca64156757b0976a4e955a4de861a968a789e Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 11:06:27 +0100 Subject: [PATCH 074/190] fix: errors in JSON format classes --- raillabel/json_format/attributes.py | 8 ++++---- raillabel/json_format/bbox.py | 6 +++--- raillabel/json_format/boolean_attribute.py | 8 ++++---- raillabel/json_format/coordinate_system.py | 12 ++++++------ raillabel/json_format/cuboid.py | 6 +++--- raillabel/json_format/frame.py | 12 ++++++------ raillabel/json_format/frame_interval.py | 4 ++-- raillabel/json_format/metadata.py | 12 ++++++------ raillabel/json_format/num.py | 8 ++++---- raillabel/json_format/num_attribute.py | 2 +- raillabel/json_format/object.py | 4 ++-- raillabel/json_format/object_data.py | 10 +++++----- raillabel/json_format/poly2d.py | 6 +++--- raillabel/json_format/poly3d.py | 6 +++--- raillabel/json_format/scene.py | 10 +++++----- raillabel/json_format/stream_camera.py | 2 +- raillabel/json_format/stream_other.py | 4 ++-- raillabel/json_format/stream_radar.py | 2 +- raillabel/json_format/stream_sync.py | 2 +- raillabel/json_format/text_attribute.py | 2 +- raillabel/json_format/vec.py | 8 ++++---- raillabel/json_format/vec_attribute.py | 2 +- tests/test_raillabel/json_format/test_scene.py | 18 ++++++++++++++++++ 23 files changed, 86 insertions(+), 68 deletions(-) create mode 100644 tests/test_raillabel/json_format/test_scene.py diff --git a/raillabel/json_format/attributes.py b/raillabel/json_format/attributes.py index 448a895..976c44d 100644 --- a/raillabel/json_format/attributes.py +++ b/raillabel/json_format/attributes.py @@ -18,7 +18,7 @@ class JSONAttributes(BaseModel): These values can be nested inside the bounding box as attributes. """ - boolean: list[JSONBooleanAttribute] | None - num: list[JSONNumAttribute] | None - text: list[JSONTextAttribute] | None - vec: list[JSONVecAttribute] | None + boolean: list[JSONBooleanAttribute] | None = None + num: list[JSONNumAttribute] | None = None + text: list[JSONTextAttribute] | None = None + vec: list[JSONVecAttribute] | None = None diff --git a/raillabel/json_format/bbox.py b/raillabel/json_format/bbox.py index bcccbbd..c1df9f1 100644 --- a/raillabel/json_format/bbox.py +++ b/raillabel/json_format/bbox.py @@ -24,10 +24,10 @@ class JSONBbox(BaseModel): val: tuple[float, float, float, float] "The array of 4 values that define the [x, y, w, h] values of the bbox." - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/boolean_attribute.py b/raillabel/json_format/boolean_attribute.py index c7b34a1..04db175 100644 --- a/raillabel/json_format/boolean_attribute.py +++ b/raillabel/json_format/boolean_attribute.py @@ -9,9 +9,9 @@ class JSONBooleanAttribute(BaseModel): """A boolean attribute.""" - name: str | None - """Friendly identifier describing the attribute. Used to track the attribute throughout - annotations and frames.""" - val: bool "The boolean value." + + name: str | None = None + """Friendly identifier describing the attribute. Used to track the attribute throughout + annotations and frames.""" diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py index fbaa241..e1d664a 100644 --- a/raillabel/json_format/coordinate_system.py +++ b/raillabel/json_format/coordinate_system.py @@ -13,15 +13,15 @@ class JSONCoordinateSystem(BaseModel): """A 3D reference frame.""" - children: list[str] - "List of children of this coordinate system." - parent: Literal["base", ""] "This is the string UID of the parent coordinate system this coordinate system is referring to." - pose_wrt_parent: JSONTransformData - "The transformation with regards to the parent coordinate system." - type: Literal["sensor", "local"] """This is a string that describes the type of the coordinate system, for example, 'local', 'geo').""" + + pose_wrt_parent: JSONTransformData | None = None + "The transformation with regards to the parent coordinate system." + + children: list[str] | None = None + "List of children of this coordinate system." diff --git a/raillabel/json_format/cuboid.py b/raillabel/json_format/cuboid.py index 991013e..e07032f 100644 --- a/raillabel/json_format/cuboid.py +++ b/raillabel/json_format/cuboid.py @@ -26,10 +26,10 @@ class JSONCuboid(BaseModel): encodes the quaternion that encode the rotation, and (sx, sy, sz) are the dimensions of the cuboid in its object coordinate system""" - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/frame.py b/raillabel/json_format/frame.py index 49a3108..797baec 100644 --- a/raillabel/json_format/frame.py +++ b/raillabel/json_format/frame.py @@ -16,10 +16,10 @@ class JSONFrame(BaseModel): """A frame is a container of dynamic, timewise, information.""" - frame_properties: JSONFrameProperties | None + frame_properties: JSONFrameProperties | None = None "Container of frame information other than annotations." - objects: dict[UUID, JSONObjectData] + objects: dict[UUID, JSONObjectData] | None = None """This is a JSON object that contains dynamic information on RailLabel objects. Object keys are strings containing 32 bytes UUIDs. Object values contain an 'object_data' JSON object.""" @@ -27,19 +27,19 @@ class JSONFrame(BaseModel): class JSONFrameProperties(BaseModel): """Container of frame information other than annotations.""" - timestamp: Decimal | str | None + timestamp: Decimal | str | None = None """The timestamp indicates a time instant as a UTC string or numerical value to describe this frame.""" - streams: dict[str, JSONStreamSync] | None + streams: dict[str, JSONStreamSync] | None = None """Stream timestamps for synchronization.""" - frame_data: JSONFrameData | None + frame_data: JSONFrameData | None = None "Additional data to describe attributes of the frame (like GPS position)." class JSONFrameData(BaseModel): """Additional data to describe attributes of the frame (like GPS position).""" - num: list[JSONNum] | None + num: list[JSONNum] | None = None "List of 'num' that describe this frame." diff --git a/raillabel/json_format/frame_interval.py b/raillabel/json_format/frame_interval.py index cacd837..b56cbe9 100644 --- a/raillabel/json_format/frame_interval.py +++ b/raillabel/json_format/frame_interval.py @@ -12,8 +12,8 @@ class JSONFrameInterval(BaseModel): That means the interval includes the limit frame numbers. """ - start: int + frame_start: int "Initial frame number of the interval." - end: int + frame_end: int "Ending frame number of the interval." diff --git a/raillabel/json_format/metadata.py b/raillabel/json_format/metadata.py index 4c7cb6e..a24e117 100644 --- a/raillabel/json_format/metadata.py +++ b/raillabel/json_format/metadata.py @@ -14,20 +14,20 @@ class JSONMetadata(BaseModel): schema_version: Literal["1.0.0"] "Version number of the OpenLABEL schema this annotation file follows." - name: str | None + name: str | None = None "Name of the OpenLABEL annotation content." - subschema_version: str | None + subschema_version: str | None = None "Version number of the RailLabel subschema this annotation file follows." - exporter_version: str | None + exporter_version: str | None = None "Version identifyer of the exporter software." - file_version: str | None + file_version: str | None = None "Version number of the OpenLABEL annotation content." - tagged_file: str | None + tagged_file: str | None = None "File name or URI of the data file being tagged." - comment: str | None + comment: str | None = None "Additional information or description about the annotation content." diff --git a/raillabel/json_format/num.py b/raillabel/json_format/num.py index 2898e2f..75d08d4 100644 --- a/raillabel/json_format/num.py +++ b/raillabel/json_format/num.py @@ -17,13 +17,13 @@ class JSONNum(BaseModel): """This is a string encoding the name of this object data. It is used as index inside the corresponding object data pointers.""" - val: list[int] + val: float "The numerical value of the number." - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py index a74a4f5..e70e69b 100644 --- a/raillabel/json_format/num_attribute.py +++ b/raillabel/json_format/num_attribute.py @@ -9,7 +9,7 @@ class JSONNumAttribute(BaseModel): """A number attribute.""" - name: str | None + name: str | None = None """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/raillabel/json_format/object.py b/raillabel/json_format/object.py index 796cc22..091843a 100644 --- a/raillabel/json_format/object.py +++ b/raillabel/json_format/object.py @@ -24,7 +24,7 @@ class JSONObject(BaseModel): type: str "The type of an object defines the class the object corresponds to." - frame_intervals: list[JSONFrameInterval] | None + frame_intervals: list[JSONFrameInterval] | None = None "The array of frame intervals where this object exists or is defined." - object_data_pointers: dict[str, JSONElementDataPointer] | None + object_data_pointers: dict[str, JSONElementDataPointer] | None = None diff --git a/raillabel/json_format/object_data.py b/raillabel/json_format/object_data.py index 78cc13a..bffa98a 100644 --- a/raillabel/json_format/object_data.py +++ b/raillabel/json_format/object_data.py @@ -15,8 +15,8 @@ class JSONObjectData(BaseModel): """Container of annotations of an object in a frame.""" - bbox: list[JSONBbox] | None - cuboid: list[JSONCuboid] | None - poly2d: list[JSONPoly2d] | None - poly3d: list[JSONPoly3d] | None - vec: list[JSONVec] | None + bbox: list[JSONBbox] | None = None + cuboid: list[JSONCuboid] | None = None + poly2d: list[JSONPoly2d] | None = None + poly3d: list[JSONPoly3d] | None = None + vec: list[JSONVec] | None = None diff --git a/raillabel/json_format/poly2d.py b/raillabel/json_format/poly2d.py index 3519d0e..d8f687c 100644 --- a/raillabel/json_format/poly2d.py +++ b/raillabel/json_format/poly2d.py @@ -30,10 +30,10 @@ class JSONPoly2d(BaseModel): MODE_POLY2D_ABSOLUTE means that any point defined by an x-value followed by a y-value is the absolute position.""" - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py index 488df30..685f088 100644 --- a/raillabel/json_format/poly3d.py +++ b/raillabel/json_format/poly3d.py @@ -24,10 +24,10 @@ class JSONPoly3d(BaseModel): """A boolean that defines whether the polyline is closed or not. In case it is closed, it is assumed that the last point of the sequence is connected with the first one.""" - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/scene.py b/raillabel/json_format/scene.py index 3de4e9a..65326ec 100644 --- a/raillabel/json_format/scene.py +++ b/raillabel/json_format/scene.py @@ -27,8 +27,8 @@ class JSONSceneContent(BaseModel): """Container of all scene content.""" metadata: JSONMetadata - coordinate_systems: dict[str, JSONCoordinateSystem] | None - streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None - objects: dict[UUID, JSONObject] | None - frames: dict[int, JSONFrame] | None - frame_intervals: list[JSONFrameInterval] | None + coordinate_systems: dict[str, JSONCoordinateSystem] | None = None + streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None = None + objects: dict[UUID, JSONObject] | None = None + frames: dict[int, JSONFrame] | None = None + frame_intervals: list[JSONFrameInterval] | None = None diff --git a/raillabel/json_format/stream_camera.py b/raillabel/json_format/stream_camera.py index c268acf..b8f27ec 100644 --- a/raillabel/json_format/stream_camera.py +++ b/raillabel/json_format/stream_camera.py @@ -23,7 +23,7 @@ class JSONStreamCamera(BaseModel): uri: str "A string encoding the subdirectory containing the sensor files." - description: str | None + description: str | None = None "Description of the stream." diff --git a/raillabel/json_format/stream_other.py b/raillabel/json_format/stream_other.py index e6260b5..a1d47ba 100644 --- a/raillabel/json_format/stream_other.py +++ b/raillabel/json_format/stream_other.py @@ -17,8 +17,8 @@ class JSONStreamOther(BaseModel): type: Literal["lidar", "gps_imu", "other"] "A string encoding the type of the stream." - uri: str | None + uri: str | None = None "A string encoding the subdirectory containing the sensor files." - description: str | None + description: str | None = None "Description of the stream." diff --git a/raillabel/json_format/stream_radar.py b/raillabel/json_format/stream_radar.py index 6724e49..3c4a19c 100644 --- a/raillabel/json_format/stream_radar.py +++ b/raillabel/json_format/stream_radar.py @@ -23,7 +23,7 @@ class JSONStreamRadar(BaseModel): uri: str "A string encoding the subdirectory containing the sensor files." - description: str | None + description: str | None = None "Description of the stream." diff --git a/raillabel/json_format/stream_sync.py b/raillabel/json_format/stream_sync.py index 0be704c..2757924 100644 --- a/raillabel/json_format/stream_sync.py +++ b/raillabel/json_format/stream_sync.py @@ -12,7 +12,7 @@ class JSONStreamSync(BaseModel): """Syncronization information of a stream in a frame.""" stream_properties: JSONStreamSyncProperties - uri: str | None + uri: str | None = None class JSONStreamSyncProperties(BaseModel): diff --git a/raillabel/json_format/text_attribute.py b/raillabel/json_format/text_attribute.py index 010df5a..221b63a 100644 --- a/raillabel/json_format/text_attribute.py +++ b/raillabel/json_format/text_attribute.py @@ -9,7 +9,7 @@ class JSONTextAttribute(BaseModel): """A text attribute.""" - name: str | None + name: str | None = None """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py index 925956e..7a28345 100644 --- a/raillabel/json_format/vec.py +++ b/raillabel/json_format/vec.py @@ -21,14 +21,14 @@ class JSONVec(BaseModel): val: list[float] "The numerical values of the vector (list) of numbers." - coordinate_system: str | None + coordinate_system: str | None = None "Name of the coordinate system in respect of which this object data is expressed." - uid: UUID | None + uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - type: Literal["values", "range"] | None + type: Literal["values", "range"] | None = None """This attribute specifies whether the vector shall be considered as a descriptor of individual values or as a definition of a range.""" - attributes: JSONAttributes | None + attributes: JSONAttributes | None = None diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py index 3c25081..6540475 100644 --- a/raillabel/json_format/vec_attribute.py +++ b/raillabel/json_format/vec_attribute.py @@ -9,7 +9,7 @@ class JSONVecAttribute(BaseModel): """A vec attribute.""" - name: str | None + name: str | None = None """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/tests/test_raillabel/json_format/test_scene.py b/tests/test_raillabel/json_format/test_scene.py new file mode 100644 index 0000000..5229eb0 --- /dev/null +++ b/tests/test_raillabel/json_format/test_scene.py @@ -0,0 +1,18 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import os + +import pytest + +from raillabel.json_format import JSONScene + + +def test_parse__openlabel_short(json_data): + JSONScene(**json_data["openlabel_v1_short"]) + + +# Executes the test if the file is called +if __name__ == "__main__": + os.system("clear") + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) From f40ac8c58a3a4b06dfd9aaffc8abac2e4d9f2e80 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:10:17 +0100 Subject: [PATCH 075/190] feat: Transform.from_json() --- raillabel/format/transform.py | 15 ++++++++++++ tests/test_raillabel/format/test_point3d.py | 12 +++++----- tests/test_raillabel/format/test_transform.py | 23 +++++++++++-------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 46e2282..e5d08f4 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -5,6 +5,8 @@ from dataclasses import dataclass +from raillabel.json_format import JSONTransformData + from .point3d import Point3d from .quaternion import Quaternion @@ -25,6 +27,19 @@ class Transform: pos: Point3d quat: Quaternion + @classmethod + def from_json(cls, json: JSONTransformData) -> Transform: + """Construct an instant of this class from RailLabel JSON data.""" + return Transform( + pos=Point3d(x=json.translation[0], y=json.translation[1], z=json.translation[2]), + quat=Quaternion( + x=json.quaternion[0], + y=json.quaternion[1], + z=json.quaternion[2], + w=json.quaternion[3], + ), + ) + @classmethod def fromdict(cls, data_dict: dict) -> Transform: """Generate a Transform object from a dict. diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index c3d6225..faac51a 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -18,12 +18,12 @@ @pytest.fixture def point3d_dict() -> dict: - return [420, 3.14, 0] + return [419, 3.14, 0] @pytest.fixture def point3d() -> dict: - return Point3d(420, 3.14, 0) + return Point3d(419, 3.14, 0) @pytest.fixture @@ -40,21 +40,21 @@ def point3d_another() -> dict: def test_fromdict(): - point3d = Point3d.fromdict([420, 3.14, 0]) + point3d = Point3d.fromdict([419, 3.14, 0]) - assert point3d.x == 420 + assert point3d.x == 419 assert point3d.y == 3.14 assert point3d.z == 0 def test_asdict(): point3d = Point3d( - x=420, + x=419, y=3.14, z=0, ) - assert point3d.asdict() == [420, 3.14, 0] + assert point3d.asdict() == [419, 3.14, 0] if __name__ == "__main__": diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index 2bf2302..db703e2 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -3,15 +3,11 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import Transform +from raillabel.json_format import JSONTransformData + # == Fixtures ========================= @@ -22,13 +18,23 @@ def transform_dict(point3d_dict, quaternion_dict) -> dict: @pytest.fixture -def transform(point3d, quaternion) -> dict: +def transform_json(point3d_dict, quaternion_dict) -> JSONTransformData: + return JSONTransformData(translation=point3d_dict, quaternion=quaternion_dict) + + +@pytest.fixture +def transform(point3d, quaternion) -> Transform: return Transform(pos=point3d, quat=quaternion) # == Tests ============================ +def test_from_json(transform_json, transform): + actual = Transform.from_json(transform_json) + assert actual == transform + + def test_fromdict(point3d, point3d_dict, quaternion, quaternion_dict): transform = Transform.fromdict({"translation": point3d_dict, "quaternion": quaternion_dict}) @@ -43,5 +49,4 @@ def test_asdict(point3d, point3d_dict, quaternion, quaternion_dict): if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) From 50ee4aa12a60e41512042cbca826bb35583dada9 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:17:37 +0100 Subject: [PATCH 076/190] refactor: rename Transform fields --- CHANGELOG.md | 4 +++ raillabel/format/sensor.py | 4 +-- raillabel/format/transform.py | 28 ++++++++----------- tests/test_raillabel/format/test_transform.py | 8 +++--- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28e26b..03dcc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,3 +103,7 @@ If you are building raillabel scenes yourself or want to manipulate the data in Functionality, that has been **moved** to the `raillabel_providerkit`: - loading annotations in formats other than raillabel itself - validating the content of files + +Other breaking changes: +- the `fromdict()` and `asdict()` methods in `raillabel.format` classes have been replaced with `from_json()` and `to_json` respectively +- `raillabel.format.Transform` fields have been changed by `pos -> position` and `quad -> quaternion` to make it more explicit diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index 9f5c6e4..6bdf5c9 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -126,12 +126,12 @@ def _extrinsics_fromdict(cls, data_dict: dict) -> Transform | None: return None return Transform( - pos=Point3d( + position=Point3d( x=data_dict["pose_wrt_parent"]["translation"][0], y=data_dict["pose_wrt_parent"]["translation"][1], z=data_dict["pose_wrt_parent"]["translation"][2], ), - quat=Quaternion( + quaternion=Quaternion( x=data_dict["pose_wrt_parent"]["quaternion"][0], y=data_dict["pose_wrt_parent"]["quaternion"][1], z=data_dict["pose_wrt_parent"]["quaternion"][2], diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index e5d08f4..65f1a12 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -13,26 +13,20 @@ @dataclass class Transform: - """A transformation between two coordinate systems. + """A transformation between two coordinate systems.""" - Parameters - ---------- - pos: raillabel.format.Point3d - Translation with regards to the parent coordinate system. - quat: raillabel.format.Quaternion - Rotation quaternion with regards to the parent coordinate system. + position: Point3d + "Translation with regards to the parent coordinate system." - """ - - pos: Point3d - quat: Quaternion + quaternion: Quaternion + "Rotation quaternion with regards to the parent coordinate system." @classmethod def from_json(cls, json: JSONTransformData) -> Transform: """Construct an instant of this class from RailLabel JSON data.""" return Transform( - pos=Point3d(x=json.translation[0], y=json.translation[1], z=json.translation[2]), - quat=Quaternion( + position=Point3d(x=json.translation[0], y=json.translation[1], z=json.translation[2]), + quaternion=Quaternion( x=json.quaternion[0], y=json.quaternion[1], z=json.quaternion[2], @@ -51,8 +45,8 @@ def fromdict(cls, data_dict: dict) -> Transform: """ return Transform( - pos=Point3d.fromdict(data_dict["translation"]), - quat=Quaternion.fromdict(data_dict["quaternion"]), + position=Point3d.fromdict(data_dict["translation"]), + quaternion=Quaternion.fromdict(data_dict["quaternion"]), ) def asdict(self) -> dict[str, list[float]]: @@ -70,6 +64,6 @@ def asdict(self) -> dict[str, list[float]]: """ return { - "translation": self.pos.asdict(), - "quaternion": self.quat.asdict(), + "translation": self.position.asdict(), + "quaternion": self.quaternion.asdict(), } diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index db703e2..f6bcd78 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -24,7 +24,7 @@ def transform_json(point3d_dict, quaternion_dict) -> JSONTransformData: @pytest.fixture def transform(point3d, quaternion) -> Transform: - return Transform(pos=point3d, quat=quaternion) + return Transform(position=point3d, quaternion=quaternion) # == Tests ============================ @@ -38,12 +38,12 @@ def test_from_json(transform_json, transform): def test_fromdict(point3d, point3d_dict, quaternion, quaternion_dict): transform = Transform.fromdict({"translation": point3d_dict, "quaternion": quaternion_dict}) - assert transform.pos == point3d - assert transform.quat == quaternion + assert transform.position == point3d + assert transform.quaternion == quaternion def test_asdict(point3d, point3d_dict, quaternion, quaternion_dict): - transform = Transform(pos=point3d, quat=quaternion) + transform = Transform(position=point3d, quaternion=quaternion) assert transform.asdict() == {"translation": point3d_dict, "quaternion": quaternion_dict} From 3017259d956f7a8aafb676157148556f0497958c Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:26:39 +0100 Subject: [PATCH 077/190] feat: FrameInterval.from_json() --- raillabel/format/frame_interval.py | 27 ++++++++++--------- .../format/test_frame_interval.py | 20 ++++++++------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 22f10f5..c092c2c 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -5,6 +5,8 @@ from dataclasses import dataclass +from raillabel.json_format import JSONFrameInterval + @dataclass class FrameInterval: @@ -22,6 +24,14 @@ class FrameInterval: frame_start: int frame_end: int + @classmethod + def from_json(cls, json: JSONFrameInterval) -> FrameInterval: + """Construct an instant of this class from RailLabel JSON data.""" + return FrameInterval( + frame_start=json.frame_start, + frame_end=json.frame_end, + ) + @classmethod def fromdict(cls, data_dict: dict) -> FrameInterval: """Generate a FrameInterval object from a dict. @@ -41,24 +51,15 @@ def fromdict(cls, data_dict: dict) -> FrameInterval: def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: """Convert a list of frame uids into FrameIntervals. - Parameters - ---------- - frame_uids: list[int] - List of frame uids, that should be included in the FrameIntervals. - - Returns - ------- - list[FrameInterval] - FrameIntervals corresponding to the frames ids. - - Example + Example: ------- - FrameInterval.from_frame_uids([0, 1, 2, 3, 6, 7, 9, 12, 13, 14]) == [ + ```python + FrameInterval.from_frame_uids([0, 1, 2, 3, 9, 12, 13, 14]) == [ FrameInterval(0, 3), - FrameInterval(6, 7), FrameInterval(9, 9), FrameInterval(12, 14), ] + ``` """ sorted_frame_uids = sorted(frame_uids) diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index 321fd04..22e608e 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -3,15 +3,10 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import FrameInterval +from raillabel.json_format import JSONFrameInterval # == Fixtures ========================= @@ -21,6 +16,11 @@ def frame_interval_dict() -> dict: return {"frame_start": 12, "frame_end": 16} +@pytest.fixture +def frame_interval_json() -> JSONFrameInterval: + return JSONFrameInterval(frame_start=12, frame_end=16) + + @pytest.fixture def frame_interval() -> dict: return FrameInterval( @@ -32,6 +32,11 @@ def frame_interval() -> dict: # == Tests ============================ +def test_from_json(frame_interval, frame_interval_json): + actual = FrameInterval.from_json(frame_interval_json) + assert actual == frame_interval + + def test_fromdict(): frame_interval = FrameInterval.fromdict( { @@ -104,5 +109,4 @@ def test_from_frame_uids_unsorted(): if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) From 427f35edd2a89902d96a01c77bafb1ab621ff053 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:29:47 +0100 Subject: [PATCH 078/190] refactor: rename FrameInterval fields --- CHANGELOG.md | 1 + raillabel/format/frame_interval.py | 33 ++++++++----------- .../format/test_frame_interval.py | 16 ++++----- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03dcc2d..ae73b05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,3 +107,4 @@ Functionality, that has been **moved** to the `raillabel_providerkit`: Other breaking changes: - the `fromdict()` and `asdict()` methods in `raillabel.format` classes have been replaced with `from_json()` and `to_json` respectively - `raillabel.format.Transform` fields have been changed by `pos -> position` and `quad -> quaternion` to make it more explicit +- `raillabel.format.FrameInterval` fields have been changed by `frame_start -> start` and `frame_end -> end` to make it more concise diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index c092c2c..8780785 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -10,26 +10,20 @@ @dataclass class FrameInterval: - """Closed interval of frames. + """Closed interval of frames.""" - Parameters - ---------- - frame_start: int - Initial frame number of the interval (inclusive). - frame_end: int - Ending frame number of the interval (inclusive). + start: int + "Initial frame number of the interval (inclusive)." - """ - - frame_start: int - frame_end: int + end: int + "Ending frame number of the interval (inclusive)." @classmethod def from_json(cls, json: JSONFrameInterval) -> FrameInterval: """Construct an instant of this class from RailLabel JSON data.""" return FrameInterval( - frame_start=json.frame_start, - frame_end=json.frame_end, + start=json.frame_start, + end=json.frame_end, ) @classmethod @@ -43,8 +37,8 @@ def fromdict(cls, data_dict: dict) -> FrameInterval: """ return FrameInterval( - frame_start=data_dict["frame_start"], - frame_end=data_dict["frame_end"], + start=data_dict["frame_start"], + end=data_dict["frame_end"], ) @classmethod @@ -66,8 +60,7 @@ def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: frame_uid_intervals = cls._slice_into_intervals(sorted_frame_uids) return [ - FrameInterval(frame_start=interval[0], frame_end=interval[-1]) - for interval in frame_uid_intervals + FrameInterval(start=interval[0], end=interval[-1]) for interval in frame_uid_intervals ] def asdict(self) -> dict: @@ -85,13 +78,13 @@ def asdict(self) -> dict: """ return { - "frame_start": int(self.frame_start), - "frame_end": int(self.frame_end), + "frame_start": int(self.start), + "frame_end": int(self.end), } def __len__(self) -> int: """Return the length in frames.""" - return abs(self.frame_start - self.frame_end) + 1 + return abs(self.start - self.end) + 1 @classmethod def _slice_into_intervals(cls, sorted_frame_uids: list[int]) -> list[list[int]]: diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index 22e608e..86ad148 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -24,8 +24,8 @@ def frame_interval_json() -> JSONFrameInterval: @pytest.fixture def frame_interval() -> dict: return FrameInterval( - frame_start=12, - frame_end=16, + start=12, + end=16, ) @@ -45,14 +45,14 @@ def test_fromdict(): } ) - assert frame_interval.frame_start == 12 - assert frame_interval.frame_end == 16 + assert frame_interval.start == 12 + assert frame_interval.end == 16 def test_asdict(): frame_interval = FrameInterval( - frame_start=12, - frame_end=16, + start=12, + end=16, ) assert frame_interval.asdict() == { @@ -63,8 +63,8 @@ def test_asdict(): def test_len(): frame_interval = FrameInterval( - frame_start=12, - frame_end=16, + start=12, + end=16, ) assert len(frame_interval) == 5 From e385f0eefad751608d5db59a05badfd0c1572b88 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:35:55 +0100 Subject: [PATCH 079/190] feat: IntrinsicsPinhole.from_json() --- raillabel/format/intrinsics_pinhole.py | 12 ++++++++++ .../format/test_intrinsics_pinhole.py | 23 +++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 76e03aa..380df11 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -5,6 +5,8 @@ from dataclasses import dataclass +from raillabel.json_format import JSONIntrinsicsPinhole + @dataclass class IntrinsicsPinhole: @@ -33,6 +35,16 @@ class IntrinsicsPinhole: width_px: int height_px: int + @classmethod + def from_json(cls, json: JSONIntrinsicsPinhole) -> IntrinsicsPinhole: + """Construct an instant of this class from RailLabel JSON data.""" + return IntrinsicsPinhole( + camera_matrix=json.camera_matrix, + distortion=json.distortion_coeffs, + width_px=json.width_px, + height_px=json.height_px, + ) + @classmethod def fromdict(cls, data_dict: dict) -> IntrinsicsPinhole: """Generate a IntrinsicsPinhole object from a dict. diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py index 2f07c4f..fb41360 100644 --- a/tests/test_raillabel/format/test_intrinsics_pinhole.py +++ b/tests/test_raillabel/format/test_intrinsics_pinhole.py @@ -3,15 +3,10 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import IntrinsicsPinhole +from raillabel.json_format import JSONIntrinsicsPinhole # == Fixtures ========================= @@ -26,6 +21,16 @@ def intrinsics_pinhole_dict() -> dict: } +@pytest.fixture +def intrinsics_pinhole_json() -> dict: + return JSONIntrinsicsPinhole( + camera_matrix=[0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], + distortion_coeffs=[0.49, 0.69, 0.31, 0.81, 0.99], + width_px=2464, + height_px=1600, + ) + + @pytest.fixture def intrinsics_pinhole() -> dict: return IntrinsicsPinhole( @@ -39,6 +44,11 @@ def intrinsics_pinhole() -> dict: # == Tests ============================ +def test_from_json(intrinsics_pinhole, intrinsics_pinhole_json): + actual = IntrinsicsPinhole.from_json(intrinsics_pinhole_json) + assert actual == intrinsics_pinhole + + def test_fromdict(): intrinsics_pinhole = IntrinsicsPinhole.fromdict( { @@ -72,5 +82,4 @@ def test_asdict(): if __name__ == "__main__": - os.system("clear") pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From e877c07d87df5baa5a6cea685525313b73353035 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:39:32 +0100 Subject: [PATCH 080/190] docs: move IntrinsicsPinhole field docs below fields --- raillabel/format/intrinsics_pinhole.py | 35 +++++++++++--------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 380df11..6979371 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -10,30 +10,25 @@ @dataclass class IntrinsicsPinhole: - """Intrinsic calibration for a camera sensor. - - Parameters - ---------- - camera_matrix: tuple of float of length 12 - This is a 3x4 camera matrix which projects 3D homogeneous points (4x1) from a camera - coordinate system into the image plane (3x1). This is the usual K matrix for camera - projection as in OpenCV. It is extended from 3x3 to 3x4 to enable its direct utilisation to - project 4x1 homogeneous 3D points. The matrix is defined to follow the camera model: - x-to-right, y-down, z-forward. The following equation applies: - x_img = camera_matrix * X_ccs. - distortion: tuple of float of length 5 to 14 - This is the array 1xN radial and tangential distortion coefficients. - width_px: int - Width of the image frame in pixels. - height_px: int - Height of the image frame in pixels. + """Intrinsic calibration for a camera sensor.""" - """ + camera_matrix: tuple[ + float, float, float, float, float, float, float, float, float, float, float, float + ] + """This is a 3x4 camera matrix which projects 3D homogeneous points (4x1) from a camera + coordinate system into the image plane (3x1). This is the usual K matrix for camera projection as + in OpenCV. It is extended from 3x3 to 3x4 to enable its direct utilisation to project 4x1 + homogeneous 3D points. The matrix is defined to follow the camera model: x-to-right, y-down, + z-forward. The following equation applies: x_img = camera_matrix * X_ccs.""" + + distortion: tuple[float, float, float, float, float] + "This is the array 1x5 radial and tangential distortion coefficients." - camera_matrix: tuple[float, ...] - distortion: tuple[float, ...] width_px: int + "Width of the image frame in pixel." + height_px: int + "Height of the image frame in pixel." @classmethod def from_json(cls, json: JSONIntrinsicsPinhole) -> IntrinsicsPinhole: From 1778aea7aa7888db49680b62b984db1fcf1d0879 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 12:45:46 +0100 Subject: [PATCH 081/190] feat: IntrinsicsRadar.from_json() --- raillabel/format/intrinsics_radar.py | 29 +++++++++++-------- .../format/test_intrinsics_radar.py | 22 +++++++++----- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/raillabel/format/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py index 380d91f..355bfb5 100644 --- a/raillabel/format/intrinsics_radar.py +++ b/raillabel/format/intrinsics_radar.py @@ -5,26 +5,31 @@ from dataclasses import dataclass +from raillabel.json_format import JSONIntrinsicsRadar + @dataclass class IntrinsicsRadar: - """Intrinsic calibration for a radar sensor. + """Intrinsic calibration for a radar sensor.""" - Parameters - ---------- resolution_px_per_m: float - Factor for calculating the 3D coordinates of a pixel in the cartesian radar images. - Number of pixels in the images per meter from the sensor. - width_px: int - Width of the cartesian image frame in pixels. - height_px: int - Height of the cartesian image frame in pixels. + """Factor for calculating the 3D coordinates of a pixel in the cartesian radar images. Number of + pixels in the images per meter from the sensor.""" - """ - - resolution_px_per_m: float width_px: int + "Width of the cartesian image frame in pixel." + height_px: int + "Height of the cartesian image frame in pixel." + + @classmethod + def from_json(cls, json: JSONIntrinsicsRadar) -> IntrinsicsRadar: + """Construct an instant of this class from RailLabel JSON data.""" + return IntrinsicsRadar( + resolution_px_per_m=json.resolution_px_per_m, + width_px=json.width_px, + height_px=json.height_px, + ) @classmethod def fromdict(cls, data_dict: dict) -> IntrinsicsRadar: diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py index a264192..d796a15 100644 --- a/tests/test_raillabel/format/test_intrinsics_radar.py +++ b/tests/test_raillabel/format/test_intrinsics_radar.py @@ -3,15 +3,10 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import IntrinsicsRadar +from raillabel.json_format import JSONIntrinsicsRadar # == Fixtures ========================= @@ -25,6 +20,15 @@ def intrinsics_radar_dict() -> dict: } +@pytest.fixture +def intrinsics_radar_json() -> dict: + return JSONIntrinsicsRadar( + resolution_px_per_m=2.856, + width_px=2856, + height_px=1428, + ) + + @pytest.fixture def intrinsics_radar() -> dict: return IntrinsicsRadar( @@ -37,6 +41,11 @@ def intrinsics_radar() -> dict: # == Tests ============================ +def test_from_json(intrinsics_radar, intrinsics_radar_json): + actual = IntrinsicsRadar.from_json(intrinsics_radar_json) + assert actual == intrinsics_radar + + def test_fromdict(): intrinsics_radar = IntrinsicsRadar.fromdict( { @@ -66,5 +75,4 @@ def test_asdict(): if __name__ == "__main__": - os.system("clear") pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 11bf03085176adbcc177f8ac3e3d7579ba9af784 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 14:16:41 +0100 Subject: [PATCH 082/190] refactor: remove Frame.uid field --- CHANGELOG.md | 1 + raillabel/format/frame.py | 5 ----- raillabel/format/object.py | 8 ++++---- raillabel/format/scene.py | 2 +- tests/test_raillabel/format/test_frame.py | 11 ++--------- tests/test_raillabel/format/test_object.py | 6 +++--- tests/test_raillabel/format/test_scene.py | 8 ++++---- 7 files changed, 15 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73b05..41db548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,3 +108,4 @@ Other breaking changes: - the `fromdict()` and `asdict()` methods in `raillabel.format` classes have been replaced with `from_json()` and `to_json` respectively - `raillabel.format.Transform` fields have been changed by `pos -> position` and `quad -> quaternion` to make it more explicit - `raillabel.format.FrameInterval` fields have been changed by `frame_start -> start` and `frame_end -> end` to make it more concise +- `raillabel.format.Frame.uid` field has been removed to avoid redundant information diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index bb4a618..0dcd722 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -20,8 +20,6 @@ class Frame: Parameters ---------- - uid: int - Number of the frame within the annotation file. Must be unique. timestamp: decimal.Decimal, optional Timestamp containing the Unix epoch time of the frame with up to nanosecond precision. sensors: dict of raillabel.format.SensorReference, optional @@ -42,7 +40,6 @@ class Frame: """ - uid: int timestamp: decimal.Decimal | None = None sensors: dict[str, SensorReference] = field(default_factory=dict) frame_data: dict[str, Num] = field(default_factory=dict) @@ -71,7 +68,6 @@ def object_data(self) -> dict[str, dict[str, type[_ObjectAnnotation]]]: @classmethod def fromdict( cls, - uid: str, data_dict: dict, objects: dict[str, Object], sensors: dict[str, Sensor], @@ -96,7 +92,6 @@ def fromdict( """ return Frame( - uid=int(uid), timestamp=cls._timestamp_fromdict(data_dict), sensors=cls._sensors_fromdict(data_dict, sensors), frame_data=cls._frame_data_fromdict(data_dict, sensors), diff --git a/raillabel/format/object.py b/raillabel/format/object.py index 06dfa13..9301ce0 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -98,7 +98,7 @@ def frame_intervals(self, frames: dict[int, Frame]) -> list[FrameInterval]: """ frame_uids_containing_object = [ - frame.uid for frame in frames.values() if self._is_object_in_frame(frame) + frame_uid for frame_uid, frame in frames.items() if self._is_object_in_frame(frame) ] return FrameInterval.from_frame_uids(frame_uids_containing_object) @@ -150,11 +150,11 @@ def _filtered_annotations(self, frame: Frame) -> list[t.Any]: def _collect_pointer_ids_per_frame(self, frames: dict[int, Frame]) -> dict[int, set[str]]: pointer_ids_per_frame: dict[int, set[str]] = {} - for frame in frames.values(): - pointer_ids_per_frame[frame.uid] = set() + for frame_uid, frame in frames.items(): + pointer_ids_per_frame[frame_uid] = set() for annotation in self._filtered_annotations(frame): - pointer_ids_per_frame[frame.uid].add(annotation.name) # type: ignore + pointer_ids_per_frame[frame_uid].add(annotation.name) # type: ignore return pointer_ids_per_frame diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 170f5ea..abe5bf4 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -173,7 +173,7 @@ def _frames_fromdict( ) -> dict[int, Frame]: frames = {} for frame_uid, frame_dict in frames_dict.items(): - frames[int(frame_uid)] = Frame.fromdict(frame_uid, frame_dict, objects, sensors) + frames[int(frame_uid)] = Frame.fromdict(frame_dict, objects, sensors) return frames diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index 904d69b..c644794 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -42,7 +42,6 @@ def frame_dict( @pytest.fixture def frame(sensor_reference_camera, num, all_annotations) -> dict: return Frame( - uid=0, timestamp=Decimal("1632321743.100000072"), sensors={sensor_reference_camera.sensor.uid: sensor_reference_camera}, frame_data={num.name: num}, @@ -55,7 +54,6 @@ def frame(sensor_reference_camera, num, all_annotations) -> dict: def test_fromdict_sensors(sensor_reference_camera_dict, sensor_reference_camera, sensor_camera): frame = Frame.fromdict( - uid=0, data_dict={ "frame_properties": { "timestamp": "1632321743.100000072", @@ -66,14 +64,12 @@ def test_fromdict_sensors(sensor_reference_camera_dict, sensor_reference_camera, objects={}, ) - assert frame.uid == 0 assert frame.timestamp == Decimal("1632321743.100000072") assert frame.sensors == {sensor_reference_camera.sensor.uid: sensor_reference_camera} def test_fromdict_frame_data(num, num_dict, sensor_camera): frame = Frame.fromdict( - uid=1, data_dict={"frame_properties": {"frame_data": {"num": [num_dict]}}}, sensors={sensor_camera.uid: sensor_camera}, objects={}, @@ -91,7 +87,6 @@ def test_fromdict_annotations( all_annotations, ): frame = Frame.fromdict( - uid=2, data_dict={ "objects": { object_person.uid: object_data_person_dict, @@ -113,7 +108,6 @@ def test_asdict_sensors( sensor_reference_camera, ): frame = Frame( - uid=0, timestamp=Decimal("1632321743.100000072"), sensors={sensor_reference_camera.sensor.uid: sensor_reference_camera}, ) @@ -127,7 +121,7 @@ def test_asdict_sensors( def test_asdict_frame_data(num, num_dict): - frame = Frame(uid=0, frame_data={num.name: num}) + frame = Frame(frame_data={num.name: num}) assert frame.asdict() == {"frame_properties": {"frame_data": {"num": [num_dict]}}} @@ -135,7 +129,7 @@ def test_asdict_frame_data(num, num_dict): def test_asdict_object_data( object_data_person_dict, object_person, object_data_train_dict, object_train, all_annotations ): - frame = Frame(uid=0, annotations=all_annotations) + frame = Frame(annotations=all_annotations) assert frame.asdict() == { "objects": { @@ -147,7 +141,6 @@ def test_asdict_object_data( def test_object_data(object_person, object_train, bbox, cuboid, poly2d, poly3d, seg3d, bbox_train): frame = Frame( - uid=2, annotations={ bbox.uid: bbox, poly2d.uid: poly2d, diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index a3657a3..482f49a 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -8,7 +8,7 @@ import sys import typing as t from pathlib import Path -from uuid import UUID, uuid4 +from uuid import uuid4 import pytest @@ -405,11 +405,11 @@ def build_annotation(name: str, object: Object, attributes: dict = {}) -> t.Unio def build_frame(uid: int, raw_object_data: dict[Object, list[t.Union[Bbox, Cuboid]]]) -> Frame: annotations = {} - for object, object_data in raw_object_data.items(): + for object_data in raw_object_data.values(): for annotation in object_data: annotations[annotation.uid] = annotation - return Frame(uid=uid, annotations=annotations) + return Frame(annotations=annotations) def build_object(type: str) -> Object: diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index e342dbe..124b55d 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -194,7 +194,7 @@ def test_fromdict_frames( "coordinate_systems": coordinate_systems_dict, "objects": objects_dict, "frames": { - str(frame.uid): frame_dict, + "0": frame_dict, }, "frame_intervals": [ { @@ -208,7 +208,7 @@ def test_fromdict_frames( ) assert scene.frames == { - frame.uid: frame, + 0: frame, } @@ -300,7 +300,7 @@ def test_asdict_frames( sensors=sensors, objects=objects, frames={ - frame.uid: frame, + "0": frame, }, ) @@ -311,7 +311,7 @@ def test_asdict_frames( "coordinate_systems": coordinate_systems_dict, "objects": objects_dict, "frames": { - str(frame.uid): frame_dict, + "0": frame_dict, }, "frame_intervals": [ { From 2f45b00dca093d1cd218a0c3984f3daabf55be90 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 14:41:59 +0100 Subject: [PATCH 083/190] feat: Point3d.from_json() --- raillabel/format/point3d.py | 23 ++++++++++----------- raillabel/format/transform.py | 2 +- tests/test_raillabel/format/test_point3d.py | 5 +++++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index e9afcf8..f916a6f 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -8,22 +8,21 @@ @dataclass class Point3d: - """A point in the 3D space. - - Parameters - ---------- - x: float or int - The x-coordinate of the point. - y: float or int - The y-coordinate of the point. - z: float or int - The z-coordinate of the point. - - """ + """A point in the 3D space.""" x: float + "The x-coordinate of the point." + y: float + "The y-coordinate of the point." + z: float + "The z-coordinate of the point." + + @classmethod + def from_json(cls, json: tuple[float, float, float]) -> Point3d: + """Construct an instant of this class from RailLabel JSON data.""" + return Point3d(x=json[0], y=json[1], z=json[2]) @classmethod def fromdict(cls, data_dict: dict) -> Point3d: diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 65f1a12..22c2cc3 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -25,7 +25,7 @@ class Transform: def from_json(cls, json: JSONTransformData) -> Transform: """Construct an instant of this class from RailLabel JSON data.""" return Transform( - position=Point3d(x=json.translation[0], y=json.translation[1], z=json.translation[2]), + position=Point3d.from_json(json.translation), quaternion=Quaternion( x=json.quaternion[0], y=json.quaternion[1], diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index faac51a..238f226 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -39,6 +39,11 @@ def point3d_another() -> dict: # == Tests ============================ +def test_from_json(point3d, point3d_dict): + actual = Point3d.from_json(point3d_dict) + assert actual == point3d + + def test_fromdict(): point3d = Point3d.fromdict([419, 3.14, 0]) From cd3d7b78261326d36c7ca72da65dc76b44e02a88 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 14:46:50 +0100 Subject: [PATCH 084/190] feat: Quaternion.from_json() --- raillabel/format/quaternion.py | 27 +++++++++---------- raillabel/format/transform.py | 7 +---- .../test_raillabel/format/test_quaternion.py | 5 ++++ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index ecff8d0..366747a 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -8,25 +8,24 @@ @dataclass class Quaternion: - """A quaternion. - - Parameters - ---------- - x: float or int - The x component of the quaternion. - y: float or int - The y component of the quaternion. - z: float or int - The z component of the quaternion. - w: float or int - The w component of the quaternion. - - """ + """A rotation represented by a quaternion.""" x: float + "The x component of the quaternion." + y: float + "The y component of the quaternion." + z: float + "The z component of the quaternion." + w: float + "The omega component of the quaternion." + + @classmethod + def from_json(cls, json: tuple[float, float, float, float]) -> Quaternion: + """Construct an instant of this class from RailLabel JSON data.""" + return Quaternion(x=json[0], y=json[1], z=json[2], w=json[3]) @classmethod def fromdict(cls, data_dict: dict) -> Quaternion: diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 22c2cc3..c8cd55a 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -26,12 +26,7 @@ def from_json(cls, json: JSONTransformData) -> Transform: """Construct an instant of this class from RailLabel JSON data.""" return Transform( position=Point3d.from_json(json.translation), - quaternion=Quaternion( - x=json.quaternion[0], - y=json.quaternion[1], - z=json.quaternion[2], - w=json.quaternion[3], - ), + quaternion=Quaternion.from_json(json.quaternion), ) @classmethod diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py index 8291881..d508e85 100644 --- a/tests/test_raillabel/format/test_quaternion.py +++ b/tests/test_raillabel/format/test_quaternion.py @@ -29,6 +29,11 @@ def quaternion() -> dict: # == Tests ============================ +def test_from_json(quaternion, quaternion_dict): + actual = Quaternion.from_json(quaternion_dict) + assert actual == quaternion + + def test_fromdict(): quaternion = Quaternion.fromdict([0.75318325, -0.10270147, 0.21430262, -0.61338551]) From ea9e925da047b81ec97d35098a3d628eb957d8f1 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 14:56:39 +0100 Subject: [PATCH 085/190] feat: Point2d.from_json() --- raillabel/format/point2d.py | 19 +++++++++---------- tests/test_raillabel/format/test_point2d.py | 5 +++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index edeb043..5b079e7 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -8,19 +8,18 @@ @dataclass class Point2d: - """A 2d point in an image. + """A 2d point in an image.""" - Parameters - ---------- - x: float or int - The x-coordinate of the point in the image. - y: float or int - The y-coordinate of the point in the image. + x: int + "The x-coordinate of the point in the image in pixels." - """ + y: int + "The y-coordinate of the point in the image in pixels." - x: float - y: float + @classmethod + def from_json(cls, json: tuple[int, int]) -> Point2d: + """Construct an instant of this class from RailLabel JSON data.""" + return Point2d(x=json[0], y=json[1]) @classmethod def fromdict(cls, data_dict: dict) -> Point2d: diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 54c2ffd..4617251 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -39,6 +39,11 @@ def point2d_another() -> dict: # == Tests ============================ +def test_from_json(point2d, point2d_dict): + actual = Point2d.from_json(point2d_dict) + assert actual == point2d + + def test_fromdict(): point2d = Point2d.fromdict([1.5, 222]) From dab645153bc2d4deb0057e1f25cea5a10ab505ca Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 14:58:17 +0100 Subject: [PATCH 086/190] docs: add float to Point field types --- raillabel/format/point2d.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 5b079e7..afd7e1b 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -10,14 +10,14 @@ class Point2d: """A 2d point in an image.""" - x: int + x: int | float "The x-coordinate of the point in the image in pixels." - y: int + y: int | float "The y-coordinate of the point in the image in pixels." @classmethod - def from_json(cls, json: tuple[int, int]) -> Point2d: + def from_json(cls, json: tuple[int | float, int | float]) -> Point2d: """Construct an instant of this class from RailLabel JSON data.""" return Point2d(x=json[0], y=json[1]) From 6ae10396dcd83505c5909f7d856e582da7c45c26 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 15:57:04 +0100 Subject: [PATCH 087/190] feat: Size2d.from_json() --- raillabel/format/point2d.py | 4 ++-- raillabel/format/size2d.py | 19 +++++++++---------- tests/test_raillabel/format/test_point2d.py | 9 +-------- tests/test_raillabel/format/test_size2d.py | 14 ++++++-------- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index afd7e1b..07566b0 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -11,10 +11,10 @@ class Point2d: """A 2d point in an image.""" x: int | float - "The x-coordinate of the point in the image in pixels." + "The x-coordinate of the point in the image in pixels from the left." y: int | float - "The y-coordinate of the point in the image in pixels." + "The y-coordinate of the point in the image in pixels from the top." @classmethod def from_json(cls, json: tuple[int | float, int | float]) -> Point2d: diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index dab987c..01df3ed 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -8,19 +8,18 @@ @dataclass class Size2d: - """The size of a rectangle in a 2d image. + """The size of a rectangle in a 2d image.""" - Parameters - ---------- - x: float or int - The size along the x-axis. - y: float or int - The size along the y-axis. + x: int | float + "The size along the x-axis." - """ + y: int | float + "The size along the y-axis." - x: float - y: float + @classmethod + def from_json(cls, json: tuple[int | float, int | float]) -> Size2d: + """Construct an instant of this class from RailLabel JSON data.""" + return Size2d(x=json[0], y=json[1]) @classmethod def fromdict(cls, data_dict: dict) -> Size2d: diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 4617251..b72ee4c 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -3,14 +3,8 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import Point2d # == Fixtures ========================= @@ -61,5 +55,4 @@ def test_asdict(): if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/test_raillabel/format/test_size2d.py index 04f1f76..65ffd4f 100644 --- a/tests/test_raillabel/format/test_size2d.py +++ b/tests/test_raillabel/format/test_size2d.py @@ -3,14 +3,8 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import Size2d # == Fixtures ========================= @@ -29,6 +23,11 @@ def size2d() -> dict: # == Tests ============================ +def test_from_json(size2d, size2d_dict): + actual = Size2d.from_json(size2d_dict) + assert actual == size2d + + def test_fromdict(): size2d = Size2d.fromdict([25, 1.344]) @@ -46,5 +45,4 @@ def test_asdict(): if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) From fee3aff682030b158ac55753a2a73701f2b2a9d1 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 16:25:47 +0100 Subject: [PATCH 088/190] refactor: delete everything related to fromdict and asdict --- raillabel/format/_object_annotation.py | 18 +- raillabel/format/bbox.py | 56 --- raillabel/format/cuboid.py | 76 ---- raillabel/format/element_data_pointer.py | 33 -- raillabel/format/frame.py | 160 +------ raillabel/format/frame_interval.py | 34 -- raillabel/format/intrinsics_pinhole.py | 43 -- raillabel/format/intrinsics_radar.py | 41 -- raillabel/format/metadata.py | 64 --- raillabel/format/num.py | 49 -- raillabel/format/object.py | 197 -------- raillabel/format/point2d.py | 19 - raillabel/format/point3d.py | 20 - raillabel/format/poly2d.py | 63 --- raillabel/format/poly3d.py | 61 --- raillabel/format/quaternion.py | 21 - raillabel/format/scene.py | 178 -------- raillabel/format/seg3d.py | 50 --- raillabel/format/sensor.py | 121 ----- raillabel/format/sensor_reference.py | 51 --- raillabel/format/size2d.py | 19 - raillabel/format/size3d.py | 20 - raillabel/format/transform.py | 34 -- raillabel/load/load.py | 10 +- raillabel/save/save.py | 12 +- tests/test_raillabel/format/conftest.py | 66 +-- .../test_raillabel/format/test_attributes.py | 126 ------ tests/test_raillabel/format/test_bbox.py | 147 ------ tests/test_raillabel/format/test_cuboid.py | 118 ----- .../format/test_element_data_pointer.py | 105 ----- tests/test_raillabel/format/test_frame.py | 168 ------- .../format/test_frame_interval.py | 29 -- .../format/test_intrinsics_pinhole.py | 42 -- .../format/test_intrinsics_radar.py | 37 -- tests/test_raillabel/format/test_metadata.py | 129 ------ tests/test_raillabel/format/test_num.py | 77 ---- tests/test_raillabel/format/test_object.py | 423 ------------------ .../format/test_object_annotation.py | 55 --- .../test_raillabel/format/test_object_data.py | 41 -- tests/test_raillabel/format/test_point2d.py | 32 +- tests/test_raillabel/format/test_point3d.py | 43 +- tests/test_raillabel/format/test_poly2d.py | 120 ----- tests/test_raillabel/format/test_poly3d.py | 112 ----- .../test_raillabel/format/test_quaternion.py | 30 +- tests/test_raillabel/format/test_scene.py | 362 --------------- tests/test_raillabel/format/test_seg3d.py | 97 ---- tests/test_raillabel/format/test_sensor.py | 277 ------------ .../format/test_sensor_reference.py | 65 --- tests/test_raillabel/format/test_size2d.py | 22 +- tests/test_raillabel/format/test_size3d.py | 48 -- tests/test_raillabel/format/test_transform.py | 22 +- tests/test_raillabel/load/test_load.py | 21 - tests/test_raillabel/save/test_save.py | 85 ---- 53 files changed, 30 insertions(+), 4319 deletions(-) delete mode 100644 tests/test_raillabel/format/test_attributes.py delete mode 100644 tests/test_raillabel/format/test_bbox.py delete mode 100644 tests/test_raillabel/format/test_cuboid.py delete mode 100644 tests/test_raillabel/format/test_element_data_pointer.py delete mode 100644 tests/test_raillabel/format/test_frame.py delete mode 100644 tests/test_raillabel/format/test_metadata.py delete mode 100644 tests/test_raillabel/format/test_num.py delete mode 100644 tests/test_raillabel/format/test_object.py delete mode 100644 tests/test_raillabel/format/test_object_annotation.py delete mode 100644 tests/test_raillabel/format/test_object_data.py delete mode 100644 tests/test_raillabel/format/test_poly2d.py delete mode 100644 tests/test_raillabel/format/test_poly3d.py delete mode 100644 tests/test_raillabel/format/test_scene.py delete mode 100644 tests/test_raillabel/format/test_seg3d.py delete mode 100644 tests/test_raillabel/format/test_sensor.py delete mode 100644 tests/test_raillabel/format/test_sensor_reference.py delete mode 100644 tests/test_raillabel/format/test_size3d.py delete mode 100644 tests/test_raillabel/load/test_load.py delete mode 100644 tests/test_raillabel/save/test_save.py diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py index 4487cf2..c9d09d4 100644 --- a/raillabel/format/_object_annotation.py +++ b/raillabel/format/_object_annotation.py @@ -3,7 +3,7 @@ from __future__ import annotations -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractproperty from dataclasses import dataclass from importlib import import_module from inspect import isclass @@ -32,22 +32,6 @@ def name(self) -> str: def OPENLABEL_ID(self) -> list[str] | str: raise NotImplementedError - # === Public Methods ===================================================== - - @abstractmethod - def asdict(self) -> dict: - raise NotImplementedError - - @classmethod - @abstractmethod - def fromdict( - cls, - data_dict: dict, - sensors: dict, - object: Object, - ) -> type[_ObjectAnnotation]: - raise NotImplementedError - # === Private Methods ==================================================== def _annotation_required_fields_asdict(self) -> dict: diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 67406e8..6e99e1e 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation -from .object import Object from .point2d import Point2d from .size2d import Size2d @@ -42,58 +41,3 @@ class Bbox(_ObjectAnnotation): size: Size2d OPENLABEL_ID = "bbox" - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Bbox: - """Generate a Bbox object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - object: raillabel.format.Object - Object this annotation belongs to. - - Returns - ------- - annotation: Bbox - Converted annotation. - - """ - return Bbox( - uid=str(data_dict["uid"]), - pos=Point2d(x=data_dict["val"][0], y=data_dict["val"][1]), - size=Size2d(x=data_dict["val"][2], y=data_dict["val"][3]), - object=object, - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - attributes=cls._attributes_fromdict(data_dict), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr = self._annotation_required_fields_asdict() - - dict_repr["val"] = [ - float(self.pos.x), - float(self.pos.y), - float(self.size.x), - float(self.size.y), - ] - - dict_repr.update(self._annotation_optional_fields_asdict()) - - return dict_repr diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 36bcf2d..ec70005 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation -from .object import Object from .point3d import Point3d from .quaternion import Quaternion from .size3d import Size3d @@ -47,78 +46,3 @@ class Cuboid(_ObjectAnnotation): size: Size3d OPENLABEL_ID = "cuboid" - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Cuboid: - """Generate a Cuboid object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - object: raillabel.format.Object - Object this annotation belongs to. - - Returns - ------- - annotation: Cuboid - Converted annotation. - - """ - return Cuboid( - uid=str(data_dict["uid"]), - pos=Point3d( - x=data_dict["val"][0], - y=data_dict["val"][1], - z=data_dict["val"][2], - ), - quat=Quaternion( - x=data_dict["val"][3], - y=data_dict["val"][4], - z=data_dict["val"][5], - w=data_dict["val"][6], - ), - size=Size3d( - x=data_dict["val"][7], - y=data_dict["val"][8], - z=data_dict["val"][9], - ), - object=object, - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - attributes=cls._attributes_fromdict(data_dict), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr = self._annotation_required_fields_asdict() - - dict_repr["val"] = [ - float(self.pos.x), - float(self.pos.y), - float(self.pos.z), - float(self.quat.x), - float(self.quat.y), - float(self.quat.z), - float(self.quat.w), - float(self.size.x), - float(self.size.y), - float(self.size.z), - ] - - dict_repr.update(self._annotation_optional_fields_asdict()) - - return dict_repr diff --git a/raillabel/format/element_data_pointer.py b/raillabel/format/element_data_pointer.py index f2c3e07..d3f23fc 100644 --- a/raillabel/format/element_data_pointer.py +++ b/raillabel/format/element_data_pointer.py @@ -34,36 +34,3 @@ class ElementDataPointer: uid: str frame_intervals: list[FrameInterval] attribute_pointers: dict[str, AttributeType] - - @property - def annotation_type(self) -> str: - """Return type of annotation e.g. bbox, cuboid.""" - return self.uid.split("__")[1] - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "type": self.annotation_type, - "frame_intervals": self._frame_intervals_asdict(), - "attribute_pointers": self._attribute_pointers_asdict(), - } - - def _frame_intervals_asdict(self) -> list[dict[str, int]]: - return [fi.asdict() for fi in self.frame_intervals] - - def _attribute_pointers_asdict(self) -> dict[str, str]: - return { - attr_name: attr_type.value for attr_name, attr_type in self.attribute_pointers.items() - } diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 0dcd722..a8ac366 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -4,13 +4,10 @@ from __future__ import annotations import decimal -import typing as t from dataclasses import dataclass, field -from ._object_annotation import _ObjectAnnotation, annotation_classes +from ._object_annotation import _ObjectAnnotation from .num import Num -from .object import Object -from .sensor import Sensor from .sensor_reference import SensorReference @@ -64,158 +61,3 @@ def object_data(self) -> dict[str, dict[str, type[_ObjectAnnotation]]]: object_data[annotation.object.uid][ann_id] = annotation return object_data - - @classmethod - def fromdict( - cls, - data_dict: dict, - objects: dict[str, Object], - sensors: dict[str, Sensor], - ) -> Frame: - """Generate a Frame object from a dict. - - Parameters - ---------- - uid: str - Unique identifier of the frame. - data_dict: dict - RailLabel format snippet containing the relevant data. - objects: dict - Dictionary of all objects in the scene. - sensors: dict - Dictionary of all sensors in the scene. - - Returns - ------- - frame: raillabel.format.Frame - Converted Frame object. - - """ - return Frame( - timestamp=cls._timestamp_fromdict(data_dict), - sensors=cls._sensors_fromdict(data_dict, sensors), - frame_data=cls._frame_data_fromdict(data_dict, sensors), - annotations=cls._objects_fromdict(data_dict, objects, sensors), - ) - - def asdict(self) -> dict[str, t.Any]: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr: dict[str, t.Any] = {} - - if self.timestamp is not None or self.sensors != {} or self.frame_data != {}: - dict_repr["frame_properties"] = {} - - if self.timestamp is not None: - dict_repr["frame_properties"]["timestamp"] = str(self.timestamp) - - if self.sensors != {}: - dict_repr["frame_properties"]["streams"] = { - str(k): v.asdict() for k, v in self.sensors.items() - } - - if self.frame_data != {}: - dict_repr["frame_properties"]["frame_data"] = { - "num": [v.asdict() for v in self.frame_data.values()] - } - - if self.annotations != {}: - dict_repr["objects"] = self._annotations_asdict() - - return dict_repr - - @classmethod - def _timestamp_fromdict(cls, data_dict: dict) -> decimal.Decimal | None: - if "frame_properties" not in data_dict or "timestamp" not in data_dict["frame_properties"]: - return None - - return decimal.Decimal(data_dict["frame_properties"]["timestamp"]) - - @classmethod - def _sensors_fromdict( - cls, data_dict: dict, scene_sensors: dict[str, Sensor] - ) -> dict[str, SensorReference]: - if "frame_properties" not in data_dict or "streams" not in data_dict["frame_properties"]: - return {} - - sensors = {} - - for sensor_id, sensor_dict in data_dict["frame_properties"]["streams"].items(): - sensors[sensor_id] = SensorReference.fromdict( - data_dict=sensor_dict, sensor=scene_sensors[sensor_id] - ) - - return sensors - - @classmethod - def _frame_data_fromdict(cls, data_dict: dict, sensors: dict[str, Sensor]) -> dict[str, Num]: - if "frame_properties" not in data_dict or "frame_data" not in data_dict["frame_properties"]: - return {} - - frame_data = {} - for ann_type in data_dict["frame_properties"]["frame_data"]: - for ann_raw in data_dict["frame_properties"]["frame_data"][ann_type]: - frame_data[ann_raw["name"]] = Num.fromdict(ann_raw, sensors) - - return frame_data - - @classmethod - def _objects_fromdict( - cls, - data_dict: dict, - objects: dict[str, Object], - sensors: dict[str, Sensor], - ) -> dict[str, type[_ObjectAnnotation]]: - if "objects" not in data_dict: - return {} - - annotations = {} - - for obj_id, obj_ann in data_dict["objects"].items(): - object_annotations = cls._object_annotations_fromdict( - data_dict=obj_ann["object_data"], - object=objects[obj_id], - sensors=sensors, - ) - - for annotation in object_annotations: - annotations[annotation.uid] = annotation - - return annotations - - @classmethod - def _object_annotations_fromdict( - cls, - data_dict: dict, - object: Object, - sensors: dict[str, Sensor], - ) -> t.Iterator[type[_ObjectAnnotation]]: - for ann_type, annotations_raw in data_dict.items(): - for ann_raw in annotations_raw: - yield annotation_classes()[ann_type].fromdict(ann_raw, sensors, object) - - def _annotations_asdict(self) -> dict[str, t.Any]: - annotations_dict: dict[str, t.Any] = {} - for object_id, annotations_ in self.object_data.items(): - annotations_dict[object_id] = {"object_data": {}} - - for annotation in annotations_.values(): - if annotation.OPENLABEL_ID not in annotations_dict[object_id]["object_data"]: - annotations_dict[object_id]["object_data"][annotation.OPENLABEL_ID] = [] - - annotations_dict[object_id]["object_data"][annotation.OPENLABEL_ID].append( - annotation.asdict() # type: ignore - ) - - return annotations_dict diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 8780785..33231a0 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -26,21 +26,6 @@ def from_json(cls, json: JSONFrameInterval) -> FrameInterval: end=json.frame_end, ) - @classmethod - def fromdict(cls, data_dict: dict) -> FrameInterval: - """Generate a FrameInterval object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return FrameInterval( - start=data_dict["frame_start"], - end=data_dict["frame_end"], - ) - @classmethod def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: """Convert a list of frame uids into FrameIntervals. @@ -63,25 +48,6 @@ def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: FrameInterval(start=interval[0], end=interval[-1]) for interval in frame_uid_intervals ] - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "frame_start": int(self.start), - "frame_end": int(self.end), - } - def __len__(self) -> int: """Return the length in frames.""" return abs(self.start - self.end) + 1 diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 6979371..357bd4e 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -39,46 +39,3 @@ def from_json(cls, json: JSONIntrinsicsPinhole) -> IntrinsicsPinhole: width_px=json.width_px, height_px=json.height_px, ) - - @classmethod - def fromdict(cls, data_dict: dict) -> IntrinsicsPinhole: - """Generate a IntrinsicsPinhole object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - Returns - ------- - raillabel.format.IntrinsicsPinhole - Converted IntrinsicsPinhole object. - - """ - return IntrinsicsPinhole( - camera_matrix=tuple(data_dict["camera_matrix"]), - distortion=tuple(data_dict["distortion_coeffs"]), - width_px=data_dict["width_px"], - height_px=data_dict["height_px"], - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "camera_matrix": list(self.camera_matrix), - "distortion_coeffs": list(self.distortion), - "width_px": int(self.width_px), - "height_px": int(self.height_px), - } diff --git a/raillabel/format/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py index 355bfb5..04ba839 100644 --- a/raillabel/format/intrinsics_radar.py +++ b/raillabel/format/intrinsics_radar.py @@ -30,44 +30,3 @@ def from_json(cls, json: JSONIntrinsicsRadar) -> IntrinsicsRadar: width_px=json.width_px, height_px=json.height_px, ) - - @classmethod - def fromdict(cls, data_dict: dict) -> IntrinsicsRadar: - """Generate a IntrinsicsRadar object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - Returns - ------- - raillabel.format.IntrinsicsRadar - Converted IntrinsicsRadar object. - - """ - return IntrinsicsRadar( - resolution_px_per_m=data_dict["resolution_px_per_m"], - width_px=data_dict["width_px"], - height_px=data_dict["height_px"], - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "resolution_px_per_m": float(self.resolution_px_per_m), - "width_px": int(self.width_px), - "height_px": int(self.height_px), - } diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index b863b97..a27e46e 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -4,7 +4,6 @@ from __future__ import annotations from dataclasses import dataclass -from importlib import metadata as importlib_metadata @dataclass @@ -60,66 +59,3 @@ class Metadata: name: str | None = None subschema_version: str | None = None tagged_file: str | None = None - - @classmethod - def fromdict(cls, data_dict: dict, subschema_version: str | None = None) -> Metadata: - """Generate a Metadata object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. Additional (non-defined) - arguments can be set and will be added as properties to Metadata. - subschema_version: str, optional - Version of the RailLabel subschema - - Returns - ------- - metadata: Metadata - Converted metadata. - - """ - metadata = Metadata( - schema_version=data_dict["schema_version"], - subschema_version=subschema_version, - exporter_version=cls._collect_exporter_version(), - ) - - return cls._set_additional_attributes(metadata, data_dict) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - """ - return self._remove_empty_fields(vars(self)) - - @classmethod - def _collect_exporter_version(cls) -> str | None: - try: - exporter_version = importlib_metadata.version("raillabel") - except importlib_metadata.PackageNotFoundError: - return None - - version_number_length = len(exporter_version) - len(exporter_version.split(".")[-1]) - return exporter_version[: version_number_length - 1] - - @classmethod - def _set_additional_attributes(cls, metadata: Metadata, data_dict: dict) -> Metadata: - preset_keys = ["schema_version", "subschema_version", "exporter_version"] - - for key, value in data_dict.items(): - if key in preset_keys: - continue - - setattr(metadata, key, value) - - return metadata - - def _remove_empty_fields(self, dict_repr: dict) -> dict: - """Remove empty fields from a dictionary.""" - return {k: v for k, v in dict_repr.items() if v is not None} diff --git a/raillabel/format/num.py b/raillabel/format/num.py index 94c538f..243c65b 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -32,52 +32,3 @@ class Num: name: str val: int | float sensor: Sensor - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict) -> Num: - """Generate a Num object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - - Returns - ------- - annotation: Num - Converted annotation. - - """ - return Num( - uid=str(data_dict["uid"]), - name=str(data_dict["name"]), - val=data_dict["val"], - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "uid": str(self.uid), - "name": str(self.name), - "val": self.val, - "coordinate_system": str(self.sensor.uid), - } - - @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor: - return sensors[data_dict["coordinate_system"]] diff --git a/raillabel/format/object.py b/raillabel/format/object.py index 9301ce0..9faa0c3 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -3,15 +3,8 @@ from __future__ import annotations -import typing as t from dataclasses import dataclass -from .element_data_pointer import AttributeType, ElementDataPointer -from .frame_interval import FrameInterval - -if t.TYPE_CHECKING: - from .frame import Frame - @dataclass class Object: @@ -32,193 +25,3 @@ class Object: uid: str name: str type: str - - # --- Public Methods -------------- - - @classmethod - def fromdict(cls, data_dict: dict, object_uid: str) -> Object: - """Generate a Object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - object_uid: str - Unique identifier of the object. - - Returns - ------- - object: raillabel.format.Object - Converted object. - - """ - return Object(uid=object_uid, type=data_dict["type"], name=data_dict["name"]) - - def asdict(self, frames: dict[int, Frame] | None = None) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - frames: dict, optional - The dictionary of frames stored under Scene.frames used for the frame intervals and - object data pointers. If None, these are not provided. Default is None. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - if frames is None: - return {"name": str(self.name), "type": str(self.type)} - - return { - "name": str(self.name), - "type": str(self.type), - "frame_intervals": self._frame_intervals_asdict(self.frame_intervals(frames)), - "object_data_pointers": self._object_data_pointers_asdict( - self.object_data_pointers(frames) - ), - } - - def frame_intervals(self, frames: dict[int, Frame]) -> list[FrameInterval]: - """Return frame intervals in which this object is present. - - Parameters - ---------- - frames: dict[int, raillabel.format.Frame] - The dictionary of frames stored under Scene.frames. - - Returns - ------- - list[FrameInterval] - List of the FrameIntervals, where this object is contained. - - """ - frame_uids_containing_object = [ - frame_uid for frame_uid, frame in frames.items() if self._is_object_in_frame(frame) - ] - - return FrameInterval.from_frame_uids(frame_uids_containing_object) - - def object_data_pointers(self, frames: dict[int, Frame]) -> dict[str, ElementDataPointer]: - """Create object data pointers used in WebLABEL visualization. - - Parameters - ---------- - frames: dict[int, raillabel.format.Frame] - The dictionary of frames stored under Scene.frames. - - Returns - ------- - dict[str, ElementDataPointer] - ObjectDataPointers dict as required by WebLABEL. Keys are the ObjectDataPointer uids. - - """ - pointer_ids_per_frame = self._collect_pointer_ids_per_frame(frames) - frame_uids_per_pointer_id = self._reverse_frame_pointer_ids(pointer_ids_per_frame) - frame_intervals_per_pointer_id = self._convert_to_intervals(frame_uids_per_pointer_id) - - attributes_per_pointer_id = self._collect_attributes_per_pointer_id(frames) - attribute_pointers_per_pointer_id = self._convert_to_attribute_pointers( - attributes_per_pointer_id - ) - - return self._create_object_data_pointers( - frame_intervals_per_pointer_id, attribute_pointers_per_pointer_id - ) - - # --- Private Methods ------------- - - def _frame_intervals_asdict( - self, frame_intervals: list[FrameInterval] - ) -> list[dict[str, t.Any]]: - return [fi.asdict() for fi in frame_intervals] - - def _object_data_pointers_asdict( - self, object_data_pointers: dict[str, ElementDataPointer] - ) -> dict: - return {pointer_id: pointer.asdict() for pointer_id, pointer in object_data_pointers.items()} - - def _is_object_in_frame(self, frame: Frame) -> bool: - return self.uid in frame.object_data - - def _filtered_annotations(self, frame: Frame) -> list[t.Any]: - return [ann for ann in frame.annotations.values() if ann.object.uid == self.uid] - - def _collect_pointer_ids_per_frame(self, frames: dict[int, Frame]) -> dict[int, set[str]]: - pointer_ids_per_frame: dict[int, set[str]] = {} - for frame_uid, frame in frames.items(): - pointer_ids_per_frame[frame_uid] = set() - - for annotation in self._filtered_annotations(frame): - pointer_ids_per_frame[frame_uid].add(annotation.name) # type: ignore - - return pointer_ids_per_frame - - def _reverse_frame_pointer_ids( - self, pointer_ids_per_frame: dict[int, set[str]] - ) -> dict[str, set[int]]: - frame_uids_per_pointer_id: dict[str, set[int]] = {} - for frame_uid, pointer_ids in pointer_ids_per_frame.items(): - for pointer_id in pointer_ids: - if pointer_id not in frame_uids_per_pointer_id: - frame_uids_per_pointer_id[pointer_id] = set() - - frame_uids_per_pointer_id[pointer_id].add(frame_uid) - - return frame_uids_per_pointer_id - - def _convert_to_intervals( - self, frame_uids_per_pointer_id: dict[str, set[int]] - ) -> dict[str, list[FrameInterval]]: - frame_intervals = {} - for pointer_id, frame_uids in frame_uids_per_pointer_id.items(): - frame_intervals[pointer_id] = FrameInterval.from_frame_uids(list(frame_uids)) - - return frame_intervals - - def _collect_attributes_per_pointer_id( - self, frames: dict[int, Frame] - ) -> dict[str, dict[str, t.Any]]: - attributes_per_pointer_id: dict[str, dict[str, t.Any]] = {} - for frame in frames.values(): - for annotation in self._filtered_annotations(frame): - if annotation.name not in attributes_per_pointer_id: # type: ignore - attributes_per_pointer_id[annotation.name] = {} # type: ignore - - attributes_per_pointer_id[annotation.name].update(annotation.attributes) # type: ignore - - return attributes_per_pointer_id - - def _convert_to_attribute_pointers( - self, attributes_per_pointer_id: dict[str, dict[str, t.Any]] - ) -> dict[str, dict[str, AttributeType]]: - for attributes in attributes_per_pointer_id.values(): - for attribute_name, attribute_value in attributes.items(): - attributes[attribute_name] = AttributeType.from_value(type(attribute_value)) - - return attributes_per_pointer_id - - def _create_object_data_pointers( - self, - frame_intervals_per_pointer_id: dict[str, list[FrameInterval]], - attribute_pointers_per_pointer_id: dict[str, dict[str, AttributeType]], - ) -> dict[str, ElementDataPointer]: - object_data_pointers = {} - for pointer_id in frame_intervals_per_pointer_id: - object_data_pointers[pointer_id] = ElementDataPointer( - uid=pointer_id, - frame_intervals=frame_intervals_per_pointer_id[pointer_id], - attribute_pointers=attribute_pointers_per_pointer_id[pointer_id], - ) - - return object_data_pointers - - # --- Special Methods ------------- - - def __hash__(self) -> int: - """Return hash.""" - return self.uid.__hash__() diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 07566b0..e1934ba 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -20,22 +20,3 @@ class Point2d: def from_json(cls, json: tuple[int | float, int | float]) -> Point2d: """Construct an instant of this class from RailLabel JSON data.""" return Point2d(x=json[0], y=json[1]) - - @classmethod - def fromdict(cls, data_dict: dict) -> Point2d: - """Generate a Point2d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Point2d( - x=data_dict[0], - y=data_dict[1], - ) - - def asdict(self) -> list[float]: - """Export self as a dict compatible with the OpenLABEL schema.""" - return [float(self.x), float(self.y)] diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index f916a6f..9fcabf1 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -23,23 +23,3 @@ class Point3d: def from_json(cls, json: tuple[float, float, float]) -> Point3d: """Construct an instant of this class from RailLabel JSON data.""" return Point3d(x=json[0], y=json[1], z=json[2]) - - @classmethod - def fromdict(cls, data_dict: dict) -> Point3d: - """Generate a Point3d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Point3d( - x=data_dict[0], - y=data_dict[1], - z=data_dict[2], - ) - - def asdict(self) -> list[float]: - """Export self as a dict compatible with the OpenLABEL schema.""" - return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 1952714..703ccf8 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation -from .object import Object from .point2d import Point2d @@ -50,65 +49,3 @@ class Poly2d(_ObjectAnnotation): mode: str = "MODE_POLY2D_ABSOLUTE" OPENLABEL_ID = "poly2d" - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Poly2d: - """Generate a Poly2d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - object: raillabel.format.Object - Object this annotation belongs to. - - Returns - ------- - annotation: Poly2d - Converted annotation. - - """ - return Poly2d( - uid=str(data_dict["uid"]), - closed=data_dict["closed"], - mode=data_dict["mode"], - points=cls._points_fromdict(data_dict), - object=object, - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - attributes=cls._attributes_fromdict(data_dict), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr = self._annotation_required_fields_asdict() - - dict_repr["closed"] = bool(self.closed) - dict_repr["val"] = [] - dict_repr["mode"] = self.mode - for point in self.points: - dict_repr["val"].extend(point.asdict()) - - dict_repr.update(self._annotation_optional_fields_asdict()) - - return dict_repr - - @classmethod - def _points_fromdict(cls, data_dict: dict) -> list[Point2d]: - return [ - Point2d(x=data_dict["val"][i], y=data_dict["val"][i + 1]) - for i in range(0, len(data_dict["val"]), 2) - ] diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index d993adc..23cc8d7 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation -from .object import Object from .point3d import Point3d @@ -42,63 +41,3 @@ class Poly3d(_ObjectAnnotation): closed: bool OPENLABEL_ID = "poly3d" - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Poly3d: - """Generate a Poly3d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - object: raillabel.format.Object - Object this annotation belongs to. - - Returns - ------- - annotation: Poly3d - Converted annotation. - - """ - return Poly3d( - uid=str(data_dict["uid"]), - closed=data_dict["closed"], - points=cls._points_fromdict(data_dict), - object=object, - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - attributes=cls._attributes_fromdict(data_dict), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr = self._annotation_required_fields_asdict() - - dict_repr["closed"] = bool(self.closed) - dict_repr["val"] = [] - for point in self.points: - dict_repr["val"].extend(point.asdict()) - - dict_repr.update(self._annotation_optional_fields_asdict()) - - return dict_repr - - @classmethod - def _points_fromdict(cls, data_dict: dict) -> list[Point3d]: - return [ - Point3d(x=data_dict["val"][i], y=data_dict["val"][i + 1], z=data_dict["val"][i + 2]) - for i in range(0, len(data_dict["val"]), 3) - ] diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index 366747a..d325cba 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -26,24 +26,3 @@ class Quaternion: def from_json(cls, json: tuple[float, float, float, float]) -> Quaternion: """Construct an instant of this class from RailLabel JSON data.""" return Quaternion(x=json[0], y=json[1], z=json[2], w=json[3]) - - @classmethod - def fromdict(cls, data_dict: dict) -> Quaternion: - """Generate a Quaternion object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Quaternion( - x=data_dict[0], - y=data_dict[1], - z=data_dict[2], - w=data_dict[3], - ) - - def asdict(self) -> list[float]: - """Export self as a dict compatible with the OpenLABEL schema.""" - return [float(self.x), float(self.y), float(self.z), float(self.w)] diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index abe5bf4..574351c 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -4,7 +4,6 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any from .frame import Frame from .frame_interval import FrameInterval @@ -44,180 +43,3 @@ class Scene: def frame_intervals(self) -> list[FrameInterval]: """Return frame intervals of the present frames.""" return FrameInterval.from_frame_uids(list(self.frames.keys())) - - # === Public Methods ========================================================================== - - @classmethod - def fromdict(cls, data_dict: dict, subschema_version: str | None = None) -> Scene: - """Generate a Scene object from a RailLABEL-dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - subschema_version: str, optional - Version of the RailLabel subschema. - - Returns - ------- - raillabel.format.Scene - Converted Scene object. - - Raises - ------ - raillabel.exceptions.MissingCoordinateSystemError - if a stream has no corresponding coordinate system. - raillabel.exceptions.MissingStreamError - if a coordinate system has no corresponding stream. - raillabel.exceptions.UnsupportedParentError - if a coordinate system has no corresponding stream. - - """ - data_dict = cls._prepare_data(data_dict) - - sensors = cls._sensors_fromdict(data_dict["streams"], data_dict["coordinate_systems"]) - objects = cls._objects_fromdict(data_dict["objects"]) - - return Scene( - metadata=Metadata.fromdict(data_dict["metadata"], subschema_version), - sensors=sensors, - objects=objects, - frames=cls._frames_fromdict(data_dict["frames"], sensors, objects), - ) - - def asdict(self, calculate_pointers: bool = True) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this Scene. - calculate_pointers: bool, optional - If True, object_data_pointers and Object frame_intervals will be calculated. Default - is True. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "openlabel": _clean_dict( - { - "metadata": self.metadata.asdict(), - "streams": self._streams_asdict(self.sensors), - "coordinate_systems": self._coordinate_systems_asdict(self.sensors), - "objects": self._objects_asdict(self.objects, calculate_pointers), - "frames": self._frames_asdict(self.frames), - "frame_intervals": self._frame_intervals_asdict(self.frame_intervals), - } - ) - } - - # === Private Methods ========================================================================= - - # --- fromdict() ---------------------------- - - @classmethod - def _prepare_data(cls, data: dict) -> dict: - """Add optional fields to dict to simplify interaction. - - Parameters - ---------- - data : dict - JSON data. - - Returns - ------- - dict - Enhanced JSON data. - - """ - if "coordinate_systems" not in data["openlabel"]: - data["openlabel"]["coordinate_systems"] = {} - - if "streams" not in data["openlabel"]: - data["openlabel"]["streams"] = {} - - if "objects" not in data["openlabel"]: - data["openlabel"]["objects"] = {} - - if "frames" not in data["openlabel"]: - data["openlabel"]["frames"] = {} - - return data["openlabel"] - - @classmethod - def _sensors_fromdict( - cls, streams_dict: dict, coordinate_systems_dict: dict - ) -> dict[str, Sensor]: - sensors = {} - - for stream_id in streams_dict: - sensors[stream_id] = Sensor.fromdict( - uid=stream_id, - cs_data_dict=coordinate_systems_dict[stream_id], - stream_data_dict=streams_dict[stream_id], - ) - - return sensors - - @classmethod - def _objects_fromdict(cls, object_dict: dict) -> dict[str, Object]: - return {uid: Object.fromdict(object_, uid) for uid, object_ in object_dict.items()} - - @classmethod - def _frames_fromdict( - cls, frames_dict: dict, sensors: dict[str, Sensor], objects: dict[str, Object] - ) -> dict[int, Frame]: - frames = {} - for frame_uid, frame_dict in frames_dict.items(): - frames[int(frame_uid)] = Frame.fromdict(frame_dict, objects, sensors) - - return frames - - # --- asdict() ------------------------------ - - def _streams_asdict(self, sensors: dict[str, Sensor]) -> dict: - return {uid: sensor.asdict()["stream"] for uid, sensor in sensors.items()} - - def _coordinate_systems_asdict(self, sensors: dict[str, Sensor]) -> dict[str, Any] | None: - if len(sensors) == 0: - return None - - coordinate_systems: dict[str, Any] = { - "base": {"type": "local", "parent": "", "children": []} - } - - for uid, sensor in sensors.items(): - coordinate_systems[uid] = sensor.asdict()["coordinate_system"] - coordinate_systems["base"]["children"].append(uid) - - return coordinate_systems - - def _objects_asdict(self, objects: dict[str, Object], calculate_pointers: bool) -> dict: - if calculate_pointers: - return {str(uid): object_.asdict(self.frames) for uid, object_ in objects.items()} - return {str(uid): object_.asdict() for uid, object_ in objects.items()} - - def _frames_asdict(self, frames: dict[int, Frame]) -> dict: - return {str(uid): frame.asdict() for uid, frame in frames.items()} - - def _frame_intervals_asdict(self, frame_intervals: list[FrameInterval]) -> list[dict]: - return [fi.asdict() for fi in frame_intervals] - - -def _clean_dict(input_dict: dict) -> dict: - """Remove all fields from a dict that are None or have a length of 0.""" - empty_keys = [] - for key, value in input_dict.items(): - is_field_empty = value is None or (hasattr(value, "__len__") and len(value) == 0) - - if is_field_empty: - empty_keys.append(key) - - for key in empty_keys: - del input_dict[key] - - return input_dict diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 369976f..e43166b 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from ._object_annotation import _ObjectAnnotation -from .object import Object @dataclass @@ -37,52 +36,3 @@ class Seg3d(_ObjectAnnotation): point_ids: list[int] OPENLABEL_ID = "vec" - - @classmethod - def fromdict(cls, data_dict: dict, sensors: dict, object: Object) -> Seg3d: - """Generate a Seg3d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensors: dict - Dictionary containing all sensors for the scene. - object: raillabel.format.Object - Object this annotation belongs to. - - Returns - ------- - annotation: Seg3d - Converted annotation. - - """ - return Seg3d( - uid=str(data_dict["uid"]), - point_ids=data_dict["val"], - object=object, - sensor=cls._coordinate_system_fromdict(data_dict, sensors), - attributes=cls._attributes_fromdict(data_dict), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr = self._annotation_required_fields_asdict() - - dict_repr["val"] = [int(pid) for pid in self.point_ids] - - dict_repr.update(self._annotation_optional_fields_asdict()) - - return dict_repr diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py index 6bdf5c9..f922cd1 100644 --- a/raillabel/format/sensor.py +++ b/raillabel/format/sensor.py @@ -5,12 +5,9 @@ from dataclasses import dataclass from enum import Enum -from typing import Any from .intrinsics_pinhole import IntrinsicsPinhole from .intrinsics_radar import IntrinsicsRadar -from .point3d import Point3d -from .quaternion import Quaternion from .transform import Transform @@ -48,124 +45,6 @@ class Sensor: uri: str | None = None description: str | None = None - @classmethod - def fromdict(cls, uid: str, cs_data_dict: dict, stream_data_dict: dict) -> Sensor: - """Generate a Sensor object from a dict. - - Parameters - ---------- - uid: str - Unique identifier of the sensor. - cs_data_dict: dict - RailLabel format dict containing the data about the coordinate system. - stream_data_dict: dict - RailLabel format dict containing the data about the stream. - - Returns - ------- - sensor: raillabel.format.Sensor - Converted Sensor object. - - """ - return Sensor( - uid=uid, - extrinsics=cls._extrinsics_fromdict(cs_data_dict), - intrinsics=cls._intrinsics_fromdict( - stream_data_dict, cls._type_fromdict(stream_data_dict) - ), - type=cls._type_fromdict(stream_data_dict), - uri=stream_data_dict.get("uri"), - description=stream_data_dict.get("description"), - ) - - def asdict(self) -> dict: - """Export self as a dict compatible with the RailLabel schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - """ - return { - "coordinate_system": self._as_coordinate_system_dict(), - "stream": self._as_stream_dict(), - } - - def _as_coordinate_system_dict(self) -> dict[str, Any]: - coordinate_system_repr: dict[str, Any] = {"type": "sensor", "parent": "base"} - - if self.extrinsics is not None: - coordinate_system_repr["pose_wrt_parent"] = self.extrinsics.asdict() - - return coordinate_system_repr - - def _as_stream_dict(self) -> dict[str, Any]: - stream_repr: dict[str, Any] = {} - - if self.type is not None: - stream_repr["type"] = str(self.type.value) - - if self.uri is not None: - stream_repr["uri"] = str(self.uri) - - if self.description is not None: - stream_repr["description"] = str(self.description) - - if isinstance(self.intrinsics, IntrinsicsPinhole): - stream_repr["stream_properties"] = {"intrinsics_pinhole": self.intrinsics.asdict()} - - elif isinstance(self.intrinsics, IntrinsicsRadar): - stream_repr["stream_properties"] = {"intrinsics_radar": self.intrinsics.asdict()} - - return stream_repr - - @classmethod - def _extrinsics_fromdict(cls, data_dict: dict) -> Transform | None: - if "pose_wrt_parent" not in data_dict: - return None - - return Transform( - position=Point3d( - x=data_dict["pose_wrt_parent"]["translation"][0], - y=data_dict["pose_wrt_parent"]["translation"][1], - z=data_dict["pose_wrt_parent"]["translation"][2], - ), - quaternion=Quaternion( - x=data_dict["pose_wrt_parent"]["quaternion"][0], - y=data_dict["pose_wrt_parent"]["quaternion"][1], - z=data_dict["pose_wrt_parent"]["quaternion"][2], - w=data_dict["pose_wrt_parent"]["quaternion"][3], - ), - ) - - @classmethod - def _intrinsics_fromdict( - cls, data_dict: dict, sensor_type: SensorType | None - ) -> IntrinsicsPinhole | IntrinsicsRadar | None: - if "stream_properties" not in data_dict: - return None - - if sensor_type == SensorType.CAMERA: - if "intrinsics_pinhole" in data_dict["stream_properties"]: - return IntrinsicsPinhole.fromdict( - data_dict["stream_properties"]["intrinsics_pinhole"] - ) - - elif ( - sensor_type == SensorType.RADAR and "intrinsics_radar" in data_dict["stream_properties"] - ): - return IntrinsicsRadar.fromdict(data_dict["stream_properties"]["intrinsics_radar"]) - - return None - - @classmethod - def _type_fromdict(cls, data_dict: dict) -> SensorType | None: - if "type" not in data_dict: - return None - - return SensorType(data_dict["type"]) - class SensorType(Enum): """Enumeration representing all possible sensor types.""" diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index fa4d1ca..5255832 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -5,7 +5,6 @@ import decimal from dataclasses import dataclass -from typing import Any from .sensor import Sensor @@ -30,53 +29,3 @@ class SensorReference: sensor: Sensor timestamp: decimal.Decimal uri: str | None = None - - @classmethod - def fromdict(cls, data_dict: dict, sensor: Sensor) -> SensorReference: - """Generate a SensorReference object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - sensor: raillabel.format.Sensor - Sensor corresponding to this SensorReference. - - Returns - ------- - sensor_reference: raillabel.format.SensorReference - Converted SensorReference object. - - """ - return SensorReference( - sensor=sensor, - timestamp=cls._timestamp_fromdict(data_dict["stream_properties"]), - uri=data_dict.get("uri"), - ) - - def asdict(self) -> dict[str, Any]: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - dict_repr: dict[str, Any] = { - "stream_properties": {"sync": {"timestamp": str(self.timestamp)}} - } - - if self.uri is not None: - dict_repr["uri"] = self.uri - - return dict_repr - - @classmethod - def _timestamp_fromdict(cls, data_dict: dict) -> decimal.Decimal: - return decimal.Decimal(data_dict["sync"]["timestamp"]) diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index 01df3ed..7ddf07f 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -20,22 +20,3 @@ class Size2d: def from_json(cls, json: tuple[int | float, int | float]) -> Size2d: """Construct an instant of this class from RailLabel JSON data.""" return Size2d(x=json[0], y=json[1]) - - @classmethod - def fromdict(cls, data_dict: dict) -> Size2d: - """Generate a Size2d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Size2d( - x=data_dict[0], - y=data_dict[1], - ) - - def asdict(self) -> list[float]: - """Export self as a dict compatible with the OpenLABEL schema.""" - return [float(self.x), float(self.y)] diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index f98a35b..5da9679 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -24,23 +24,3 @@ class Size3d: x: float y: float z: float - - @classmethod - def fromdict(cls, data_dict: dict) -> Size3d: - """Generate a Size3d object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Size3d( - x=data_dict[0], - y=data_dict[1], - z=data_dict[2], - ) - - def asdict(self) -> list[float]: - """Export self as a dict compatible with the OpenLABEL schema.""" - return [float(self.x), float(self.y), float(self.z)] diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index c8cd55a..68fb971 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -28,37 +28,3 @@ def from_json(cls, json: JSONTransformData) -> Transform: position=Point3d.from_json(json.translation), quaternion=Quaternion.from_json(json.quaternion), ) - - @classmethod - def fromdict(cls, data_dict: dict) -> Transform: - """Generate a Transform object from a dict. - - Parameters - ---------- - data_dict: dict - RailLabel format snippet containing the relevant data. - - """ - return Transform( - position=Point3d.fromdict(data_dict["translation"]), - quaternion=Quaternion.fromdict(data_dict["quaternion"]), - ) - - def asdict(self) -> dict[str, list[float]]: - """Export self as a dict compatible with the OpenLABEL schema. - - Returns - ------- - dict_repr: dict - Dict representation of this class instance. - - Raises - ------ - ValueError - if an attribute can not be converted to the type required by the OpenLabel schema. - - """ - return { - "translation": self.position.asdict(), - "quaternion": self.quaternion.asdict(), - } diff --git a/raillabel/load/load.py b/raillabel/load/load.py index f45105d..b59a4d8 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -3,13 +3,12 @@ from __future__ import annotations -import json from pathlib import Path -from raillabel.format import Scene +from raillabel.format import Metadata, Scene -def load(path: Path | str) -> Scene: +def load(_path: Path | str) -> Scene: """Load an annotation file of any supported type. Parameters @@ -23,7 +22,4 @@ def load(path: Path | str) -> Scene: Scene with the loaded data. """ - with Path(path).open() as scene_file: - raw_scene = json.load(scene_file) - - return Scene.fromdict(raw_scene) + return Scene(metadata=Metadata("1.0.0")) diff --git a/raillabel/save/save.py b/raillabel/save/save.py index 3adfef5..94714a9 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -3,13 +3,12 @@ from __future__ import annotations -import json from pathlib import Path from raillabel.format import Scene -def save(scene: Scene, path: Path | str, prettify_json: bool = False) -> None: +def save(_scene: Scene, _path: Path | str, _prettify_json: bool = False) -> None: """Save a raillabel.Scene in a JSON file. Parameters @@ -25,12 +24,3 @@ def save(scene: Scene, path: Path | str, prettify_json: bool = False) -> None: also the file size. Default is False. """ - path = Path(path) - - data = scene.asdict() - - with Path(path).open("w") as save_file: - if prettify_json: - json.dump(data, save_file, indent=4) - else: - json.dump(data, save_file) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index bc4a860..f5a7eec 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -1,60 +1,10 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 - -from .test_attributes import ( - attributes_multiple_types, - attributes_multiple_types_dict, - attributes_single_type, - attributes_single_type_dict, -) -from .test_bbox import bbox, bbox_dict, bbox_train, bbox_train_dict -from .test_cuboid import cuboid, cuboid_dict -from .test_element_data_pointer import ( - element_data_pointer_full, - element_data_pointer_full_dict, - element_data_pointer_minimal, - element_data_pointer_minimal_dict, -) -from .test_frame import frame, frame_dict -from .test_frame_interval import frame_interval, frame_interval_dict -from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_dict -from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_dict -from .test_metadata import ( - metadata_full, - metadata_full_dict, - metadata_minimal, - metadata_minimal_dict, -) -from .test_num import num, num_dict -from .test_object import ( - object_person, - object_person_dict, - object_train, - object_train_dict, - objects, - objects_dict, -) -from .test_object_annotation import all_annotations -from .test_object_data import object_data_person_dict, object_data_train_dict -from .test_point2d import point2d, point2d_another, point2d_another_dict, point2d_dict -from .test_point3d import point3d, point3d_another, point3d_another_dict, point3d_dict -from .test_poly2d import poly2d, poly2d_dict -from .test_poly3d import poly3d, poly3d_dict -from .test_quaternion import quaternion, quaternion_dict -from .test_scene import scene, scene_dict -from .test_seg3d import seg3d, seg3d_dict -from .test_sensor import ( - coordinate_systems_dict, - sensor_camera, - sensor_camera_dict, - sensor_lidar, - sensor_lidar_dict, - sensor_radar, - sensor_radar_dict, - sensors, - streams_dict, -) -from .test_sensor_reference import sensor_reference_camera, sensor_reference_camera_dict -from .test_size2d import size2d, size2d_dict -from .test_size3d import size3d, size3d_dict -from .test_transform import transform, transform_dict +from .test_frame_interval import frame_interval, frame_interval_json +from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json +from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json +from .test_point2d import point2d, point2d_json +from .test_point3d import point3d, point3d_json +from .test_quaternion import quaternion, quaternion_json +from .test_size2d import size2d, size2d_json +from .test_transform import transform, transform_json diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/test_raillabel/format/test_attributes.py deleted file mode 100644 index b72fac8..0000000 --- a/tests/test_raillabel/format/test_attributes.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import _ObjectAnnotation - -# == Fixtures ========================= - - -@pytest.fixture -def attributes_single_type_dict() -> dict: - return { - "text": [ - {"name": "test_text_attr0", "val": "test_text_attr0_val"}, - {"name": "test_text_attr1", "val": "test_text_attr1_val"}, - ] - } - - -@pytest.fixture -def attributes_single_type() -> dict: - return { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - } - - -@pytest.fixture -def attributes_multiple_types_dict() -> dict: - return { - "text": [{"name": "text_attr", "val": "text_val"}], - "num": [{"name": "num_attr", "val": 0}], - "boolean": [{"name": "bool_attr", "val": True}], - "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], - } - - -@pytest.fixture -def attributes_multiple_types() -> dict: - return { - "text_attr": "text_val", - "num_attr": 0, - "bool_attr": True, - "vec_attr": [0, 1, 2], - } - - -# == Tests ============================ - - -def test_fromdict__single_type(): - attributes_dict = { - "attributes": { - "text": [ - {"name": "test_text_attr0", "val": "test_text_attr0_val"}, - {"name": "test_text_attr1", "val": "test_text_attr1_val"}, - ] - } - } - - assert _ObjectAnnotation._attributes_fromdict(attributes_dict) == { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - } - - -def test_fromdict__multiple_types(): - attributes_dict = { - "attributes": { - "text": [{"name": "text_attr", "val": "text_val"}], - "num": [{"name": "num_attr", "val": 0}], - "boolean": [{"name": "bool_attr", "val": True}], - "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], - } - } - - assert _ObjectAnnotation._attributes_fromdict(attributes_dict) == { - "text_attr": "text_val", - "num_attr": 0, - "bool_attr": True, - "vec_attr": [0, 1, 2], - } - - -def test_asdict__single_type(): - attributes = { - "test_text_attr0": "test_text_attr0_val", - "test_text_attr1": "test_text_attr1_val", - } - - assert _ObjectAnnotation._attributes_asdict(None, attributes) == { - "text": [ - {"name": "test_text_attr0", "val": "test_text_attr0_val"}, - {"name": "test_text_attr1", "val": "test_text_attr1_val"}, - ] - } - - -def test_asdict__multiple_types(): - attributes = { - "text_attr": "text_val", - "num_attr": 0, - "bool_attr": True, - "vec_attr": [0, 1, 2], - } - - assert _ObjectAnnotation._attributes_asdict(None, attributes) == { - "text": [{"name": "text_attr", "val": "text_val"}], - "num": [{"name": "num_attr", "val": 0}], - "boolean": [{"name": "bool_attr", "val": True}], - "vec": [{"name": "vec_attr", "val": [0, 1, 2]}], - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py deleted file mode 100644 index 040df09..0000000 --- a/tests/test_raillabel/format/test_bbox.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Bbox - -# == Fixtures ========================= - - -@pytest.fixture -def bbox_dict( - sensor_camera, - attributes_multiple_types_dict, - point2d_dict, - size2d_dict, -) -> dict: - return { - "uid": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "name": "rgb_middle__bbox__person", - "val": point2d_dict + size2d_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - } - - -@pytest.fixture -def bbox( - point2d, - size2d, - sensor_camera, - attributes_multiple_types, - object_person, -) -> dict: - return Bbox( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", - pos=point2d, - size=size2d, - sensor=sensor_camera, - attributes=attributes_multiple_types, - object=object_person, - ) - - -@pytest.fixture -def bbox_train_dict(sensor_camera, attributes_single_type_dict, point2d_dict, size2d_dict) -> dict: - return { - "uid": "6a7cfdb7-149d-4987-98dd-79d05a8cc8e6", - "name": "rgb_middle__bbox__train", - "val": point2d_dict + size2d_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_single_type_dict, - } - - -@pytest.fixture -def bbox_train( - point2d, - size2d, - sensor_camera, - attributes_single_type, - object_train, -) -> dict: - return Bbox( - uid="6a7cfdb7-149d-4987-98dd-79d05a8cc8e6", - pos=point2d, - size=size2d, - sensor=sensor_camera, - attributes=attributes_single_type, - object=object_train, - ) - - -# == Tests ============================ - - -def test_fromdict( - point2d, - point2d_dict, - size2d, - size2d_dict, - sensor_camera, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - bbox = Bbox.fromdict( - { - "uid": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "name": "rgb_middle__bbox__person", - "val": point2d_dict + size2d_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - }, - sensors, - object_person, - ) - - assert bbox.uid == "78f0ad89-2750-4a30-9d66-44c9da73a714" - assert bbox.name == "rgb_middle__bbox__person" - assert bbox.pos == point2d - assert bbox.size == size2d - assert bbox.object == object_person - assert bbox.sensor == sensor_camera - assert bbox.attributes == attributes_multiple_types - - -def test_asdict( - point2d, - point2d_dict, - size2d, - size2d_dict, - sensor_camera, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - bbox = Bbox( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", - pos=point2d, - size=size2d, - object=object_person, - sensor=sensor_camera, - attributes=attributes_multiple_types, - ) - - assert bbox.asdict() == { - "uid": "78f0ad89-2750-4a30-9d66-44c9da73a714", - "name": "rgb_middle__bbox__person", - "val": point2d_dict + size2d_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py deleted file mode 100644 index 96af51d..0000000 --- a/tests/test_raillabel/format/test_cuboid.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Cuboid - -# == Fixtures ========================= - - -@pytest.fixture -def cuboid_dict( - sensor_lidar, attributes_multiple_types_dict, point3d_dict, size3d_dict, quaternion_dict -) -> dict: - return { - "uid": "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", - "name": "lidar__cuboid__person", - "val": point3d_dict + quaternion_dict + size3d_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - } - - -@pytest.fixture -def cuboid( - point3d, size3d, quaternion, sensor_lidar, attributes_multiple_types, object_person -) -> dict: - return Cuboid( - uid="2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", - pos=point3d, - quat=quaternion, - size=size3d, - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - ) - - -# == Tests ============================ - - -def test_fromdict( - point3d, - point3d_dict, - size3d, - size3d_dict, - quaternion, - quaternion_dict, - sensor_lidar, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - cuboid = Cuboid.fromdict( - { - "uid": "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", - "name": "lidar__cuboid__person", - "val": point3d_dict + quaternion_dict + size3d_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - }, - sensors, - object_person, - ) - - assert cuboid.uid == "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad" - assert cuboid.name == "lidar__cuboid__person" - assert cuboid.pos == point3d - assert cuboid.quat == quaternion - assert cuboid.size == size3d - assert cuboid.object == object_person - assert cuboid.sensor == sensor_lidar - assert cuboid.attributes == attributes_multiple_types - - -def test_asdict( - point3d, - point3d_dict, - size3d, - size3d_dict, - quaternion, - quaternion_dict, - sensor_lidar, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - cuboid = Cuboid( - uid="2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", - pos=point3d, - quat=quaternion, - size=size3d, - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - ) - - assert cuboid.asdict() == { - "uid": "2c6b3de0-86c2-4684-b576-4cfd4f50d6ad", - "name": "lidar__cuboid__person", - "val": point3d_dict + quaternion_dict + size3d_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_element_data_pointer.py b/tests/test_raillabel/format/test_element_data_pointer.py deleted file mode 100644 index 1611ea6..0000000 --- a/tests/test_raillabel/format/test_element_data_pointer.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format._attribute_type import AttributeType -from raillabel.format import ElementDataPointer - -# == Fixtures ========================= - - -@pytest.fixture -def element_data_pointer_minimal_dict() -> dict: - return { - "type": "bbox", - "frame_intervals": [], - "attribute_pointers": {}, - } - - -@pytest.fixture -def element_data_pointer_minimal(): - return ElementDataPointer( - uid="rgb_middle__bbox__person", frame_intervals=[], attribute_pointers={} - ) - - -@pytest.fixture -def element_data_pointer_full_dict(frame_interval_dict) -> dict: - return { - "type": "bbox", - "frame_intervals": [frame_interval_dict], - "attribute_pointers": { - "text_attr": "text", - "num_attr": "num", - "bool_attr": "boolean", - "vec_attr": "vec", - }, - } - - -@pytest.fixture -def element_data_pointer_full(sensor_camera, object_person, frame_interval): - return ElementDataPointer( - uid="rgb_middle__bbox__person", - frame_intervals=[frame_interval], - attribute_pointers={ - "text_attr": AttributeType.TEXT, - "num_attr": AttributeType.NUM, - "bool_attr": AttributeType.BOOLEAN, - "vec_attr": AttributeType.VEC, - }, - ) - - -# == Tests ============================ - - -def test_asdict_minimal(sensor_camera, object_person): - element_data_pointer = ElementDataPointer( - uid="rgb_middle__bbox__person", frame_intervals=[], attribute_pointers={} - ) - - assert element_data_pointer.asdict() == { - "type": "bbox", - "frame_intervals": [], - "attribute_pointers": {}, - } - - -def test_asdict_full(sensor_camera, object_person, frame_interval, frame_interval_dict): - element_data_pointer = ElementDataPointer( - uid="rgb_middle__bbox__person", - frame_intervals=[frame_interval], - attribute_pointers={ - "text_attr": AttributeType.TEXT, - "num_attr": AttributeType.NUM, - "bool_attr": AttributeType.BOOLEAN, - "vec_attr": AttributeType.VEC, - }, - ) - - assert element_data_pointer.asdict() == { - "type": "bbox", - "frame_intervals": [frame_interval_dict], - "attribute_pointers": { - "text_attr": "text", - "num_attr": "num", - "bool_attr": "boolean", - "vec_attr": "vec", - }, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py deleted file mode 100644 index c644794..0000000 --- a/tests/test_raillabel/format/test_frame.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from decimal import Decimal -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Frame - -# == Fixtures ========================= - - -@pytest.fixture -def frame_dict( - sensor_reference_camera_dict, - num_dict, - object_person, - object_data_person_dict, - object_train, - object_data_train_dict, -) -> dict: - return { - "frame_properties": { - "timestamp": "1632321743.100000072", - "streams": {"rgb_middle": sensor_reference_camera_dict}, - "frame_data": {"num": [num_dict]}, - }, - "objects": { - object_person.uid: object_data_person_dict, - object_train.uid: object_data_train_dict, - }, - } - - -@pytest.fixture -def frame(sensor_reference_camera, num, all_annotations) -> dict: - return Frame( - timestamp=Decimal("1632321743.100000072"), - sensors={sensor_reference_camera.sensor.uid: sensor_reference_camera}, - frame_data={num.name: num}, - annotations=all_annotations, - ) - - -# == Tests ============================ - - -def test_fromdict_sensors(sensor_reference_camera_dict, sensor_reference_camera, sensor_camera): - frame = Frame.fromdict( - data_dict={ - "frame_properties": { - "timestamp": "1632321743.100000072", - "streams": {"rgb_middle": sensor_reference_camera_dict}, - } - }, - sensors={sensor_camera.uid: sensor_camera}, - objects={}, - ) - - assert frame.timestamp == Decimal("1632321743.100000072") - assert frame.sensors == {sensor_reference_camera.sensor.uid: sensor_reference_camera} - - -def test_fromdict_frame_data(num, num_dict, sensor_camera): - frame = Frame.fromdict( - data_dict={"frame_properties": {"frame_data": {"num": [num_dict]}}}, - sensors={sensor_camera.uid: sensor_camera}, - objects={}, - ) - - assert frame.frame_data == {num.name: num} - - -def test_fromdict_annotations( - object_data_person_dict, - object_person, - object_data_train_dict, - object_train, - sensors, - all_annotations, -): - frame = Frame.fromdict( - data_dict={ - "objects": { - object_person.uid: object_data_person_dict, - object_train.uid: object_data_train_dict, - } - }, - sensors=sensors, - objects={ - object_person.uid: object_person, - object_train.uid: object_train, - }, - ) - - assert frame.annotations == all_annotations - - -def test_asdict_sensors( - sensor_reference_camera_dict, - sensor_reference_camera, -): - frame = Frame( - timestamp=Decimal("1632321743.100000072"), - sensors={sensor_reference_camera.sensor.uid: sensor_reference_camera}, - ) - - assert frame.asdict() == { - "frame_properties": { - "timestamp": "1632321743.100000072", - "streams": {"rgb_middle": sensor_reference_camera_dict}, - } - } - - -def test_asdict_frame_data(num, num_dict): - frame = Frame(frame_data={num.name: num}) - - assert frame.asdict() == {"frame_properties": {"frame_data": {"num": [num_dict]}}} - - -def test_asdict_object_data( - object_data_person_dict, object_person, object_data_train_dict, object_train, all_annotations -): - frame = Frame(annotations=all_annotations) - - assert frame.asdict() == { - "objects": { - object_person.uid: object_data_person_dict, - object_train.uid: object_data_train_dict, - } - } - - -def test_object_data(object_person, object_train, bbox, cuboid, poly2d, poly3d, seg3d, bbox_train): - frame = Frame( - annotations={ - bbox.uid: bbox, - poly2d.uid: poly2d, - cuboid.uid: cuboid, - poly3d.uid: poly3d, - seg3d.uid: seg3d, - bbox_train.uid: bbox_train, - }, - ) - - assert frame.object_data == { - object_person.uid: { - bbox.uid: bbox, - poly2d.uid: poly2d, - cuboid.uid: cuboid, - poly3d.uid: poly3d, - seg3d.uid: seg3d, - }, - object_train.uid: {bbox_train.uid: bbox_train}, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index 86ad148..c55b9cd 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -11,11 +11,6 @@ # == Fixtures ========================= -@pytest.fixture -def frame_interval_dict() -> dict: - return {"frame_start": 12, "frame_end": 16} - - @pytest.fixture def frame_interval_json() -> JSONFrameInterval: return JSONFrameInterval(frame_start=12, frame_end=16) @@ -37,30 +32,6 @@ def test_from_json(frame_interval, frame_interval_json): assert actual == frame_interval -def test_fromdict(): - frame_interval = FrameInterval.fromdict( - { - "frame_start": 12, - "frame_end": 16, - } - ) - - assert frame_interval.start == 12 - assert frame_interval.end == 16 - - -def test_asdict(): - frame_interval = FrameInterval( - start=12, - end=16, - ) - - assert frame_interval.asdict() == { - "frame_start": 12, - "frame_end": 16, - } - - def test_len(): frame_interval = FrameInterval( start=12, diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py index fb41360..852ce2b 100644 --- a/tests/test_raillabel/format/test_intrinsics_pinhole.py +++ b/tests/test_raillabel/format/test_intrinsics_pinhole.py @@ -11,16 +11,6 @@ # == Fixtures ========================= -@pytest.fixture -def intrinsics_pinhole_dict() -> dict: - return { - "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], - "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], - "width_px": 2464, - "height_px": 1600, - } - - @pytest.fixture def intrinsics_pinhole_json() -> dict: return JSONIntrinsicsPinhole( @@ -49,37 +39,5 @@ def test_from_json(intrinsics_pinhole, intrinsics_pinhole_json): assert actual == intrinsics_pinhole -def test_fromdict(): - intrinsics_pinhole = IntrinsicsPinhole.fromdict( - { - "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], - "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], - "width_px": 2464, - "height_px": 1600, - } - ) - - assert intrinsics_pinhole.camera_matrix == (0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0) - assert intrinsics_pinhole.distortion == (0.49, 0.69, 0.31, 0.81, 0.99) - assert intrinsics_pinhole.width_px == 2464 - assert intrinsics_pinhole.height_px == 1600 - - -def test_asdict(): - intrinsics_pinhole = IntrinsicsPinhole( - camera_matrix=(0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0), - distortion=(0.49, 0.69, 0.31, 0.81, 0.99), - width_px=2464, - height_px=1600, - ) - - assert intrinsics_pinhole.asdict() == { - "camera_matrix": [0.48, 0, 0.81, 0, 0, 0.16, 0.83, 0, 0, 0, 1, 0], - "distortion_coeffs": [0.49, 0.69, 0.31, 0.81, 0.99], - "width_px": 2464, - "height_px": 1600, - } - - if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py index d796a15..ed3a8e1 100644 --- a/tests/test_raillabel/format/test_intrinsics_radar.py +++ b/tests/test_raillabel/format/test_intrinsics_radar.py @@ -11,15 +11,6 @@ # == Fixtures ========================= -@pytest.fixture -def intrinsics_radar_dict() -> dict: - return { - "resolution_px_per_m": 2.856, - "width_px": 2856, - "height_px": 1428, - } - - @pytest.fixture def intrinsics_radar_json() -> dict: return JSONIntrinsicsRadar( @@ -46,33 +37,5 @@ def test_from_json(intrinsics_radar, intrinsics_radar_json): assert actual == intrinsics_radar -def test_fromdict(): - intrinsics_radar = IntrinsicsRadar.fromdict( - { - "resolution_px_per_m": 2.856, - "width_px": 2856, - "height_px": 1428, - } - ) - - assert intrinsics_radar.resolution_px_per_m == 2.856 - assert intrinsics_radar.width_px == 2856 - assert intrinsics_radar.height_px == 1428 - - -def test_asdict(): - intrinsics_radar = IntrinsicsRadar( - resolution_px_per_m=2.856, - width_px=2856, - height_px=1428, - ) - - assert intrinsics_radar.asdict() == { - "resolution_px_per_m": 2.856, - "width_px": 2856, - "height_px": 1428, - } - - if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py deleted file mode 100644 index 5634297..0000000 --- a/tests/test_raillabel/format/test_metadata.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Metadata - -# == Fixtures ========================= - - -@pytest.fixture -def metadata_minimal_dict() -> dict: - return {"schema_version": "1.0.0"} - - -@pytest.fixture -def metadata_full_dict() -> dict: - return { - "schema_version": "1.0.0", - "annotator": "test_annotator", - "subschema_version": "2.1.0", - "comment": "test_comment", - "name": "test_project", - "tagged_file": "test_folder", - } - - -@pytest.fixture -def metadata_minimal() -> dict: - return Metadata(schema_version="1.0.0") - - -@pytest.fixture -def metadata_full() -> dict: - return Metadata( - schema_version="1.0.0", - annotator="test_annotator", - subschema_version="2.1.0", - comment="test_comment", - name="test_project", - tagged_file="test_folder", - ) - - -# == Tests ============================ - - -def test_fromdict_minimal(): - metadata = Metadata.fromdict( - {"schema_version": "1.0.0"}, - ) - - assert metadata.schema_version == "1.0.0" - assert metadata.annotator is None - - -def test_fromdict_full(): - metadata = Metadata.fromdict( - { - "schema_version": "1.0.0", - "annotator": "test_annotator", - "subschema_version": "2.1.0", - "comment": "test_comment", - "name": "test_project", - "tagged_file": "test_folder", - }, - "2.1.1", - ) - - assert metadata.annotator == "test_annotator" - assert metadata.schema_version == "1.0.0" - assert metadata.comment == "test_comment" - assert metadata.name == "test_project" - assert metadata.subschema_version == "2.1.1" - assert metadata.tagged_file == "test_folder" - - -def test_fromdict_additional_arg_valid(): - metadata = Metadata.fromdict({"schema_version": "1.0.0", "additional_argument": "Some Value"}) - - assert metadata.schema_version == "1.0.0" - assert metadata.additional_argument == "Some Value" - - -def test_asdict_minimal(): - metadata_dict = Metadata(schema_version="1.0.0").asdict() - - assert metadata_dict == {"schema_version": "1.0.0"} - - -def test_asdict_full(): - metadata_dict = Metadata( - annotator="test_annotator", - schema_version="1.0.0", - comment="test_comment", - name="test_project", - subschema_version="2.1.0", - tagged_file="test_folder", - ).asdict() - - assert metadata_dict == { - "schema_version": "1.0.0", - "annotator": "test_annotator", - "subschema_version": "2.1.0", - "comment": "test_comment", - "name": "test_project", - "tagged_file": "test_folder", - } - - -def test_fromdict_additional_arg(): - metadata = Metadata(schema_version="1.0.0") - - metadata.additional_argument = "Some Value" - - assert metadata.asdict() == {"schema_version": "1.0.0", "additional_argument": "Some Value"} - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py deleted file mode 100644 index 7c3d0c1..0000000 --- a/tests/test_raillabel/format/test_num.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Num - -# == Fixtures ========================= - - -@pytest.fixture -def num_dict(sensor_camera) -> dict: - return { - "uid": "4e86c449-3B19-410c-aa64-603d46da3b26", - "name": "some_number", - "val": 24, - "coordinate_system": sensor_camera.uid, - } - - -@pytest.fixture -def num(sensor_camera) -> dict: - return Num( - uid="4e86c449-3B19-410c-aa64-603d46da3b26", - name="some_number", - val=24, - sensor=sensor_camera, - ) - - -# == Tests ============================ - - -def test_fromdict(sensor_camera): - num = Num.fromdict( - { - "uid": "4e86c449-3B19-410c-aa64-603d46da3b26", - "name": "some_number", - "val": 24, - "coordinate_system": sensor_camera.uid, - }, - {sensor_camera.uid: sensor_camera}, - ) - - assert num.uid == "4e86c449-3B19-410c-aa64-603d46da3b26" - assert num.name == "some_number" - assert num.val == 24 - assert num.sensor == sensor_camera - - -def test_asdict(sensor_camera): - num = Num( - uid="4e86c449-3B19-410c-aa64-603d46da3b26", - name="some_number", - val=24, - sensor=sensor_camera, - ) - - assert num.asdict() == { - "uid": "4e86c449-3B19-410c-aa64-603d46da3b26", - "name": "some_number", - "val": 24, - "coordinate_system": sensor_camera.uid, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py deleted file mode 100644 index 482f49a..0000000 --- a/tests/test_raillabel/format/test_object.py +++ /dev/null @@ -1,423 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import random -import sys -import typing as t -from pathlib import Path -from uuid import uuid4 - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format._attribute_type import AttributeType -from raillabel.format import ( - Bbox, - Cuboid, - Frame, - FrameInterval, - Object, - Point2d, - Point3d, - Quaternion, - Sensor, - Size2d, - Size3d, -) - -# == Fixtures ========================= - - -@pytest.fixture -def objects_dict(object_person_dict, object_train_dict) -> dict: - return { - object_person_dict["object_uid"]: object_person_dict["data_dict"], - object_train_dict["object_uid"]: object_train_dict["data_dict"], - } - - -@pytest.fixture -def objects(object_person, object_train) -> dict[str, Object]: - return { - object_person.uid: object_person, - object_train.uid: object_train, - } - - -@pytest.fixture -def object_person_dict() -> dict: - return { - "object_uid": "b40ba3ad-0327-46ff-9c28-2506cfd6d934", - "data_dict": { - "name": "person_0000", - "type": "person", - }, - } - - -@pytest.fixture -def object_person() -> dict: - return Object( - uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", - name="person_0000", - type="person", - ) - - -@pytest.fixture -def object_train_dict() -> dict: - return { - "object_uid": "d51a19be-8bc2-4a82-b66a-03c8de95b0cf", - "data_dict": { - "name": "train_0000", - "type": "train", - }, - } - - -@pytest.fixture -def object_train() -> dict: - return Object( - uid="d51a19be-8bc2-4a82-b66a-03c8de95b0cf", - name="train_0000", - type="train", - ) - - -# == Tests ============================ - - -def test_fromdict(): - object = Object.fromdict( - object_uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", - data_dict={ - "name": "person_0000", - "type": "person", - }, - ) - - assert object.uid == "b40ba3ad-0327-46ff-9c28-2506cfd6d934" - assert object.name == "person_0000" - assert object.type == "person" - - -def test_asdict_no_frames(): - object = Object( - uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", - name="person_0000", - type="person", - ) - - assert object.asdict() == { - "name": "person_0000", - "type": "person", - } - - -def test_asdict_with_frames(): - object = Object( - uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", - name="person_0000", - type="person", - ) - - frames = { - 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - } - - object_dict = object.asdict(frames) - - assert "frame_intervals" in object_dict - assert "object_data_pointers" in object_dict - assert "rgb_middle__bbox__person" in object_dict["object_data_pointers"] - - -def test_frame_intervals(): - object = Object( - uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934", - name="person_0000", - type="person", - ) - - frames = { - 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - 1: build_frame(1, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - 2: build_frame(2, {}), - 3: build_frame(3, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - } - - assert object.frame_intervals(frames) == [ - FrameInterval(0, 1), - FrameInterval(3, 3), - ] - - -def test_object_data_pointers__sensor(): - object = build_object("person") - - frames = { - 0: build_frame( - 0, - { - object: [ - build_annotation("rgb_middle__bbox__person", object), - build_annotation("lidar__bbox__person", object), - ] - }, - ) - } - - object_data_pointers = object.object_data_pointers(frames) - - assert set(object_data_pointers.keys()) == set( - ["rgb_middle__bbox__person", "lidar__bbox__person"] - ) - - -def test_object_data_pointers__annotation_type(): - object = build_object("person") - - frames = { - 0: build_frame( - 0, - { - object: [ - build_annotation("rgb_middle__bbox__person", object), - build_annotation("rgb_middle__cuboid__person", object), - ] - }, - ) - } - - object_data_pointers = object.object_data_pointers(frames) - - assert set(object_data_pointers.keys()) == set( - ["rgb_middle__bbox__person", "rgb_middle__cuboid__person"] - ) - - -def test_object_data_pointers__one_frame_interval(): - object = build_object("person") - - frames = { - 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - 1: build_frame(1, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - } - - object_data_pointers = object.object_data_pointers(frames) - - assert len(object_data_pointers) == 1 - assert object_data_pointers["rgb_middle__bbox__person"].frame_intervals == [FrameInterval(0, 1)] - - -def test_object_data_pointers__two_frame_intervals(): - object = build_object("person") - - frames = { - 0: build_frame(0, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - 1: build_frame(1, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - 8: build_frame(8, {object: [build_annotation("rgb_middle__bbox__person", object)]}), - } - - object_data_pointers = object.object_data_pointers(frames) - - assert len(object_data_pointers) == 1 - assert object_data_pointers["rgb_middle__bbox__person"].frame_intervals == [ - FrameInterval(0, 1), - FrameInterval(8, 8), - ] - - -def test_object_data_pointers__attributes_one_annotation(): - object = build_object("person") - - frames = { - 0: build_frame( - 0, - { - object: [ - build_annotation( - name="rgb_middle__bbox__person", - object=object, - attributes={ - "text_attr": "some value", - "num_attr": 0, - "bool_attr": True, - "vec_attr": [0, 1], - }, - ), - ] - }, - ) - } - - object_data_pointers = object.object_data_pointers(frames) - - assert object_data_pointers["rgb_middle__bbox__person"].attribute_pointers == { - "text_attr": AttributeType.TEXT, - "num_attr": AttributeType.NUM, - "bool_attr": AttributeType.BOOLEAN, - "vec_attr": AttributeType.VEC, - } - - -def test_object_data_pointers__attributes_multiple_annotations_with_differing_attributes(): - object = build_object("person") - - frames = { - 0: build_frame( - 0, - { - object: [ - build_annotation( - name="rgb_middle__bbox__person", - object=object, - attributes={ - "text_attr": "some value", - "num_attr": 0, - }, - ), - build_annotation( - name="rgb_middle__bbox__person", - object=object, - attributes={ - "bool_attr": True, - "vec_attr": [0, 1], - }, - ), - ] - }, - ) - } - - object_data_pointers = object.object_data_pointers(frames) - - assert object_data_pointers["rgb_middle__bbox__person"].attribute_pointers == { - "text_attr": AttributeType.TEXT, - "num_attr": AttributeType.NUM, - "bool_attr": AttributeType.BOOLEAN, - "vec_attr": AttributeType.VEC, - } - - -def test_object_data_pointers__multiple_objects_of_differing_type(): - object_person = build_object("person") - object_train = build_object("train") - - frames = { - 0: build_frame( - 0, - { - object_person: [ - build_annotation("lidar__bbox__person", object_person), - ] - }, - ), - 1: build_frame( - 1, - { - object_train: [ - build_annotation("lidar__bbox__train", object_train), - ] - }, - ), - } - - person_object_data_pointers = object_person.object_data_pointers(frames) - assert len(person_object_data_pointers) == 1 - assert person_object_data_pointers["lidar__bbox__person"].frame_intervals == [ - FrameInterval(0, 0) - ] - - train_object_data_pointers = object_train.object_data_pointers(frames) - assert len(train_object_data_pointers) == 1 - assert train_object_data_pointers["lidar__bbox__train"].frame_intervals == [FrameInterval(1, 1)] - - -def test_object_data_pointers__multiple_objects_of_same_type(): - object1 = build_object("person") - object2 = build_object("person") - - frames = { - 0: build_frame( - 0, - { - object1: [ - build_annotation("lidar__bbox__person", object1), - ] - }, - ), - 1: build_frame( - 1, - { - object2: [ - build_annotation("lidar__bbox__person", object2), - ] - }, - ), - } - - object1_data_pointers = object1.object_data_pointers(frames) - assert len(object1_data_pointers) == 1 - assert object1_data_pointers["lidar__bbox__person"].frame_intervals == [FrameInterval(0, 0)] - - object2_data_pointers = object2.object_data_pointers(frames) - assert len(object2_data_pointers) == 1 - assert object2_data_pointers["lidar__bbox__person"].frame_intervals == [FrameInterval(1, 1)] - - -# == Helpers ========================== - - -def build_annotation(name: str, object: Object, attributes: dict = {}) -> t.Union[Bbox, Cuboid]: - sensor_uid, ann_type, object_type = tuple(name.split("__")) - - sensor = Sensor(sensor_uid) - - if ann_type == "bbox": - return Bbox( - uid=str(uuid4()), - object=object, - attributes=attributes, - sensor=sensor, - pos=Point2d(50, 100), - size=Size2d(30, 30), - ) - - elif ann_type == "cuboid": - return Cuboid( - uid=str(uuid4()), - object=object, - attributes=attributes, - sensor=sensor, - pos=Point3d(50, 100, 20), - size=Size3d(30, 30, 30), - quat=Quaternion(0, 0, 0, 1), - ) - - else: - raise ValueError() - - -def build_frame(uid: int, raw_object_data: dict[Object, list[t.Union[Bbox, Cuboid]]]) -> Frame: - annotations = {} - for object_data in raw_object_data.values(): - for annotation in object_data: - annotations[annotation.uid] = annotation - - return Frame(annotations=annotations) - - -def build_object(type: str) -> Object: - return Object( - uid=str(uuid4()), name=f"{type}_{str(random.randint(0, 9999)).zfill(4)}", type=type - ) - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_object_annotation.py b/tests/test_raillabel/format/test_object_annotation.py deleted file mode 100644 index 143ca47..0000000 --- a/tests/test_raillabel/format/test_object_annotation.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -import raillabel -from raillabel.format import annotation_classes - -# == Fixtures ========================= - - -@pytest.fixture -def all_annotations( - bbox, - bbox_train, - cuboid, - poly2d, - poly3d, - seg3d, -): - return { - bbox.uid: bbox, - bbox_train.uid: bbox_train, - cuboid.uid: cuboid, - poly2d.uid: poly2d, - poly3d.uid: poly3d, - seg3d.uid: seg3d, - } - - -# == Tests ============================ - - -def test_annotation_classes(): - assert annotation_classes() == { - "bbox": raillabel.format.Bbox, - "poly2d": raillabel.format.Poly2d, - "cuboid": raillabel.format.Cuboid, - "poly3d": raillabel.format.Poly3d, - "vec": raillabel.format.Seg3d, - } - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_object_data.py b/tests/test_raillabel/format/test_object_data.py deleted file mode 100644 index 244f930..0000000 --- a/tests/test_raillabel/format/test_object_data.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -# == Fixtures ========================= - - -@pytest.fixture -def object_data_person_dict(bbox_dict, poly2d_dict, cuboid_dict, poly3d_dict, seg3d_dict) -> dict: - return { - "object_data": { - "bbox": [bbox_dict], - "poly2d": [poly2d_dict], - "cuboid": [cuboid_dict], - "poly3d": [poly3d_dict], - "vec": [seg3d_dict], - } - } - - -@pytest.fixture -def object_data_train_dict(bbox_train_dict) -> dict: - return { - "object_data": { - "bbox": [bbox_train_dict], - } - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index b72ee4c..8950e62 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -11,7 +11,7 @@ @pytest.fixture -def point2d_dict() -> dict: +def point2d_json() -> dict: return [1.5, 222] @@ -20,39 +20,13 @@ def point2d() -> dict: return Point2d(1.5, 222) -@pytest.fixture -def point2d_another_dict() -> dict: - return [19, 84] - - -@pytest.fixture -def point2d_another() -> dict: - return Point2d(19, 84) - - # == Tests ============================ -def test_from_json(point2d, point2d_dict): - actual = Point2d.from_json(point2d_dict) +def test_from_json(point2d, point2d_json): + actual = Point2d.from_json(point2d_json) assert actual == point2d -def test_fromdict(): - point2d = Point2d.fromdict([1.5, 222]) - - assert point2d.x == 1.5 - assert point2d.y == 222 - - -def test_asdict(): - point2d = Point2d( - x=1.5, - y=222, - ) - - assert point2d.asdict() == [1.5, 222] - - if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index 238f226..2d73b01 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -3,21 +3,15 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import Point3d # == Fixtures ========================= @pytest.fixture -def point3d_dict() -> dict: +def point3d_json() -> dict: return [419, 3.14, 0] @@ -26,42 +20,13 @@ def point3d() -> dict: return Point3d(419, 3.14, 0) -@pytest.fixture -def point3d_another_dict() -> dict: - return [9, 8, 7] - - -@pytest.fixture -def point3d_another() -> dict: - return Point3d(9, 8, 7) - - # == Tests ============================ -def test_from_json(point3d, point3d_dict): - actual = Point3d.from_json(point3d_dict) +def test_from_json(point3d, point3d_json): + actual = Point3d.from_json(point3d_json) assert actual == point3d -def test_fromdict(): - point3d = Point3d.fromdict([419, 3.14, 0]) - - assert point3d.x == 419 - assert point3d.y == 3.14 - assert point3d.z == 0 - - -def test_asdict(): - point3d = Point3d( - x=419, - y=3.14, - z=0, - ) - - assert point3d.asdict() == [419, 3.14, 0] - - if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py deleted file mode 100644 index 24baa98..0000000 --- a/tests/test_raillabel/format/test_poly2d.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Poly2d - -# == Fixtures ========================= - - -@pytest.fixture -def poly2d_dict( - sensor_camera, attributes_multiple_types_dict, point2d_dict, point2d_another_dict -) -> dict: - return { - "uid": "d73b5988-767B-47ef-979c-022af60c6ab2", - "name": "rgb_middle__poly2d__person", - "val": point2d_dict + point2d_another_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - "mode": "MODE_POLY2D_ABSOLUTE", - } - - -@pytest.fixture -def poly2d( - point2d, point2d_another, sensor_camera, attributes_multiple_types, object_person -) -> dict: - return Poly2d( - uid="d73b5988-767B-47ef-979c-022af60c6ab2", - points=[point2d, point2d_another], - object=object_person, - sensor=sensor_camera, - attributes=attributes_multiple_types, - closed=True, - mode="MODE_POLY2D_ABSOLUTE", - ) - - -# == Tests ============================ - - -def test_fromdict( - point2d, - point2d_dict, - point2d_another, - point2d_another_dict, - sensor_camera, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - poly2d = Poly2d.fromdict( - { - "uid": "d73b5988-767B-47ef-979c-022af60c6ab2", - "name": "rgb_middle__poly2d__person", - "val": point2d_dict + point2d_another_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - "mode": "MODE_POLY2D_ABSOLUTE", - }, - sensors, - object_person, - ) - - assert poly2d.uid == "d73b5988-767B-47ef-979c-022af60c6ab2" - assert poly2d.name == "rgb_middle__poly2d__person" - assert poly2d.points == [point2d, point2d_another] - assert poly2d.object == object_person - assert poly2d.sensor == sensor_camera - assert poly2d.attributes == attributes_multiple_types - assert poly2d.closed == True - assert poly2d.mode == "MODE_POLY2D_ABSOLUTE" - - -def test_asdict( - point2d, - point2d_dict, - point2d_another, - point2d_another_dict, - sensor_camera, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - poly2d = Poly2d( - uid="d73b5988-767B-47ef-979c-022af60c6ab2", - points=[point2d, point2d_another], - object=object_person, - sensor=sensor_camera, - attributes=attributes_multiple_types, - closed=True, - mode="MODE_POLY2D_ABSOLUTE", - ) - - assert poly2d.asdict() == { - "uid": "d73b5988-767B-47ef-979c-022af60c6ab2", - "name": "rgb_middle__poly2d__person", - "val": point2d_dict + point2d_another_dict, - "coordinate_system": sensor_camera.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - "mode": "MODE_POLY2D_ABSOLUTE", - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py deleted file mode 100644 index 87d8dec..0000000 --- a/tests/test_raillabel/format/test_poly3d.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Poly3d - -# == Fixtures ========================= - - -@pytest.fixture -def poly3d_dict( - sensor_lidar, attributes_multiple_types_dict, point3d_dict, point3d_another_dict -) -> dict: - return { - "uid": "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", - "name": "lidar__poly3d__person", - "val": point3d_dict + point3d_another_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - } - - -@pytest.fixture -def poly3d(point3d, point3d_another, sensor_lidar, object_person, attributes_multiple_types) -> dict: - return Poly3d( - uid="9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", - points=[point3d, point3d_another], - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - closed=True, - ) - - -# == Tests ============================ - - -def test_fromdict( - point3d, - point3d_dict, - point3d_another, - point3d_another_dict, - sensor_lidar, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - poly3d = Poly3d.fromdict( - { - "uid": "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", - "name": "lidar__poly3d__person", - "val": point3d_dict + point3d_another_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - }, - sensors, - object_person, - ) - - assert poly3d.uid == "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb" - assert poly3d.name == "lidar__poly3d__person" - assert poly3d.points == [point3d, point3d_another] - assert poly3d.object == object_person - assert poly3d.sensor == sensor_lidar - assert poly3d.attributes == attributes_multiple_types - assert poly3d.closed == True - - -def test_asdict( - point3d, - point3d_dict, - point3d_another, - point3d_another_dict, - sensor_lidar, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - poly3d = Poly3d( - uid="9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", - points=[point3d, point3d_another], - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - closed=True, - ) - - assert poly3d.asdict() == { - "uid": "9a9a30f5-D334-4f11-aa3f-c3c83f2935eb", - "name": "lidar__poly3d__person", - "val": point3d_dict + point3d_another_dict, - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - "closed": True, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py index d508e85..78ebab4 100644 --- a/tests/test_raillabel/format/test_quaternion.py +++ b/tests/test_raillabel/format/test_quaternion.py @@ -3,21 +3,15 @@ from __future__ import annotations -import os -import sys -from pathlib import Path - import pytest -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - from raillabel.format import Quaternion # == Fixtures ========================= @pytest.fixture -def quaternion_dict() -> dict: +def quaternion_json() -> dict: return [0.75318325, -0.10270147, 0.21430262, -0.61338551] @@ -29,26 +23,10 @@ def quaternion() -> dict: # == Tests ============================ -def test_from_json(quaternion, quaternion_dict): - actual = Quaternion.from_json(quaternion_dict) +def test_from_json(quaternion, quaternion_json): + actual = Quaternion.from_json(quaternion_json) assert actual == quaternion -def test_fromdict(): - quaternion = Quaternion.fromdict([0.75318325, -0.10270147, 0.21430262, -0.61338551]) - - assert quaternion.x == 0.75318325 - assert quaternion.y == -0.10270147 - assert quaternion.z == 0.21430262 - assert quaternion.w == -0.61338551 - - -def test_asdict(): - quaternion = Quaternion(x=0.75318325, y=-0.10270147, z=0.21430262, w=-0.61338551) - - assert quaternion.asdict() == [0.75318325, -0.10270147, 0.21430262, -0.61338551] - - if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py deleted file mode 100644 index 124b55d..0000000 --- a/tests/test_raillabel/format/test_scene.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Frame, FrameInterval, Scene -from raillabel.format.scene import _clean_dict - -# == Fixtures ========================= - - -@pytest.fixture -def scene_dict( - metadata_full_dict, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, - object_person_dict, - object_train_dict, - frame, - frame_dict, -) -> dict: - return { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["stream"], - sensor_radar_dict["uid"]: sensor_radar_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_lidar_dict["uid"], - sensor_camera_dict["uid"], - sensor_radar_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], - sensor_radar_dict["uid"]: sensor_radar_dict["coordinate_system"], - }, - "objects": { - object_person_dict["object_uid"]: object_person_dict["data_dict"], - object_train_dict["object_uid"]: object_train_dict["data_dict"], - }, - "frames": { - frame.uid: frame_dict, - }, - "frame_intervals": [ - { - "frame_start": 0, - "frame_end": 0, - } - ], - } - } - - -@pytest.fixture -def scene( - metadata_full, sensor_camera, sensor_lidar, sensor_radar, object_person, object_train, frame -) -> Scene: - return Scene( - metadata=metadata_full, - sensors={ - sensor_lidar.uid: sensor_lidar, - sensor_camera.uid: sensor_camera, - sensor_radar.uid: sensor_radar, - }, - objects={ - object_person.uid: object_person, - object_train.uid: object_train, - }, - frames={frame.uid: frame}, - ) - - -# == Tests ============================ - - -def test_fromdict_metadata( - metadata_full, - metadata_full_dict, -): - scene = Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - } - }, - subschema_version=metadata_full.subschema_version, - ) - - scene.metadata.exporter_version = None # necessary for testing on remote - - assert scene.metadata == metadata_full - - -def test_fromdict_sensors( - metadata_full_dict, - sensor_camera, - sensor_lidar, - sensor_radar, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, -): - scene = Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["stream"], - sensor_radar_dict["uid"]: sensor_radar_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_lidar_dict["uid"], - sensor_camera_dict["uid"], - sensor_radar_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], - sensor_radar_dict["uid"]: sensor_radar_dict["coordinate_system"], - }, - } - } - ) - - assert scene.sensors == { - sensor_lidar.uid: sensor_lidar, - sensor_camera.uid: sensor_camera, - sensor_radar.uid: sensor_radar, - } - - -def test_fromdict_objects( - metadata_full, - metadata_full_dict, - object_person, - object_train, - object_person_dict, - object_train_dict, -): - scene = Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "objects": { - object_person_dict["object_uid"]: object_person_dict["data_dict"], - object_train_dict["object_uid"]: object_train_dict["data_dict"], - }, - } - }, - subschema_version=metadata_full.subschema_version, - ) - - assert scene.objects == { - object_person.uid: object_person, - object_train.uid: object_train, - } - - -def test_fromdict_frames( - metadata_full, - metadata_full_dict, - streams_dict, - coordinate_systems_dict, - objects_dict, - frame, - frame_dict, -): - scene = Scene.fromdict( - { - "openlabel": { - "metadata": metadata_full_dict, - "streams": streams_dict, - "coordinate_systems": coordinate_systems_dict, - "objects": objects_dict, - "frames": { - "0": frame_dict, - }, - "frame_intervals": [ - { - "frame_start": 0, - "frame_end": 0, - } - ], - } - }, - subschema_version=metadata_full.subschema_version, - ) - - assert scene.frames == { - 0: frame, - } - - -def test_asdict_sensors( - metadata_full, - metadata_full_dict, - sensor_camera, - sensor_lidar, - sensor_radar, - sensor_camera_dict, - sensor_lidar_dict, - sensor_radar_dict, -): - scene = Scene( - metadata=metadata_full, - sensors={ - sensor_lidar.uid: sensor_lidar, - sensor_camera.uid: sensor_camera, - sensor_radar.uid: sensor_radar, - }, - ) - - assert scene.asdict() == { - "openlabel": { - "metadata": metadata_full_dict, - "streams": { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["stream"], - sensor_radar_dict["uid"]: sensor_radar_dict["stream"], - }, - "coordinate_systems": { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_lidar_dict["uid"], - sensor_camera_dict["uid"], - sensor_radar_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], - sensor_radar_dict["uid"]: sensor_radar_dict["coordinate_system"], - }, - } - } - - -def test_asdict_objects( - metadata_full, - metadata_full_dict, - object_person, - object_train, - object_person_dict, - object_train_dict, -): - scene = Scene( - metadata=metadata_full, - objects={ - object_person.uid: object_person, - object_train.uid: object_train, - }, - ) - - assert scene.asdict(calculate_pointers=False) == { - "openlabel": { - "metadata": metadata_full_dict, - "objects": { - object_person_dict["object_uid"]: object_person_dict["data_dict"], - object_train_dict["object_uid"]: object_train_dict["data_dict"], - }, - } - } - - -def test_asdict_frames( - metadata_full, - metadata_full_dict, - sensors, - streams_dict, - coordinate_systems_dict, - objects, - objects_dict, - frame, - frame_dict, -): - scene = Scene( - metadata=metadata_full, - sensors=sensors, - objects=objects, - frames={ - "0": frame, - }, - ) - - assert scene.asdict(calculate_pointers=False) == { - "openlabel": { - "metadata": metadata_full_dict, - "streams": streams_dict, - "coordinate_systems": coordinate_systems_dict, - "objects": objects_dict, - "frames": { - "0": frame_dict, - }, - "frame_intervals": [ - { - "frame_start": 0, - "frame_end": 0, - } - ], - } - } - - -def test_frame_intervals(metadata_minimal): - scene = Scene( - metadata=metadata_minimal, - frames={ - 1: Frame(1), - 2: Frame(2), - 3: Frame(3), - 8: Frame(8), - }, - ) - - assert scene.frame_intervals == [ - FrameInterval(1, 3), - FrameInterval(8, 8), - ] - - -def test_integration(json_data): - scene_dict = json_data["openlabel_v1_short"] - - actual = Scene.fromdict(scene_dict).asdict() - - del actual["openlabel"]["metadata"]["exporter_version"] - assert actual == scene_dict - - -def test_clean_dict(): - input_dict = {"non_empty_field": "non_empty_value", "none_field": None, "field_with_len_0": []} - - assert _clean_dict(input_dict) == { - "non_empty_field": "non_empty_value", - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py deleted file mode 100644 index 1ff15e3..0000000 --- a/tests/test_raillabel/format/test_seg3d.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Seg3d - -# == Fixtures ========================= - - -@pytest.fixture -def seg3d_dict(sensor_lidar, attributes_multiple_types_dict) -> dict: - return { - "uid": "db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", - "name": "lidar__vec__person", - "val": [586, 789, 173], - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - } - - -@pytest.fixture -def seg3d(sensor_lidar, attributes_multiple_types, object_person) -> dict: - return Seg3d( - uid="db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", - point_ids=[586, 789, 173], - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - ) - - -# == Tests ============================ - - -def test_fromdict( - sensor_lidar, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - seg3d = Seg3d.fromdict( - { - "uid": "db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", - "name": "lidar__vec__person", - "val": [586, 789, 173], - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - }, - sensors, - object_person, - ) - - assert seg3d.uid == "db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a" - assert seg3d.name == "lidar__vec__person" - assert seg3d.point_ids == [586, 789, 173] - assert seg3d.object == object_person - assert seg3d.sensor == sensor_lidar - assert seg3d.attributes == attributes_multiple_types - - -def test_asdict( - sensor_lidar, - sensors, - object_person, - attributes_multiple_types, - attributes_multiple_types_dict, -): - seg3d = Seg3d( - uid="db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", - point_ids=[586, 789, 173], - object=object_person, - sensor=sensor_lidar, - attributes=attributes_multiple_types, - ) - - assert seg3d.asdict() == { - "uid": "db4e4a77-B926-4a6c-a2a6-e0ecf9d8734a", - "name": "lidar__vec__person", - "val": [586, 789, 173], - "coordinate_system": sensor_lidar.uid, - "attributes": attributes_multiple_types_dict, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_sensor.py b/tests/test_raillabel/format/test_sensor.py deleted file mode 100644 index 59b2259..0000000 --- a/tests/test_raillabel/format/test_sensor.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -import typing as t -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Sensor, SensorType - -# == Fixtures ========================= - - -@pytest.fixture -def sensors(sensor_lidar, sensor_camera, sensor_radar) -> dict[str, Sensor]: - return { - sensor_lidar.uid: sensor_lidar, - sensor_camera.uid: sensor_camera, - sensor_radar.uid: sensor_radar, - } - - -@pytest.fixture -def streams_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict) -> dict: - return { - sensor_camera_dict["uid"]: sensor_camera_dict["stream"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["stream"], - sensor_radar_dict["uid"]: sensor_radar_dict["stream"], - } - - -@pytest.fixture -def coordinate_systems_dict(sensor_camera_dict, sensor_lidar_dict, sensor_radar_dict) -> dict: - return { - "base": { - "type": "local", - "parent": "", - "children": [ - sensor_lidar_dict["uid"], - sensor_camera_dict["uid"], - sensor_radar_dict["uid"], - ], - }, - sensor_camera_dict["uid"]: sensor_camera_dict["coordinate_system"], - sensor_lidar_dict["uid"]: sensor_lidar_dict["coordinate_system"], - sensor_radar_dict["uid"]: sensor_radar_dict["coordinate_system"], - } - - -@pytest.fixture -def sensor_lidar_dict(transform_dict) -> dict: - return { - "uid": "lidar", - "stream": { - "type": "lidar", - "uri": "/lidar_merged", - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -@pytest.fixture -def sensor_lidar(transform) -> Sensor: - return Sensor( - uid="lidar", - extrinsics=transform, - intrinsics=None, - type=SensorType.LIDAR, - uri="/lidar_merged", - ) - - -@pytest.fixture -def sensor_camera_dict(transform_dict, intrinsics_pinhole_dict) -> dict: - return { - "uid": "rgb_middle", - "stream": { - "type": "camera", - "uri": "/S1206063/image", - "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -@pytest.fixture -def sensor_camera(transform, intrinsics_pinhole) -> Sensor: - return Sensor( - uid="rgb_middle", - extrinsics=transform, - intrinsics=intrinsics_pinhole, - type=SensorType.CAMERA, - uri="/S1206063/image", - ) - - -@pytest.fixture -def sensor_radar_dict(transform_dict, intrinsics_radar_dict) -> dict: - return { - "uid": "radar", - "stream": { - "type": "radar", - "uri": "/talker1/Nvt/Cartesian", - "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -@pytest.fixture -def sensor_radar(transform, intrinsics_radar) -> Sensor: - return Sensor( - uid="radar", - extrinsics=transform, - intrinsics=intrinsics_radar, - type=SensorType.RADAR, - uri="/talker1/Nvt/Cartesian", - ) - - -# == Tests ============================ - - -def test_lidar_fromdict(transform, transform_dict): - sensor = Sensor.fromdict( - uid="lidar", - stream_data_dict={ - "type": "lidar", - "uri": "/lidar_merged", - }, - cs_data_dict={ - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - ) - - assert sensor.uid == "lidar" - assert sensor.extrinsics == transform - assert sensor.intrinsics == None - assert sensor.type == SensorType.LIDAR - assert sensor.uri == "/lidar_merged" - - -def test_lidar_asdict(transform, transform_dict): - sensor = Sensor( - uid="lidar", - extrinsics=transform, - intrinsics=None, - type=SensorType.LIDAR, - uri="/lidar_merged", - ) - - assert sensor.asdict() == { - "stream": { - "type": "lidar", - "uri": "/lidar_merged", - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -def test_camera_fromdict(transform, transform_dict, intrinsics_pinhole, intrinsics_pinhole_dict): - sensor = Sensor.fromdict( - uid="rgb_middle", - stream_data_dict={ - "type": "camera", - "uri": "/S1206063/image", - "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, - }, - cs_data_dict={ - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - ) - - assert sensor.uid == "rgb_middle" - assert sensor.extrinsics == transform - assert sensor.intrinsics == intrinsics_pinhole - assert sensor.type == SensorType.CAMERA - assert sensor.uri == "/S1206063/image" - - -def test_camera_asdict(transform, transform_dict, intrinsics_pinhole, intrinsics_pinhole_dict): - sensor = Sensor( - uid="rgb_middle", - extrinsics=transform, - intrinsics=intrinsics_pinhole, - type=SensorType.CAMERA, - uri="/S1206063/image", - ) - - assert sensor.asdict() == { - "stream": { - "type": "camera", - "uri": "/S1206063/image", - "stream_properties": {"intrinsics_pinhole": intrinsics_pinhole_dict}, - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -def test_radar_fromdict(transform, transform_dict, intrinsics_radar, intrinsics_radar_dict): - sensor = Sensor.fromdict( - uid="radar", - stream_data_dict={ - "type": "radar", - "uri": "/talker1/Nvt/Cartesian", - "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, - }, - cs_data_dict={ - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - ) - - assert sensor.uid == "radar" - assert sensor.extrinsics == transform - assert sensor.intrinsics == intrinsics_radar - assert sensor.type == SensorType.RADAR - assert sensor.uri == "/talker1/Nvt/Cartesian" - - -def test_radar_asdict(transform, transform_dict, intrinsics_radar, intrinsics_radar_dict): - sensor = Sensor( - uid="radar", - extrinsics=transform, - intrinsics=intrinsics_radar, - type=SensorType.RADAR, - uri="/talker1/Nvt/Cartesian", - ) - - assert sensor.asdict() == { - "stream": { - "type": "radar", - "uri": "/talker1/Nvt/Cartesian", - "stream_properties": {"intrinsics_radar": intrinsics_radar_dict}, - }, - "coordinate_system": { - "type": "sensor", - "parent": "base", - "pose_wrt_parent": transform_dict, - }, - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_sensor_reference.py b/tests/test_raillabel/format/test_sensor_reference.py deleted file mode 100644 index 886b41c..0000000 --- a/tests/test_raillabel/format/test_sensor_reference.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from decimal import Decimal -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import SensorReference - -# == Fixtures ========================= - - -@pytest.fixture -def sensor_reference_camera_dict() -> dict: - return { - "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, - "uri": "rgb_test0.png", - } - - -@pytest.fixture -def sensor_reference_camera(sensor_camera) -> dict: - return SensorReference( - sensor=sensor_camera, timestamp=Decimal("1632321743.100000072"), uri="rgb_test0.png" - ) - - -# == Tests ============================ - - -def test_fromdict(sensor_camera): - sensor_reference = SensorReference.fromdict( - { - "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, - "uri": "rgb_test0.png", - }, - sensor_camera, - ) - - assert sensor_reference.sensor == sensor_camera - assert sensor_reference.timestamp == Decimal("1632321743.100000072") - assert sensor_reference.uri == "rgb_test0.png" - - -def test_asdict(sensor_camera): - sensor_reference = SensorReference( - sensor=sensor_camera, timestamp=Decimal("1632321743.100000072"), uri="rgb_test0.png" - ) - - assert sensor_reference.asdict() == { - "stream_properties": {"sync": {"timestamp": "1632321743.100000072"}}, - "uri": "rgb_test0.png", - } - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/test_raillabel/format/test_size2d.py index 65ffd4f..3e4b3a3 100644 --- a/tests/test_raillabel/format/test_size2d.py +++ b/tests/test_raillabel/format/test_size2d.py @@ -11,7 +11,7 @@ @pytest.fixture -def size2d_dict() -> dict: +def size2d_json() -> dict: return [25, 1.344] @@ -23,26 +23,10 @@ def size2d() -> dict: # == Tests ============================ -def test_from_json(size2d, size2d_dict): - actual = Size2d.from_json(size2d_dict) +def test_from_json(size2d, size2d_json): + actual = Size2d.from_json(size2d_json) assert actual == size2d -def test_fromdict(): - size2d = Size2d.fromdict([25, 1.344]) - - assert size2d.x == 25 - assert size2d.y == 1.344 - - -def test_asdict(): - size2d = Size2d( - x=25, - y=1.344, - ) - - assert size2d.asdict() == [25, 1.344] - - if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/test_raillabel/format/test_size3d.py deleted file mode 100644 index 8626da3..0000000 --- a/tests/test_raillabel/format/test_size3d.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import os -import sys -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent.parent.parent)) - -from raillabel.format import Size3d - -# == Fixtures ========================= - - -@pytest.fixture -def size3d_dict() -> dict: - return [0.35, 0.7, 1.92] - - -@pytest.fixture -def size3d() -> dict: - return Size3d(0.35, 0.7, 1.92) - - -# == Tests ============================ - - -def test_fromdict(): - size3d = Size3d.fromdict([0.35, 0.7, 1.92]) - - assert size3d.x == 0.35 - assert size3d.y == 0.7 - assert size3d.z == 1.92 - - -def test_asdict(): - size3d = Size3d(x=0.35, y=0.7, z=1.92) - - assert size3d.asdict() == [0.35, 0.7, 1.92] - - -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index f6bcd78..db97033 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -13,13 +13,8 @@ @pytest.fixture -def transform_dict(point3d_dict, quaternion_dict) -> dict: - return {"translation": point3d_dict, "quaternion": quaternion_dict} - - -@pytest.fixture -def transform_json(point3d_dict, quaternion_dict) -> JSONTransformData: - return JSONTransformData(translation=point3d_dict, quaternion=quaternion_dict) +def transform_json(point3d_json, quaternion_json) -> JSONTransformData: + return JSONTransformData(translation=point3d_json, quaternion=quaternion_json) @pytest.fixture @@ -35,18 +30,5 @@ def test_from_json(transform_json, transform): assert actual == transform -def test_fromdict(point3d, point3d_dict, quaternion, quaternion_dict): - transform = Transform.fromdict({"translation": point3d_dict, "quaternion": quaternion_dict}) - - assert transform.position == point3d - assert transform.quaternion == quaternion - - -def test_asdict(point3d, point3d_dict, quaternion, quaternion_dict): - transform = Transform(position=point3d, quaternion=quaternion) - - assert transform.asdict() == {"translation": point3d_dict, "quaternion": quaternion_dict} - - if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/load/test_load.py b/tests/test_raillabel/load/test_load.py deleted file mode 100644 index 7c95c19..0000000 --- a/tests/test_raillabel/load/test_load.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os - -import pytest - -import raillabel - - -def test_load__contains_all_elements(json_paths): - actual = raillabel.load(json_paths["openlabel_v1_short"]) - assert len(actual.sensors) == 4 - assert len(actual.objects) == 3 - assert len(actual.frames) == 2 - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear"]) diff --git a/tests/test_raillabel/save/test_save.py b/tests/test_raillabel/save/test_save.py deleted file mode 100644 index fae68b1..0000000 --- a/tests/test_raillabel/save/test_save.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import json -import os -import sys -import tempfile -from copy import deepcopy -from pathlib import Path - -import pytest - -sys.path.insert(1, str(Path(__file__).parent.parent.parent)) - -import raillabel - - -def test_save_scene(json_paths): - with tempfile.TemporaryDirectory("w") as temp_dir: - scene_orig = raillabel.load(json_paths["openlabel_v1_short"]) - - raillabel.save(scene_orig, Path(temp_dir) / "test_save_file.json") - scene_saved = raillabel.load(Path(temp_dir) / "test_save_file.json") - - assert scene_orig == scene_saved - - -def test_save_json(json_data): - with tempfile.TemporaryDirectory("w") as temp_dir: - stripped_input_data = deepcopy(json_data["openlabel_v1_short"]) - - # Removes the object data pointers from the example file so that it needs to be generated from the data - for object in stripped_input_data["openlabel"]["objects"].values(): - del object["frame_intervals"] - del object["object_data_pointers"] - - with (Path(temp_dir) / "stripped_input_data.json").open("w") as f: - json.dump(stripped_input_data, f) - - scene = raillabel.load(Path(temp_dir) / "stripped_input_data.json") - raillabel.save(scene, Path(temp_dir) / "test_save_file.json") - - with (Path(temp_dir) / "test_save_file.json").open() as f: - saved_and_loaded_data = json.load(f) - - # Removes the exporter version and subschema version from the generated file as these are hard to test for - if "exporter_version" in saved_and_loaded_data["openlabel"]["metadata"]: - del saved_and_loaded_data["openlabel"]["metadata"]["exporter_version"] - - if "subschema_version" in saved_and_loaded_data["openlabel"]["metadata"]: - del saved_and_loaded_data["openlabel"]["metadata"]["subschema_version"] - - assert saved_and_loaded_data == json_data["openlabel_v1_short"] - - -def test_frame_intervals(): - data = { - "openlabel": { - "metadata": {"schema_version": "1.0.0"}, - "frames": { - "0": {}, - "1": {}, - "2": {}, - "5": {}, - "7": {}, - "8": {}, - }, - } - } - - scene = raillabel.Scene.fromdict(data) - dict_repr = scene.asdict()["openlabel"] - - assert "frame_intervals" in dict_repr - assert dict_repr["frame_intervals"] == [ - {"frame_start": 0, "frame_end": 2}, - {"frame_start": 5, "frame_end": 5}, - {"frame_start": 7, "frame_end": 8}, - ] - - -# Executes the test if the file is called -if __name__ == "__main__": - os.system("clear") - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-vv"]) From fb36164f776f17392058d98f58d3249d63b1a00f Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 09:57:46 +0100 Subject: [PATCH 089/190] refactor: extract all parent class information from Bbox --- raillabel/format/bbox.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 6e99e1e..b5a7069 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -4,14 +4,14 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID -from ._object_annotation import _ObjectAnnotation from .point2d import Point2d from .size2d import Size2d @dataclass -class Bbox(_ObjectAnnotation): +class Bbox: """A 2D bounding box in an image. Parameters @@ -38,6 +38,15 @@ class Bbox(_ObjectAnnotation): """ pos: Point2d + "The center point of the bbox in pixels." + size: Size2d + "The dimensions of the bbox in pixels from the top left corner to the bottom right corner." + + object: UUID + "The uid of the object, this annotation belongs to." + + sensor: str + "The uid of the sensor, this annotation is labeled in." - OPENLABEL_ID = "bbox" + attributes: dict[str, int | float | bool | str | list] From d751da8ffac9d326df2d2c47550aa90e1b0c2825 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:02:20 +0100 Subject: [PATCH 090/190] feat: implement Bbox.from_json() --- raillabel/format/_attributes.py | 32 ++++++++ raillabel/format/bbox.py | 42 +++++----- raillabel/json_format/bbox.py | 2 +- raillabel/json_format/boolean_attribute.py | 8 +- raillabel/json_format/cuboid.py | 2 +- raillabel/json_format/num_attribute.py | 2 +- raillabel/json_format/poly2d.py | 2 +- raillabel/json_format/poly3d.py | 2 +- raillabel/json_format/text_attribute.py | 2 +- raillabel/json_format/vec.py | 2 +- raillabel/json_format/vec_attribute.py | 2 +- tests/test_raillabel/format/conftest.py | 1 + .../test_raillabel/format/test_attributes.py | 76 +++++++++++++++++++ tests/test_raillabel/format/test_bbox.py | 55 ++++++++++++++ 14 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 raillabel/format/_attributes.py create mode 100644 tests/test_raillabel/format/test_attributes.py create mode 100644 tests/test_raillabel/format/test_bbox.py diff --git a/raillabel/format/_attributes.py b/raillabel/format/_attributes.py new file mode 100644 index 0000000..5c8824e --- /dev/null +++ b/raillabel/format/_attributes.py @@ -0,0 +1,32 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from raillabel.json_format import JSONAttributes + + +def _attributes_from_json(json: JSONAttributes | None) -> dict[str, float | bool | str | list]: + """Parse the annotation attributes from json.""" + if json is None: + return {} + + attributes: dict[str, float | bool | str | list] = {} + + if json.boolean is not None: + for bool_attribute in json.boolean: + attributes[bool_attribute.name] = bool_attribute.val + + if json.num is not None: + for num_attribute in json.num: + attributes[num_attribute.name] = num_attribute.val + + if json.text is not None: + for text_attribute in json.text: + attributes[text_attribute.name] = text_attribute.val + + if json.vec is not None: + for vec_attribute in json.vec: + attributes[vec_attribute.name] = vec_attribute.val + + return attributes diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index b5a7069..760bfd5 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -6,36 +6,16 @@ from dataclasses import dataclass from uuid import UUID +from raillabel.json_format import JSONBbox + +from ._attributes import _attributes_from_json from .point2d import Point2d from .size2d import Size2d @dataclass class Bbox: - """A 2D bounding box in an image. - - Parameters - ---------- - uid: str - This a string representing the unique universal identifier of the annotation. - pos: raillabel.format.Point2d - The center point of the bbox in pixels. - size: raillabel.format.Size2d - The dimensions of the bbox in pixels from the top left corner to the bottom right corner. - object: raillabel.format.Object - A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor - A reference to the sensor, this annotation is labeled in. Default is None. - attributes: dict, optional - Attributes of the annotation. Dict keys are the name str of the attribute, values are the - attribute values. Default is {}. - - Properties (read-only) - ---------------------- - name: str - Name of the annotation used by the VCD player for indexing in the object data pointers. - - """ + """A 2D bounding box in an image.""" pos: Point2d "The center point of the bbox in pixels." @@ -49,4 +29,16 @@ class Bbox: sensor: str "The uid of the sensor, this annotation is labeled in." - attributes: dict[str, int | float | bool | str | list] + attributes: dict[str, float | bool | str | list] + "Additional information associated with the bbox." + + @classmethod + def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: + """Construct an instant of this class from RailLabel JSON data.""" + return Bbox( + pos=Point2d.from_json((json.val[0], json.val[1])), + size=Size2d.from_json((json.val[2], json.val[3])), + object=object_uid, + sensor=json.coordinate_system, + attributes=_attributes_from_json(json.attributes), + ) diff --git a/raillabel/json_format/bbox.py b/raillabel/json_format/bbox.py index c1df9f1..f82c8c5 100644 --- a/raillabel/json_format/bbox.py +++ b/raillabel/json_format/bbox.py @@ -24,7 +24,7 @@ class JSONBbox(BaseModel): val: tuple[float, float, float, float] "The array of 4 values that define the [x, y, w, h] values of the bbox." - coordinate_system: str | None = None + coordinate_system: str "Name of the coordinate system in respect of which this object data is expressed." uid: UUID | None = None diff --git a/raillabel/json_format/boolean_attribute.py b/raillabel/json_format/boolean_attribute.py index 04db175..afda14b 100644 --- a/raillabel/json_format/boolean_attribute.py +++ b/raillabel/json_format/boolean_attribute.py @@ -9,9 +9,9 @@ class JSONBooleanAttribute(BaseModel): """A boolean attribute.""" - val: bool - "The boolean value." - - name: str | None = None + name: str """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" + + val: bool + "The boolean value." diff --git a/raillabel/json_format/cuboid.py b/raillabel/json_format/cuboid.py index e07032f..1a918a4 100644 --- a/raillabel/json_format/cuboid.py +++ b/raillabel/json_format/cuboid.py @@ -26,7 +26,7 @@ class JSONCuboid(BaseModel): encodes the quaternion that encode the rotation, and (sx, sy, sz) are the dimensions of the cuboid in its object coordinate system""" - coordinate_system: str | None = None + coordinate_system: str "Name of the coordinate system in respect of which this object data is expressed." uid: UUID | None = None diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py index e70e69b..44693e9 100644 --- a/raillabel/json_format/num_attribute.py +++ b/raillabel/json_format/num_attribute.py @@ -9,7 +9,7 @@ class JSONNumAttribute(BaseModel): """A number attribute.""" - name: str | None = None + name: str """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/raillabel/json_format/poly2d.py b/raillabel/json_format/poly2d.py index d8f687c..9d31a16 100644 --- a/raillabel/json_format/poly2d.py +++ b/raillabel/json_format/poly2d.py @@ -30,7 +30,7 @@ class JSONPoly2d(BaseModel): MODE_POLY2D_ABSOLUTE means that any point defined by an x-value followed by a y-value is the absolute position.""" - coordinate_system: str | None = None + coordinate_system: str "Name of the coordinate system in respect of which this object data is expressed." uid: UUID | None = None diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py index 685f088..eb72dd2 100644 --- a/raillabel/json_format/poly3d.py +++ b/raillabel/json_format/poly3d.py @@ -24,7 +24,7 @@ class JSONPoly3d(BaseModel): """A boolean that defines whether the polyline is closed or not. In case it is closed, it is assumed that the last point of the sequence is connected with the first one.""" - coordinate_system: str | None = None + coordinate_system: str "Name of the coordinate system in respect of which this object data is expressed." uid: UUID | None = None diff --git a/raillabel/json_format/text_attribute.py b/raillabel/json_format/text_attribute.py index 221b63a..cb1fb23 100644 --- a/raillabel/json_format/text_attribute.py +++ b/raillabel/json_format/text_attribute.py @@ -9,7 +9,7 @@ class JSONTextAttribute(BaseModel): """A text attribute.""" - name: str | None = None + name: str """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py index 7a28345..daa01c8 100644 --- a/raillabel/json_format/vec.py +++ b/raillabel/json_format/vec.py @@ -21,7 +21,7 @@ class JSONVec(BaseModel): val: list[float] "The numerical values of the vector (list) of numbers." - coordinate_system: str | None = None + coordinate_system: str "Name of the coordinate system in respect of which this object data is expressed." uid: UUID | None = None diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py index 6540475..65f23ee 100644 --- a/raillabel/json_format/vec_attribute.py +++ b/raillabel/json_format/vec_attribute.py @@ -9,7 +9,7 @@ class JSONVecAttribute(BaseModel): """A vec attribute.""" - name: str | None = None + name: str """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index f5a7eec..04c9910 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -1,5 +1,6 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from .test_attributes import attributes_multiple_types, attributes_multiple_types_json from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/test_raillabel/format/test_attributes.py new file mode 100644 index 0000000..df13068 --- /dev/null +++ b/tests/test_raillabel/format/test_attributes.py @@ -0,0 +1,76 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.json_format import ( + JSONAttributes, + JSONBooleanAttribute, + JSONNumAttribute, + JSONTextAttribute, + JSONVecAttribute, +) +from raillabel.format._attributes import _attributes_from_json + +# == Fixtures ========================= + + +@pytest.fixture +def attributes_multiple_types_json() -> JSONAttributes: + return JSONAttributes( + boolean=[ + JSONBooleanAttribute(name="has_red_hat", val=True), + JSONBooleanAttribute(name="has_green_hat", val=False), + ], + num=[JSONNumAttribute(name="number_of_red_clothing_items", val=2)], + text=[JSONTextAttribute(name="color_of_hat", val="red")], + vec=[ + JSONVecAttribute( + name="clothing_items", val=["red_hat", "brown_coat", "black_pants", "red_shoes"] + ) + ], + ) + + +@pytest.fixture +def attributes_multiple_types() -> dict: + return { + "has_red_hat": True, + "has_green_hat": False, + "number_of_red_clothing_items": 2, + "color_of_hat": "red", + "clothing_items": ["red_hat", "brown_coat", "black_pants", "red_shoes"], + } + + +# == Tests ============================ + + +def test_attributes_from_json__none(): + actual = _attributes_from_json(None) + assert actual == {} + + +def test_attributes_from_json__empty(): + json_attributes = JSONAttributes( + boolean=None, + num=None, + text=None, + vec=None, + ) + + actual = _attributes_from_json(json_attributes) + assert actual == {} + + +def test_attributes_from_json__multiple_types( + attributes_multiple_types, attributes_multiple_types_json +): + actual = _attributes_from_json(attributes_multiple_types_json) + assert actual == attributes_multiple_types + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py new file mode 100644 index 0000000..3566b59 --- /dev/null +++ b/tests/test_raillabel/format/test_bbox.py @@ -0,0 +1,55 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Bbox +from raillabel.json_format import JSONBbox + +# == Fixtures ========================= + + +@pytest.fixture +def bbox_json( + attributes_multiple_types_json, + point2d_json, + size2d_json, +) -> JSONBbox: + return JSONBbox( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="rgb_middle__bbox__person", + val=point2d_json + size2d_json, + coordinate_system="rgb_middle", + attributes=attributes_multiple_types_json, + ) + + +@pytest.fixture +def bbox( + point2d, + size2d, + attributes_multiple_types, +) -> dict: + return Bbox( + pos=point2d, + size=size2d, + sensor="rgb_middle", + attributes=attributes_multiple_types, + object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + ) + + +# == Tests ============================ + + +def test_from_json(bbox, bbox_json): + actual = Bbox.from_json(bbox_json, object_uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934") + assert actual == bbox + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 1d8431b7f202b12b816267a44bae3dea37862040 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:09:02 +0100 Subject: [PATCH 091/190] feat: implement Bbox.name() --- raillabel/format/bbox.py | 4 ++++ tests/test_raillabel/format/test_bbox.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 760bfd5..6fd8b03 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -42,3 +42,7 @@ def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) + + def name(self, object_type: str) -> str: + """Return the name of the annotation used for indexing in the object data pointers.""" + return f"{self.sensor}__bbox__{object_type}" diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index 3566b59..bfb809b 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -33,7 +33,7 @@ def bbox( point2d, size2d, attributes_multiple_types, -) -> dict: +) -> Bbox: return Bbox( pos=point2d, size=size2d, @@ -47,9 +47,14 @@ def bbox( def test_from_json(bbox, bbox_json): - actual = Bbox.from_json(bbox_json, object_uid="b40ba3ad-0327-46ff-9c28-2506cfd6d934") + actual = Bbox.from_json(bbox_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) assert actual == bbox +def test_name(bbox): + actual = bbox.name("person") + assert actual == "rgb_middle__bbox__person" + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From dc9146e27e0e0844536c8d631aa7d323eae8d517 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:22:13 +0100 Subject: [PATCH 092/190] feat: implement Cuboid.from_json() --- raillabel/format/cuboid.py | 34 +++++++++++-- raillabel/format/size3d.py | 23 ++++----- tests/test_raillabel/format/conftest.py | 1 + tests/test_raillabel/format/test_cuboid.py | 58 ++++++++++++++++++++++ tests/test_raillabel/format/test_size3d.py | 32 ++++++++++++ 5 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 tests/test_raillabel/format/test_cuboid.py create mode 100644 tests/test_raillabel/format/test_size3d.py diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index ec70005..caddf91 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -4,15 +4,18 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID -from ._object_annotation import _ObjectAnnotation +from raillabel.json_format import JSONCuboid + +from ._attributes import _attributes_from_json from .point3d import Point3d from .quaternion import Quaternion from .size3d import Size3d @dataclass -class Cuboid(_ObjectAnnotation): +class Cuboid: """3D bounding box. Parameters @@ -42,7 +45,32 @@ class Cuboid(_ObjectAnnotation): """ pos: Point3d + """The center position of the cuboid in meters, where the x coordinate points ahead of the + vehicle, y points to the left and z points upwards.""" + quat: Quaternion + "The rotation of the cuboid in quaternions." + size: Size3d + "The size of the cuboid in meters." + + object: UUID + "The uid of the object, this annotation belongs to." + + sensor: str + "The uid of the sensor, this annotation is labeled in." + + attributes: dict[str, float | bool | str | list] + "Additional information associated with the bbox." - OPENLABEL_ID = "cuboid" + @classmethod + def from_json(cls, json: JSONCuboid, object_uid: UUID) -> Cuboid: + """Construct an instant of this class from RailLabel JSON data.""" + return Cuboid( + pos=Point3d.from_json((json.val[0], json.val[1], json.val[2])), + quat=Quaternion.from_json((json.val[3], json.val[4], json.val[5], json.val[6])), + size=Size3d.from_json((json.val[7], json.val[8], json.val[9])), + object=object_uid, + sensor=json.coordinate_system, + attributes=_attributes_from_json(json.attributes), + ) diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index 5da9679..3c0054d 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -8,19 +8,18 @@ @dataclass class Size3d: - """The 3D size of a cube. - - Parameters - ---------- - x: float or int - The size along the x-axis. - y: float or int - The size along the y-axis. - z: float or int - The size along the z-axis. - - """ + """The 3D size of a cube.""" x: float + "The size along the x-axis." + y: float + "The size along the y-axis." + z: float + "The size along the z-axis." + + @classmethod + def from_json(cls, json: tuple[float, float, float]) -> Size3d: + """Construct an instant of this class from RailLabel JSON data.""" + return Size3d(x=json[0], y=json[1], z=json[2]) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 04c9910..02b7fbc 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -8,4 +8,5 @@ from .test_point3d import point3d, point3d_json from .test_quaternion import quaternion, quaternion_json from .test_size2d import size2d, size2d_json +from .test_size3d import size3d, size3d_json from .test_transform import transform, transform_json diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py new file mode 100644 index 0000000..e3235c0 --- /dev/null +++ b/tests/test_raillabel/format/test_cuboid.py @@ -0,0 +1,58 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Cuboid +from raillabel.json_format import JSONCuboid + +# == Fixtures ========================= + + +@pytest.fixture +def cuboid_json( + attributes_multiple_types_json, + point3d_json, + quaternion_json, + size3d_json, +) -> JSONCuboid: + return JSONCuboid( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="lidar__cuboid__person", + val=point3d_json + quaternion_json + size3d_json, + coordinate_system="lidar", + attributes=attributes_multiple_types_json, + ) + + +@pytest.fixture +def cuboid( + point3d, + size3d, + quaternion, + attributes_multiple_types, +) -> Cuboid: + return Cuboid( + pos=point3d, + quat=quaternion, + size=size3d, + sensor="lidar", + attributes=attributes_multiple_types, + object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + ) + + +# == Tests ============================ + + +def test_from_json(cuboid, cuboid_json): + actual = Cuboid.from_json(cuboid_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + assert actual == cuboid + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/test_raillabel/format/test_size3d.py new file mode 100644 index 0000000..d38e9d2 --- /dev/null +++ b/tests/test_raillabel/format/test_size3d.py @@ -0,0 +1,32 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Size3d + +# == Fixtures ========================= + + +@pytest.fixture +def size3d_json() -> dict: + return [25, 1.344, 12.3] + + +@pytest.fixture +def size3d() -> dict: + return Size3d(25, 1.344, 12.3) + + +# == Tests ============================ + + +def test_from_json(size3d, size3d_json): + actual = Size3d.from_json(size3d_json) + assert actual == size3d + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From e27755bf7f29ac40737566337fb67cb49da2c7e2 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:24:32 +0100 Subject: [PATCH 093/190] feat: implement Cuboid.name() --- raillabel/format/cuboid.py | 32 ++++------------------ tests/test_raillabel/format/test_cuboid.py | 5 ++++ 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index caddf91..73e784d 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -16,33 +16,7 @@ @dataclass class Cuboid: - """3D bounding box. - - Parameters - ---------- - uid: str - This a string representing the unique universal identifier of the annotation. - pos: raillabel.format.Point3d - The center position of the cuboid in meters, where the x coordinate points ahead of the - vehicle, y points to the left and z points upwards. - quat: raillabel.format.Quaternion - The rotation of the cuboid in quaternions. - size: raillabel.format.Size3d - The size of the cuboid in meters. - object: raillabel.format.Object - A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor - A reference to the sensor, this annotation is labeled in. Default is None. - attributes: dict, optional - Attributes of the annotation. Dict keys are the name str of the attribute, values are the - attribute values. Default is {}. - - Properties (read-only) - ---------------------- - name: str - Name of the annotation used by the VCD player for indexing in the object data pointers. - - """ + """3D bounding box.""" pos: Point3d """The center position of the cuboid in meters, where the x coordinate points ahead of the @@ -74,3 +48,7 @@ def from_json(cls, json: JSONCuboid, object_uid: UUID) -> Cuboid: sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) + + def name(self, object_type: str) -> str: + """Return the name of the annotation used for indexing in the object data pointers.""" + return f"{self.sensor}__cuboid__{object_type}" diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index e3235c0..ae20150 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -54,5 +54,10 @@ def test_from_json(cuboid, cuboid_json): assert actual == cuboid +def test_name(cuboid): + actual = cuboid.name("person") + assert actual == "lidar__cuboid__person" + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From a2d6e24446bcdd34256a869d79cba557bd7f298f Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:38:03 +0100 Subject: [PATCH 094/190] feat: implement Poly2d.from_json() --- raillabel/format/bbox.py | 2 +- raillabel/format/cuboid.py | 2 +- raillabel/format/poly2d.py | 69 ++++++++++----------- tests/test_raillabel/format/conftest.py | 2 +- tests/test_raillabel/format/test_point2d.py | 19 +++++- tests/test_raillabel/format/test_poly2d.py | 62 ++++++++++++++++++ 6 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 tests/test_raillabel/format/test_poly2d.py diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 6fd8b03..682c704 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -30,7 +30,7 @@ class Bbox: "The uid of the sensor, this annotation is labeled in." attributes: dict[str, float | bool | str | list] - "Additional information associated with the bbox." + "Additional information associated with the annotation." @classmethod def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 73e784d..ff6f9bc 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -35,7 +35,7 @@ class Cuboid: "The uid of the sensor, this annotation is labeled in." attributes: dict[str, float | bool | str | list] - "Additional information associated with the bbox." + "Additional information associated with the annotation." @classmethod def from_json(cls, json: JSONCuboid, object_uid: UUID) -> Cuboid: diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 703ccf8..b0bf981 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -4,48 +4,43 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID -from ._object_annotation import _ObjectAnnotation +from raillabel.json_format import JSONPoly2d + +from ._attributes import _attributes_from_json from .point2d import Point2d @dataclass -class Poly2d(_ObjectAnnotation): - """Sequence of 2D points. Can either be a polygon or polyline. - - Parameters - ---------- - uid: str - This a string representing the unique universal identifier for the annotation. - points: list of raillabel.format.Point2d - List of the 2d points that make up the polyline. - closed: bool - This parameter states, whether the polyline represents a closed shape (a polygon) or an - open line. - mode: str, optional - Mode of the polyline list of values: "MODE_POLY2D_ABSOLUTE" determines that the poly2d list - contains the sequence of (x, y) values of all points of the polyline. "MODE_POLY2D_RELATIVE" - specifies that only the first point of the sequence is defined with its (x, y) values, while - all the rest are defined relative to it. "MODE_POLY2D_SRF6DCC" specifies that SRF6DCC chain - code method is used. "MODE_POLY2D_RS6FCC" specifies that the RS6FCC method is used. Default - is 'MODE_POLY2D_ABSOLUTE'. - object: raillabel.format.Object - A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor - A reference to the sensor, this annotation is labeled in. Default is None. - attributes: dict, optional - Attributes of the annotation. Dict keys are the name str of the attribute, values are the - attribute values. Default is {}. - - Properties (read-only) - ---------------------- - name: str - Name of the annotation used by the VCD player for indexing in the object data pointers. - - """ +class Poly2d: + """Sequence of 2D points. Can either be a polygon or polyline.""" points: list[Point2d] - closed: bool - mode: str = "MODE_POLY2D_ABSOLUTE" + "List of the 2d points that make up the polyline." - OPENLABEL_ID = "poly2d" + closed: bool + "If True, this object represents a polygon and if False, it represents a polyline." + + object: UUID + "The uid of the object, this annotation belongs to." + + sensor: str + "The uid of the sensor, this annotation is labeled in." + + attributes: dict[str, float | bool | str | list] + "Additional information associated with the annotation." + + @classmethod + def from_json(cls, json: JSONPoly2d, object_uid: UUID) -> Poly2d: + """Construct an instant of this class from RailLabel JSON data.""" + return Poly2d( + points=[ + Point2d(x=float(json.val[i]), y=float(json.val[i + 1])) + for i in range(0, len(json.val), 2) + ], + closed=json.closed, + object=object_uid, + sensor=json.coordinate_system, + attributes=_attributes_from_json(json.attributes), + ) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 02b7fbc..3458907 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -4,7 +4,7 @@ from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json -from .test_point2d import point2d, point2d_json +from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json from .test_point3d import point3d, point3d_json from .test_quaternion import quaternion, quaternion_json from .test_size2d import size2d, size2d_json diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 8950e62..04d5165 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -11,15 +11,25 @@ @pytest.fixture -def point2d_json() -> dict: +def point2d_json() -> tuple[float, float]: return [1.5, 222] @pytest.fixture -def point2d() -> dict: +def point2d() -> Point2d: return Point2d(1.5, 222) +@pytest.fixture +def another_point2d_json() -> tuple[float, float]: + return [1.7, 222.2] + + +@pytest.fixture +def another_point2d() -> Point2d: + return Point2d(1.7, 222.2) + + # == Tests ============================ @@ -28,5 +38,10 @@ def test_from_json(point2d, point2d_json): assert actual == point2d +def test_from_json__another(another_point2d, another_point2d_json): + actual = Point2d.from_json(another_point2d_json) + assert actual == another_point2d + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py new file mode 100644 index 0000000..05c1000 --- /dev/null +++ b/tests/test_raillabel/format/test_poly2d.py @@ -0,0 +1,62 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Poly2d +from raillabel.json_format import JSONPoly2d + +# == Fixtures ========================= + + +@pytest.fixture +def poly2d_json( + point2d_json, + another_point2d_json, + attributes_multiple_types_json, +) -> JSONPoly2d: + return JSONPoly2d( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="lidar__poly2d__person", + closed=True, + mode="MODE_POLY2D_ABSOLUTE", + val=point2d_json + another_point2d_json, + coordinate_system="lidar", + attributes=attributes_multiple_types_json, + ) + + +@pytest.fixture +def poly2d( + point2d, + another_point2d, + attributes_multiple_types, +) -> Poly2d: + return Poly2d( + points=[point2d, another_point2d], + closed=True, + sensor="lidar", + attributes=attributes_multiple_types, + object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + ) + + +# == Tests ============================ + + +def test_from_json(poly2d, poly2d_json): + actual = Poly2d.from_json(poly2d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + assert actual == poly2d + + +# def test_name(poly2d): +# actual = poly2d.name("person") +# assert actual == "lidar__poly2d__person" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 94d4565da01143c4dac7fa682c21467c58980302 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:39:18 +0100 Subject: [PATCH 095/190] feat: implement Poly2d.name() --- raillabel/format/poly2d.py | 4 ++++ tests/test_raillabel/format/test_poly2d.py | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index b0bf981..1a99b3d 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -44,3 +44,7 @@ def from_json(cls, json: JSONPoly2d, object_uid: UUID) -> Poly2d: sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) + + def name(self, object_type: str) -> str: + """Return the name of the annotation used for indexing in the object data pointers.""" + return f"{self.sensor}__poly2d__{object_type}" diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index 05c1000..d7d72df 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -25,7 +25,7 @@ def poly2d_json( closed=True, mode="MODE_POLY2D_ABSOLUTE", val=point2d_json + another_point2d_json, - coordinate_system="lidar", + coordinate_system="rgb_middle", attributes=attributes_multiple_types_json, ) @@ -39,7 +39,7 @@ def poly2d( return Poly2d( points=[point2d, another_point2d], closed=True, - sensor="lidar", + sensor="rgb_middle", attributes=attributes_multiple_types, object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), ) @@ -53,9 +53,9 @@ def test_from_json(poly2d, poly2d_json): assert actual == poly2d -# def test_name(poly2d): -# actual = poly2d.name("person") -# assert actual == "lidar__poly2d__person" +def test_name(poly2d): + actual = poly2d.name("person") + assert actual == "rgb_middle__poly2d__person" if __name__ == "__main__": From 031b819971a6efa499e0184c5f39e050f3173a92 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:45:07 +0100 Subject: [PATCH 096/190] feat: implement Poly3d.from_json() --- raillabel/format/poly3d.py | 61 +++++++++++---------- tests/test_raillabel/format/conftest.py | 2 +- tests/test_raillabel/format/test_point3d.py | 15 +++++ tests/test_raillabel/format/test_poly2d.py | 2 +- tests/test_raillabel/format/test_poly3d.py | 61 +++++++++++++++++++++ 5 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 tests/test_raillabel/format/test_poly3d.py diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 23cc8d7..0ee10f4 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -4,40 +4,43 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID -from ._object_annotation import _ObjectAnnotation +from raillabel.json_format import JSONPoly3d + +from ._attributes import _attributes_from_json from .point3d import Point3d @dataclass -class Poly3d(_ObjectAnnotation): - """Sequence of 3D points. Can either be a polygon or polyline. - - Parameters - ---------- - uid: str - This a string representing the unique universal identifier for the annotation. - points: list of raillabel.format.Point3d - List of the 3d points that make up the polyline. - closed: bool - This parameter states, whether the polyline represents a closed shape (a polygon) or an - open line. - object: raillabel.format.Object - A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor - A reference to the sensor, this annotation is labeled in. Default is None. - attributes: dict, optional - Attributes of the annotation. Dict keys are the name str of the attribute, values are the - attribute values. Default is {}. - - Properties (read-only) - ---------------------- - name: str - Name of the annotation used by the VCD player for indexing in the object data pointers. - - """ +class Poly3d: + """Sequence of 3D points. Can either be a polygon or polyline.""" points: list[Point3d] - closed: bool + "List of the 3d points that make up the polyline." - OPENLABEL_ID = "poly3d" + closed: bool + "If True, this object represents a polygon and if False, it represents a polyline." + + object: UUID + "The uid of the object, this annotation belongs to." + + sensor: str + "The uid of the sensor, this annotation is labeled in." + + attributes: dict[str, float | bool | str | list] + "Additional information associated with the annotation." + + @classmethod + def from_json(cls, json: JSONPoly3d, object_uid: UUID) -> Poly3d: + """Construct an instant of this class from RailLabel JSON data.""" + return Poly3d( + points=[ + Point3d(x=float(json.val[i]), y=float(json.val[i + 1]), z=float(json.val[i + 2])) + for i in range(0, len(json.val), 3) + ], + closed=json.closed, + object=object_uid, + sensor=json.coordinate_system, + attributes=_attributes_from_json(json.attributes), + ) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 3458907..091014f 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -5,7 +5,7 @@ from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json -from .test_point3d import point3d, point3d_json +from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json from .test_quaternion import quaternion, quaternion_json from .test_size2d import size2d, size2d_json from .test_size3d import size3d, size3d_json diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index 2d73b01..f4404f4 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -20,6 +20,16 @@ def point3d() -> dict: return Point3d(419, 3.14, 0) +@pytest.fixture +def another_point3d_json() -> dict: + return [419.2, 3.34, 0.2] + + +@pytest.fixture +def another_point3d() -> dict: + return Point3d(419.2, 3.34, 0.2) + + # == Tests ============================ @@ -28,5 +38,10 @@ def test_from_json(point3d, point3d_json): assert actual == point3d +def test_from_json__another(another_point3d, another_point3d_json): + actual = Point3d.from_json(another_point3d_json) + assert actual == another_point3d + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index d7d72df..a1b6bbb 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -21,7 +21,7 @@ def poly2d_json( ) -> JSONPoly2d: return JSONPoly2d( uid="78f0ad89-2750-4a30-9d66-44c9da73a714", - name="lidar__poly2d__person", + name="rgb_middle__poly2d__person", closed=True, mode="MODE_POLY2D_ABSOLUTE", val=point2d_json + another_point2d_json, diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py new file mode 100644 index 0000000..78d6f84 --- /dev/null +++ b/tests/test_raillabel/format/test_poly3d.py @@ -0,0 +1,61 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Poly3d +from raillabel.json_format import JSONPoly3d + +# == Fixtures ========================= + + +@pytest.fixture +def poly3d_json( + point3d_json, + another_point3d_json, + attributes_multiple_types_json, +) -> JSONPoly3d: + return JSONPoly3d( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="lidar__poly3d__person", + closed=True, + val=point3d_json + another_point3d_json, + coordinate_system="lidar", + attributes=attributes_multiple_types_json, + ) + + +@pytest.fixture +def poly3d( + point3d, + another_point3d, + attributes_multiple_types, +) -> Poly3d: + return Poly3d( + points=[point3d, another_point3d], + closed=True, + sensor="lidar", + attributes=attributes_multiple_types, + object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + ) + + +# == Tests ============================ + + +def test_from_json(poly3d, poly3d_json): + actual = Poly3d.from_json(poly3d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + assert actual == poly3d + + +# def test_name(poly3d): +# actual = poly3d.name("person") +# assert actual == "lidar__poly3d__person" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 04f68acb69dc8d351869969d70398a281bc37eff Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:46:03 +0100 Subject: [PATCH 097/190] feat: implement Poly3d.name() --- raillabel/format/poly3d.py | 4 ++++ tests/test_raillabel/format/test_poly3d.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 0ee10f4..45d7f34 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -44,3 +44,7 @@ def from_json(cls, json: JSONPoly3d, object_uid: UUID) -> Poly3d: sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) + + def name(self, object_type: str) -> str: + """Return the name of the annotation used for indexing in the object data pointers.""" + return f"{self.sensor}__poly3d__{object_type}" diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 78d6f84..a8addd6 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -52,9 +52,9 @@ def test_from_json(poly3d, poly3d_json): assert actual == poly3d -# def test_name(poly3d): -# actual = poly3d.name("person") -# assert actual == "lidar__poly3d__person" +def test_name(poly3d): + actual = poly3d.name("person") + assert actual == "lidar__poly3d__person" if __name__ == "__main__": From 87d99f17f9aaf15ca8c71ee9b1cf448f7710da3d Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:52:00 +0100 Subject: [PATCH 098/190] feat: implement Seg3d.from_json() --- raillabel/format/seg3d.py | 52 ++++++++++----------- tests/test_raillabel/format/test_seg3d.py | 55 +++++++++++++++++++++++ 2 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 tests/test_raillabel/format/test_seg3d.py diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index e43166b..8bd1ccf 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -4,35 +4,35 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID -from ._object_annotation import _ObjectAnnotation +from raillabel.json_format import JSONVec + +from ._attributes import _attributes_from_json @dataclass -class Seg3d(_ObjectAnnotation): - """The 3D segmentation of a lidar pointcloud. - - Parameters - ---------- - uid: str - This a string representing the unique universal identifier of the annotation. - point_ids: list of int - The list of point indices. - object: raillabel.format.Object - A reference to the object, this annotation belongs to. - sensor: raillabel.format.Sensor - A reference to the sensor, this annotation is labeled in. Default is None. - attributes: dict, optional - Attributes of the annotation. Dict keys are the name str of the attribute, values are the - attribute values. Default is {}. - - Properties (read-only) - ---------------------- - name: str - Name of the annotation used by the VCD player for indexing in the object data pointers. - - """ +class Seg3d: + """The 3D segmentation of a lidar pointcloud.""" point_ids: list[int] - - OPENLABEL_ID = "vec" + "The list of point indices." + + object: UUID + "The uid of the object, this annotation belongs to." + + sensor: str + "The uid of the sensor, this annotation is labeled in." + + attributes: dict[str, float | bool | str | list] + "Additional information associated with the annotation." + + @classmethod + def from_json(cls, json: JSONVec, object_uid: UUID) -> Seg3d: + """Construct an instant of this class from RailLabel JSON data.""" + return Seg3d( + point_ids=[int(point_id) for point_id in json.val], + object=object_uid, + sensor=json.coordinate_system, + attributes=_attributes_from_json(json.attributes), + ) diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py new file mode 100644 index 0000000..49ddd04 --- /dev/null +++ b/tests/test_raillabel/format/test_seg3d.py @@ -0,0 +1,55 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Seg3d +from raillabel.json_format import JSONVec + +# == Fixtures ========================= + + +@pytest.fixture +def seg3d_json( + attributes_multiple_types_json, +) -> JSONVec: + return JSONVec( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="rgb_middle__seg3d__person", + val=[1234, 5678], + coordinate_system="lidar", + attributes=attributes_multiple_types_json, + ) + + +@pytest.fixture +def seg3d( + attributes_multiple_types, +) -> Seg3d: + return Seg3d( + point_ids=[1234, 5678], + sensor="lidar", + attributes=attributes_multiple_types, + object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + ) + + +# == Tests ============================ + + +def test_from_json(seg3d, seg3d_json): + actual = Seg3d.from_json(seg3d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + assert actual == seg3d + + +# def test_name(seg3d): +# actual = seg3d.name("person") +# assert actual == "rgb_middle__seg3d__person" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 4e7b08a991e96d4f287717ced19b5f533d442ced Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 15:53:07 +0100 Subject: [PATCH 099/190] feat: implement Seg3d.name() --- raillabel/format/seg3d.py | 4 ++++ tests/test_raillabel/format/test_seg3d.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 8bd1ccf..c4647e0 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -36,3 +36,7 @@ def from_json(cls, json: JSONVec, object_uid: UUID) -> Seg3d: sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) + + def name(self, object_type: str) -> str: + """Return the name of the annotation used for indexing in the object data pointers.""" + return f"{self.sensor}__vec__{object_type}" diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index 49ddd04..a98262a 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -46,9 +46,9 @@ def test_from_json(seg3d, seg3d_json): assert actual == seg3d -# def test_name(seg3d): -# actual = seg3d.name("person") -# assert actual == "rgb_middle__seg3d__person" +def test_name(seg3d): + actual = seg3d.name("person") + assert actual == "lidar__vec__person" if __name__ == "__main__": From 06c5b77e8188d0ba721214a605c4e47334d62804 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 16:00:36 +0100 Subject: [PATCH 100/190] feat: implement Num.from_dict() --- raillabel/format/num.py | 39 ++++++++++------------ raillabel/json_format/num.py | 4 --- tests/test_raillabel/format/test_num.py | 44 +++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 25 deletions(-) create mode 100644 tests/test_raillabel/format/test_num.py diff --git a/raillabel/format/num.py b/raillabel/format/num.py index 243c65b..d92aff6 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -5,30 +5,27 @@ from dataclasses import dataclass -from .sensor import Sensor +from raillabel.json_format import JSONNum @dataclass class Num: - """A number. + """A number.""" - Parameters - ---------- - uid: str - This a string representing the unique universal identifier of the annotation. name: str - Human readable name describing the annotation. - val: int or float - This is the value of the number object. - attributes: dict, optional - Attributes of the annotation. Dict keys are the uid str of the attribute, values are the - attribute values. Default is {}. - sensor: raillabel.format.Sensor - A reference to the sensor, this value is represented in. Default is None. - - """ - - uid: str - name: str - val: int | float - sensor: Sensor + "Human readable name describing the annotation." + + val: float + "This is the value of the number object." + + sensor: str | None + "A reference to the sensor, this value is represented in." + + @classmethod + def from_json(cls, json: JSONNum) -> Num: + """Construct an instant of this class from RailLabel JSON data.""" + return Num( + name=json.name, + val=json.val, + sensor=json.coordinate_system, + ) diff --git a/raillabel/json_format/num.py b/raillabel/json_format/num.py index 75d08d4..b51ac76 100644 --- a/raillabel/json_format/num.py +++ b/raillabel/json_format/num.py @@ -7,8 +7,6 @@ from pydantic import BaseModel -from .attributes import JSONAttributes - class JSONNum(BaseModel): """A number.""" @@ -25,5 +23,3 @@ class JSONNum(BaseModel): uid: UUID | None = None "This is a string encoding the Universal Unique identifyer of the annotation." - - attributes: JSONAttributes | None = None diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py new file mode 100644 index 0000000..dcde12d --- /dev/null +++ b/tests/test_raillabel/format/test_num.py @@ -0,0 +1,44 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from uuid import UUID + +import pytest + +from raillabel.format import Num +from raillabel.json_format import JSONNum + +# == Fixtures ========================= + + +@pytest.fixture +def num_json() -> JSONNum: + return JSONNum( + uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + name="velocity", + val=49.21321, + coordinate_system="gps_imu", + ) + + +@pytest.fixture +def num() -> Num: + return Num( + sensor="gps_imu", + name="velocity", + val=49.21321, + ) + + +# == Tests ============================ + + +def test_from_json(num, num_json): + actual = Num.from_json(num_json) + assert actual == num + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From cb5afee252f5259e0d6bd3573a64261fbf27651a Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 16:12:29 +0100 Subject: [PATCH 101/190] feat: implement StreamReference.from_dict() --- raillabel/format/sensor_reference.py | 36 ++++++++--------- .../format/test_stream_reference.py | 39 +++++++++++++++++++ 2 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 tests/test_raillabel/format/test_stream_reference.py diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index 5255832..34c37ae 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -3,29 +3,27 @@ from __future__ import annotations -import decimal from dataclasses import dataclass +from decimal import Decimal -from .sensor import Sensor +from raillabel.json_format import JSONStreamSync @dataclass class SensorReference: - """A reference to a sensor in a specific frame. - - Parameters - ---------- - sensor: raillabel.format.Sensor - The sensor this SensorReference corresponds to. - timestamp: decimal.Decimal - Timestamp containing the Unix epoch time of the sensor in a specific frame with up to - nanosecond precision. - uri: str, optional - URI to the file corresponding to the frame recording in the particular frame. Default is - None. - - """ - - sensor: Sensor - timestamp: decimal.Decimal + """A reference to a sensor in a specific frame.""" + + timestamp: Decimal + """Timestamp containing the Unix epoch time of the sensor in a specific frame with up to + nanosecond precision.""" + uri: str | None = None + "URI to the file corresponding to the frame recording in the particular frame." + + @classmethod + def from_json(cls, json: JSONStreamSync) -> SensorReference: + """Construct an instant of this class from RailLabel JSON data.""" + return SensorReference( + timestamp=Decimal(json.stream_properties.sync.timestamp), + uri=json.uri, + ) diff --git a/tests/test_raillabel/format/test_stream_reference.py b/tests/test_raillabel/format/test_stream_reference.py new file mode 100644 index 0000000..087890e --- /dev/null +++ b/tests/test_raillabel/format/test_stream_reference.py @@ -0,0 +1,39 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest +from decimal import Decimal + +from raillabel.format import SensorReference +from raillabel.json_format import JSONStreamSync, JSONStreamSyncProperties, JSONStreamSyncTimestamp + +# == Fixtures ========================= + + +@pytest.fixture +def sensor_reference_json() -> JSONStreamSync: + return JSONStreamSync( + stream_properties=JSONStreamSyncProperties( + sync=JSONStreamSyncTimestamp(timestamp="1631337747.123123123") + ), + uri="/uri/to/file.png", + ) + + +@pytest.fixture +def sensor_reference() -> SensorReference: + return SensorReference(timestamp=Decimal("1631337747.123123123"), uri="/uri/to/file.png") + + +# == Tests ============================ + + +def test_from_json(sensor_reference, sensor_reference_json): + actual = SensorReference.from_json(sensor_reference_json) + assert actual == sensor_reference + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 691304ecfba2416c0d1c8ad24f69fd8650eee74c Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 11 Nov 2024 17:09:26 +0100 Subject: [PATCH 102/190] feat: implement Frame.from_json() --- raillabel/format/frame.py | 138 ++++++++++++------ raillabel/json_format/__init__.py | 4 +- tests/test_raillabel/format/conftest.py | 12 ++ tests/test_raillabel/format/test_bbox.py | 7 +- tests/test_raillabel/format/test_cuboid.py | 7 +- tests/test_raillabel/format/test_frame.py | 92 ++++++++++++ tests/test_raillabel/format/test_poly2d.py | 11 +- tests/test_raillabel/format/test_poly3d.py | 11 +- tests/test_raillabel/format/test_seg3d.py | 7 +- ..._reference.py => test_sensor_reference.py} | 20 +++ 10 files changed, 256 insertions(+), 53 deletions(-) create mode 100644 tests/test_raillabel/format/test_frame.py rename tests/test_raillabel/format/{test_stream_reference.py => test_sensor_reference.py} (62%) diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index a8ac366..168c658 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -3,61 +3,113 @@ from __future__ import annotations -import decimal from dataclasses import dataclass, field +from decimal import Decimal +from uuid import UUID -from ._object_annotation import _ObjectAnnotation +from raillabel.json_format import JSONFrame, JSONFrameProperties, JSONObjectData + +from .bbox import Bbox +from .cuboid import Cuboid from .num import Num +from .poly2d import Poly2d +from .poly3d import Poly3d +from .seg3d import Seg3d from .sensor_reference import SensorReference @dataclass class Frame: - """A container of dynamic, timewise, information. - - Parameters - ---------- - timestamp: decimal.Decimal, optional - Timestamp containing the Unix epoch time of the frame with up to nanosecond precision. - sensors: dict of raillabel.format.SensorReference, optional - References to the sensors with frame specific information like timestamp and uri. - Default is {}. - frame_data: dict, optional - Dictionary containing data directly connected to the frame and not to any object, like - gps/imu data. Dictionary keys are the ID-strings of the variable the data belongs to. - Default is {}. - annotations: dict[str, _ObjectAnnotation subclass], optional - Dictionary containing all annotations of this frame. Keys are annotation uids. - - Read-Only Attributes - -------------------- - object_data: dict[str, dict[str, _ObjectAnnotation subclass]] - Annotations categorized by object. Keys are object uids and values are the annotations - as a dict, that are part of the object. - - """ - - timestamp: decimal.Decimal | None = None + """A container of dynamic, timewise, information.""" + + timestamp: Decimal | None = None + "Timestamp containing the Unix epoch time of the frame with up to nanosecond precision." + sensors: dict[str, SensorReference] = field(default_factory=dict) + "References to the sensors with frame specific information like timestamp and uri." + frame_data: dict[str, Num] = field(default_factory=dict) - annotations: dict[str, type[_ObjectAnnotation]] = field(default_factory=dict) + """Dictionary containing data directly connected to the frame and not to any object, like + gps/imu data. Dictionary keys are the ID-strings of the variable the data belongs to.""" + + annotations: dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d] = field(default_factory=dict) + "All annotations of this frame." + + @classmethod + def from_json(cls, json: JSONFrame) -> Frame: + """Construct an instant of this class from RailLabel JSON data.""" + return Frame( + timestamp=_timestamp_from_dict(json.frame_properties), + sensors=_sensors_from_dict(json.frame_properties), + frame_data=_frame_data_from_dict(json.frame_properties), + annotations=_annotations_from_json(json.objects), + ) + + +def _timestamp_from_dict(frame_properties: JSONFrameProperties | None) -> Decimal | None: + if frame_properties is None: + return None + + if frame_properties.timestamp is None: + return None + + return Decimal(frame_properties.timestamp) + + +def _sensors_from_dict(frame_properties: JSONFrameProperties | None) -> dict[str, SensorReference]: + if frame_properties is None: + return {} + + if frame_properties.streams is None: + return {} + + return { + sensor_id: SensorReference.from_json(sensor_ref) + for sensor_id, sensor_ref in frame_properties.streams.items() + } + + +def _frame_data_from_dict(frame_properties: JSONFrameProperties | None) -> dict[str, Num]: + if frame_properties is None: + return {} + + if frame_properties.frame_data is None: + return {} + + if frame_properties.frame_data.num is None: + return {} + + return {num.name: Num.from_json(num) for num in frame_properties.frame_data.num} + + +def _annotations_from_json( + json_object_data: dict[UUID, JSONObjectData] | None, +) -> dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d]: + if json_object_data is None: + return {} + + annotations: dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d] = {} + + for object_uid, object_data in json_object_data.items(): + for json_bbox in _resolve_none_to_empty_list(object_data.bbox): + annotations[json_bbox.uid] = Bbox.from_json(json_bbox, object_uid) + + for json_cuboid in _resolve_none_to_empty_list(object_data.cuboid): + annotations[json_cuboid.uid] = Cuboid.from_json(json_cuboid, object_uid) + + for json_poly2d in _resolve_none_to_empty_list(object_data.poly2d): + annotations[json_poly2d.uid] = Poly2d.from_json(json_poly2d, object_uid) - @property - def object_data(self) -> dict[str, dict[str, type[_ObjectAnnotation]]]: - """Return annotations categorized by Object-Id. + for json_poly3d in _resolve_none_to_empty_list(object_data.poly3d): + annotations[json_poly3d.uid] = Poly3d.from_json(json_poly3d, object_uid) - Returns - ------- - dict[str, dict[UUID, _ObjectAnnotation subclass]] - Dictionary of annotations. Keys are object uids and values are annotations, that are - contained in the object. + for json_seg3d in _resolve_none_to_empty_list(object_data.vec): + annotations[json_seg3d.uid] = Seg3d.from_json(json_seg3d, object_uid) - """ - object_data: dict[str, dict[str, type[_ObjectAnnotation]]] = {} - for ann_id, annotation in self.annotations.items(): - if annotation.object.uid not in object_data: - object_data[annotation.object.uid] = {} + return annotations - object_data[annotation.object.uid][ann_id] = annotation - return object_data +def _resolve_none_to_empty_list(optional_list: list | None) -> list: + if optional_list is None: + return [] + return optional_list diff --git a/raillabel/json_format/__init__.py b/raillabel/json_format/__init__.py index 4093a69..059d7d1 100644 --- a/raillabel/json_format/__init__.py +++ b/raillabel/json_format/__init__.py @@ -8,7 +8,7 @@ from .coordinate_system import JSONCoordinateSystem from .cuboid import JSONCuboid from .element_data_pointer import JSONElementDataPointer -from .frame import JSONFrame +from .frame import JSONFrame, JSONFrameData, JSONFrameProperties from .frame_interval import JSONFrameInterval from .metadata import JSONMetadata from .num import JSONNum @@ -36,6 +36,8 @@ "JSONElementDataPointer", "JSONFrameInterval", "JSONFrame", + "JSONFrameData", + "JSONFrameProperties", "JSONMetadata", "JSONNumAttribute", "JSONNum", diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 091014f..00b915b 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -1,12 +1,24 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 from .test_attributes import attributes_multiple_types, attributes_multiple_types_json +from .test_bbox import bbox, bbox_json, bbox_uid +from .test_cuboid import cuboid, cuboid_json, cuboid_uid from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json +from .test_num import num, num_json from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json +from .test_poly2d import poly2d, poly2d_json, poly2d_uid +from .test_poly3d import poly3d, poly3d_json, poly3d_uid from .test_quaternion import quaternion, quaternion_json from .test_size2d import size2d, size2d_json from .test_size3d import size3d, size3d_json +from .test_seg3d import seg3d, seg3d_json, seg3d_uid +from .test_sensor_reference import ( + another_sensor_reference, + another_sensor_reference_json, + sensor_reference, + sensor_reference_json, +) from .test_transform import transform, transform_json diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index bfb809b..b615508 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -20,7 +20,7 @@ def bbox_json( size2d_json, ) -> JSONBbox: return JSONBbox( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid="2811f67c-124C-4fac-a275-20807d0471de", name="rgb_middle__bbox__person", val=point2d_json + size2d_json, coordinate_system="rgb_middle", @@ -28,6 +28,11 @@ def bbox_json( ) +@pytest.fixture +def bbox_uid() -> UUID: + return UUID("2811f67c-124C-4fac-a275-20807d0471de") + + @pytest.fixture def bbox( point2d, diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index ae20150..b8937b3 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -21,7 +21,7 @@ def cuboid_json( size3d_json, ) -> JSONCuboid: return JSONCuboid( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid="51def938-20BA-4699-95be-d6330c44cb77", name="lidar__cuboid__person", val=point3d_json + quaternion_json + size3d_json, coordinate_system="lidar", @@ -29,6 +29,11 @@ def cuboid_json( ) +@pytest.fixture +def cuboid_uid() -> UUID: + return UUID("51def938-20BA-4699-95be-d6330c44cb77") + + @pytest.fixture def cuboid( point3d, diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py new file mode 100644 index 0000000..26af71d --- /dev/null +++ b/tests/test_raillabel/format/test_frame.py @@ -0,0 +1,92 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from decimal import Decimal + +import pytest + +from raillabel.format import Frame +from raillabel.json_format import JSONFrame, JSONFrameData, JSONFrameProperties, JSONObjectData + +# == Fixtures ========================= + + +@pytest.fixture +def frame_json( + sensor_reference_json, + another_sensor_reference_json, + num_json, + bbox_json, + cuboid_json, + poly2d_json, + poly3d_json, + seg3d_json, +) -> JSONFrame: + return JSONFrame( + frame_properties=JSONFrameProperties( + timestamp=Decimal("1631337747.123123123"), + streams={ + "rgb_middle": sensor_reference_json, + "lidar": another_sensor_reference_json, + }, + frame_data=JSONFrameData(num=[num_json]), + ), + objects={ + "cfcf9750-3bc3-4077-9079-a82c0c63976a": JSONObjectData( + poly2d=[poly2d_json], + poly3d=[poly3d_json], + ), + "b40ba3ad-0327-46ff-9c28-2506cfd6d934": JSONObjectData( + bbox=[bbox_json], + cuboid=[cuboid_json], + vec=[seg3d_json], + ), + }, + ) + + +@pytest.fixture +def frame( + sensor_reference, + another_sensor_reference, + num, + bbox, + bbox_uid, + cuboid, + cuboid_uid, + poly2d, + poly2d_uid, + poly3d, + poly3d_uid, + seg3d, + seg3d_uid, +) -> dict: + return Frame( + timestamp=Decimal("1631337747.123123123"), + sensors={ + "rgb_middle": sensor_reference, + "lidar": another_sensor_reference, + }, + frame_data={num.name: num}, + annotations={ + bbox_uid: bbox, + cuboid_uid: cuboid, + poly2d_uid: poly2d, + poly3d_uid: poly3d, + seg3d_uid: seg3d, + }, + ) + + +# == Tests ============================ + + +def test_from_json(frame, frame_json): + actual = Frame.from_json(frame_json) + assert actual == frame + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index a1b6bbb..f707bd9 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -20,7 +20,7 @@ def poly2d_json( attributes_multiple_types_json, ) -> JSONPoly2d: return JSONPoly2d( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid="013e7b34-62E5-435c-9412-87318c50f6d8", name="rgb_middle__poly2d__person", closed=True, mode="MODE_POLY2D_ABSOLUTE", @@ -30,6 +30,11 @@ def poly2d_json( ) +@pytest.fixture +def poly2d_uid() -> UUID: + return UUID("013e7b34-62E5-435c-9412-87318c50f6d8") + + @pytest.fixture def poly2d( point2d, @@ -41,7 +46,7 @@ def poly2d( closed=True, sensor="rgb_middle", attributes=attributes_multiple_types, - object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + object=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a"), ) @@ -49,7 +54,7 @@ def poly2d( def test_from_json(poly2d, poly2d_json): - actual = Poly2d.from_json(poly2d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + actual = Poly2d.from_json(poly2d_json, object_uid=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a")) assert actual == poly2d diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index a8addd6..9fa0a6c 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -20,7 +20,7 @@ def poly3d_json( attributes_multiple_types_json, ) -> JSONPoly3d: return JSONPoly3d( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid="0da87210-46F1-40e5-b661-20ea1c392f50", name="lidar__poly3d__person", closed=True, val=point3d_json + another_point3d_json, @@ -29,6 +29,11 @@ def poly3d_json( ) +@pytest.fixture +def poly3d_uid() -> UUID: + return UUID("0da87210-46F1-40e5-b661-20ea1c392f50") + + @pytest.fixture def poly3d( point3d, @@ -40,7 +45,7 @@ def poly3d( closed=True, sensor="lidar", attributes=attributes_multiple_types, - object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + object=UUID("cfcf9750-3bc3-4077-9079-a82c0c63976a"), ) @@ -48,7 +53,7 @@ def poly3d( def test_from_json(poly3d, poly3d_json): - actual = Poly3d.from_json(poly3d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) + actual = Poly3d.from_json(poly3d_json, object_uid=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a")) assert actual == poly3d diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index a98262a..f025e10 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -18,7 +18,7 @@ def seg3d_json( attributes_multiple_types_json, ) -> JSONVec: return JSONVec( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid="d52e2b25-0B48-4899-86d5-4bc41be6b7d3", name="rgb_middle__seg3d__person", val=[1234, 5678], coordinate_system="lidar", @@ -26,6 +26,11 @@ def seg3d_json( ) +@pytest.fixture +def seg3d_uid() -> UUID: + return UUID("d52e2b25-0B48-4899-86d5-4bc41be6b7d3") + + @pytest.fixture def seg3d( attributes_multiple_types, diff --git a/tests/test_raillabel/format/test_stream_reference.py b/tests/test_raillabel/format/test_sensor_reference.py similarity index 62% rename from tests/test_raillabel/format/test_stream_reference.py rename to tests/test_raillabel/format/test_sensor_reference.py index 087890e..8078274 100644 --- a/tests/test_raillabel/format/test_stream_reference.py +++ b/tests/test_raillabel/format/test_sensor_reference.py @@ -27,6 +27,21 @@ def sensor_reference() -> SensorReference: return SensorReference(timestamp=Decimal("1631337747.123123123"), uri="/uri/to/file.png") +@pytest.fixture +def another_sensor_reference_json() -> JSONStreamSync: + return JSONStreamSync( + stream_properties=JSONStreamSyncProperties( + sync=JSONStreamSyncTimestamp(timestamp="1631337747.103123123") + ), + uri="/uri/to/file.pcd", + ) + + +@pytest.fixture +def another_sensor_reference() -> SensorReference: + return SensorReference(timestamp=Decimal("1631337747.103123123"), uri="/uri/to/file.pcd") + + # == Tests ============================ @@ -35,5 +50,10 @@ def test_from_json(sensor_reference, sensor_reference_json): assert actual == sensor_reference +def test_from_json(another_sensor_reference, another_sensor_reference_json): + actual = SensorReference.from_json(another_sensor_reference_json) + assert actual == another_sensor_reference + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From e7b420112de9c8276bc358189272758dc1e21b03 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 17:29:24 +0100 Subject: [PATCH 103/190] refactor: remove unused parent class --- raillabel/format/__init__.py | 3 - raillabel/format/_object_annotation.py | 101 ------------------------- 2 files changed, 104 deletions(-) delete mode 100644 raillabel/format/_object_annotation.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 5761cc8..583d985 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 """Module containing all relevant format classes.""" -from ._object_annotation import _ObjectAnnotation, annotation_classes from .bbox import Bbox from .cuboid import Cuboid from .element_data_pointer import ElementDataPointer @@ -27,8 +26,6 @@ from .transform import Transform __all__ = [ - "_ObjectAnnotation", - "annotation_classes", "Bbox", "Cuboid", "ElementDataPointer", diff --git a/raillabel/format/_object_annotation.py b/raillabel/format/_object_annotation.py deleted file mode 100644 index c9d09d4..0000000 --- a/raillabel/format/_object_annotation.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from abc import ABC, abstractproperty -from dataclasses import dataclass -from importlib import import_module -from inspect import isclass -from pathlib import Path -from pkgutil import iter_modules -from typing import Any - -from ._attribute_type import AttributeType -from .object import Object -from .sensor import Sensor - - -@dataclass -class _ObjectAnnotation(ABC): - uid: str - object: Object - sensor: Sensor - attributes: dict[str, int | float | bool | str | list] - - @property - def name(self) -> str: - return f"{self.sensor.uid}__{self.OPENLABEL_ID}__{self.object.type}" - - @property - @abstractproperty - def OPENLABEL_ID(self) -> list[str] | str: - raise NotImplementedError - - # === Private Methods ==================================================== - - def _annotation_required_fields_asdict(self) -> dict: - """Return the required fields from the parent class to dict.""" - return { - "uid": str(self.uid), - "name": str(self.name), - } - - def _annotation_optional_fields_asdict(self) -> dict[str, Any]: - """Return the optional fields from the parent class to dict.""" - dict_repr: dict[str, Any] = {} - - if self.sensor is not None: - dict_repr["coordinate_system"] = str(self.sensor.uid) - - if self.attributes != {}: - dict_repr["attributes"] = self._attributes_asdict(self.attributes) - - return dict_repr - - def _attributes_asdict(self, attributes: dict[str, Any]) -> dict[str, Any]: - attributes_dict: dict[str, Any] = {} - - for attr_name, attr_value in attributes.items(): - attr_type = AttributeType.from_value(type(attr_value)).value - - if attr_type not in attributes_dict: - attributes_dict[attr_type] = [] - - attributes_dict[attr_type].append({"name": attr_name, "val": attr_value}) - - return attributes_dict - - @classmethod - def _coordinate_system_fromdict(cls, data_dict: dict, sensors: dict) -> Sensor: - return sensors[data_dict["coordinate_system"]] - - @classmethod - def _attributes_fromdict( - cls, - data_dict: dict, - ) -> dict[str, int | float | bool | str | list]: - if "attributes" not in data_dict: - return {} - - return {a["name"]: a["val"] for type_ in data_dict["attributes"].values() for a in type_} - - -def annotation_classes() -> dict[str, type[_ObjectAnnotation]]: - """Return dictionary with _Annotation child classes.""" - out = {} - - package_dir = str(Path(__file__).resolve().parent) - for _, module_name, _ in iter_modules([package_dir]): - module = import_module(f"raillabel.format.{module_name}") - for attribute_name in dir(module): - attribute = getattr(module, attribute_name) - - if ( - isclass(attribute) - and issubclass(attribute, _ObjectAnnotation) - and attribute != _ObjectAnnotation - ): - out[attribute.OPENLABEL_ID] = attribute - - return out # type: ignore From dd864a470782e2691fa78b6445860c2d7ec3155f Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 17:32:37 +0100 Subject: [PATCH 104/190] refactor: remove legacy files --- raillabel/format/_attribute_type.py | 57 ------------------------ raillabel/format/element_data_pointer.py | 36 --------------- 2 files changed, 93 deletions(-) delete mode 100644 raillabel/format/_attribute_type.py delete mode 100644 raillabel/format/element_data_pointer.py diff --git a/raillabel/format/_attribute_type.py b/raillabel/format/_attribute_type.py deleted file mode 100644 index 526c5e2..0000000 --- a/raillabel/format/_attribute_type.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from enum import Enum - - -class AttributeType(Enum): - """Enum of all valid RailLabel attribute types.""" - - TEXT = "text" - NUM = "num" - BOOLEAN = "boolean" - VEC = "vec" - - @classmethod - def from_value(cls, attribute_value_class: type) -> AttributeType: - """Return AttributeType based on class of attribute value. - - Parameters - ---------- - attribute_value_class: type - Class of the attribute value. Can be gathered by calling type()-function. - - Returns - ------- - AttributeType - Corresponding AttributeType. - - Raises - ------ - UnsupportedAttributeTypeError - if attribute value class does not correspond to an Attribute Type. - - """ - if attribute_value_class is str: - return AttributeType.TEXT - - if attribute_value_class in [float, int]: - return AttributeType.NUM - - if attribute_value_class is bool: - return AttributeType.BOOLEAN - - if attribute_value_class in [list, tuple]: - return AttributeType.VEC - - raise UnsupportedAttributeTypeError(attribute_value_class) - - -class UnsupportedAttributeTypeError(ValueError): - def __init__(self, attribute_value_class: type) -> None: - super().__init__( - f"Type {attribute_value_class} does not correspond to a valid RailLabel attribute " - "type. Supported types are str, float, int, bool, list, tuple." - ) diff --git a/raillabel/format/element_data_pointer.py b/raillabel/format/element_data_pointer.py deleted file mode 100644 index d3f23fc..0000000 --- a/raillabel/format/element_data_pointer.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from dataclasses import dataclass - -from ._attribute_type import AttributeType -from .frame_interval import FrameInterval - - -@dataclass -class ElementDataPointer: - """Container of pointers for annotations indexed by the "name" property. - - Used for indexing annotations for easy referencing without loading all of them. - - Parameters - ---------- - uid: str - Unique identifier of the ElementDataPointer assembled using a specific schema. - frame_intervals: list[raillabel.format.FrameInterval] - Frame intervals, this element data pointer is contained in. - attribute_pointers: dict[str, raillabel.util._attribute_type.AttributeType] - References of attributes contained in the referenced annotations with attribute type. - - Properties (read-only) - ---------------------- - uid: str - Unique identifier of the ElementDataPointer built from the attributes. - - """ - - uid: str - frame_intervals: list[FrameInterval] - attribute_pointers: dict[str, AttributeType] From 7f3cc6de0d75ced7395f362f155f1823da9d7bba Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 17:40:46 +0100 Subject: [PATCH 105/190] feat: implement Object.from_json() --- raillabel/format/__init__.py | 1 - raillabel/format/object.py | 27 +++++++-------- tests/test_raillabel/format/test_object.py | 39 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 tests/test_raillabel/format/test_object.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 583d985..2ef5f95 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -4,7 +4,6 @@ from .bbox import Bbox from .cuboid import Cuboid -from .element_data_pointer import ElementDataPointer from .frame import Frame from .frame_interval import FrameInterval from .intrinsics_pinhole import IntrinsicsPinhole diff --git a/raillabel/format/object.py b/raillabel/format/object.py index 9faa0c3..a0b9989 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -5,23 +5,24 @@ from dataclasses import dataclass +from raillabel.json_format import JSONObject + @dataclass class Object: - """Physical, unique object in the data, that can be tracked via its UID. + """Physical, unique object in the data, that can be tracked via its UID.""" - Parameters - ---------- - uid: str - This a string representing the unique universal identifier for the object. name: str - Name of the object. It is a friendly name and not used for indexing. Commonly the class - name is used followed by an underscore and an integer (i.e. person_0032). - type: str - The type of an object defines the class the object corresponds to. - - """ + """Name of the object. It is a friendly name and not used for indexing. Commonly the class name + is used followed by an underscore and an integer (i.e. person_0032).""" - uid: str - name: str type: str + "The type of an object defines the class the object corresponds to (like 'person')." + + @classmethod + def from_json(cls, json: JSONObject) -> Object: + """Construct an instant of this class from RailLabel JSON data.""" + return Object( + name=json.name, + type=json.type, + ) diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py new file mode 100644 index 0000000..65d37a5 --- /dev/null +++ b/tests/test_raillabel/format/test_object.py @@ -0,0 +1,39 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.json_format import JSONObject +from raillabel.format import Object + +# == Fixtures ========================= + + +@pytest.fixture +def object_person_json() -> JSONObject: + return JSONObject( + name="person0032", + type="person", + ) + + +@pytest.fixture +def object_person() -> Object: + return Object( + name="person0032", + type="person", + ) + + +# == Tests ============================ + + +def test_from_json(object_person, object_person_json): + actual = Object.from_json(object_person_json) + assert actual == object_person + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 33d3f7e82a9657df7ea9ae1fdf0cfd02e8b7dd71 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 19:59:19 +0100 Subject: [PATCH 106/190] feat: implement Camera.from_json() --- raillabel/format/__init__.py | 5 +- raillabel/format/camera.py | 46 ++++++++++++++++++ tests/test_raillabel/format/test_camera.py | 55 ++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 raillabel/format/camera.py create mode 100644 tests/test_raillabel/format/test_camera.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 2ef5f95..4346f99 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -3,6 +3,7 @@ """Module containing all relevant format classes.""" from .bbox import Bbox +from .camera import Camera from .cuboid import Cuboid from .frame import Frame from .frame_interval import FrameInterval @@ -18,7 +19,6 @@ from .quaternion import Quaternion from .scene import Scene from .seg3d import Seg3d -from .sensor import Sensor, SensorType from .sensor_reference import SensorReference from .size2d import Size2d from .size3d import Size3d @@ -26,6 +26,7 @@ __all__ = [ "Bbox", + "Camera", "Cuboid", "ElementDataPointer", "Frame", @@ -42,8 +43,6 @@ "Quaternion", "Scene", "Seg3d", - "Sensor", - "SensorType", "SensorReference", "Size2d", "Size3d", diff --git a/raillabel/format/camera.py b/raillabel/format/camera.py new file mode 100644 index 0000000..3b67a45 --- /dev/null +++ b/raillabel/format/camera.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamCamera, JSONTransformData + +from .intrinsics_pinhole import IntrinsicsPinhole +from .transform import Transform + + +@dataclass +class Camera: + """A camera sensor.""" + + intrinsics: IntrinsicsPinhole + "The intrinsic calibration of the sensor." + + extrinsics: Transform | None = None + "External calibration of the sensor defined by the 3D transform to the coordinate system origin." + + uri: str | None = None + "Name of the subdirectory containing the sensor files." + + description: str | None = None + "Additional information about the sensor." + + @classmethod + def from_json( + cls, json_stream: JSONStreamCamera, json_coordinate_system: JSONCoordinateSystem + ) -> Camera: + """Construct an instant of this class from RailLabel JSON data.""" + return Camera( + intrinsics=IntrinsicsPinhole.from_json(json_stream.stream_properties.intrinsics_pinhole), + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) + + +def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: + if json_transform is None: + return None + return Transform.from_json(json_transform) diff --git a/tests/test_raillabel/format/test_camera.py b/tests/test_raillabel/format/test_camera.py new file mode 100644 index 0000000..e29a726 --- /dev/null +++ b/tests/test_raillabel/format/test_camera.py @@ -0,0 +1,55 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Camera +from raillabel.json_format import ( + JSONCoordinateSystem, + JSONStreamCamera, + JSONStreamCameraProperties, + JSONIntrinsicsPinhole, +) + +# == Fixtures ========================= + + +@pytest.fixture +def camera_json( + intrinsics_pinhole_json, transform_json +) -> tuple[JSONStreamCamera, JSONCoordinateSystem]: + return ( + JSONStreamCamera( + type="camera", + stream_properties=JSONStreamCameraProperties(intrinsics_pinhole=intrinsics_pinhole_json), + uri="/path/to/sensor/data", + description="A very nice camera", + ), + JSONCoordinateSystem( + parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + ), + ) + + +@pytest.fixture +def camera(intrinsics_pinhole, transform) -> dict: + return Camera( + intrinsics=intrinsics_pinhole, + extrinsics=transform, + uri="/path/to/sensor/data", + description="A very nice camera", + ) + + +# == Tests ============================ + + +def test_from_json__camera(camera, camera_json): + actual = Camera.from_json(camera_json[0], camera_json[1]) + assert actual == camera + + +if __name__ == "__main__": + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 995a110ec57093e506532f925c7544578cdafc7c Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 20:03:29 +0100 Subject: [PATCH 107/190] refactor: remove sensor class --- CHANGELOG.md | 1 + raillabel/format/scene.py | 4 +-- raillabel/format/sensor.py | 56 -------------------------------------- 3 files changed, 3 insertions(+), 58 deletions(-) delete mode 100644 raillabel/format/sensor.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 41db548..d37eb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,3 +109,4 @@ Other breaking changes: - `raillabel.format.Transform` fields have been changed by `pos -> position` and `quad -> quaternion` to make it more explicit - `raillabel.format.FrameInterval` fields have been changed by `frame_start -> start` and `frame_end -> end` to make it more concise - `raillabel.format.Frame.uid` field has been removed to avoid redundant information +- `raillabel.format.Sensor` has been removed in favor of the different sensor type classes `raillabel.format.Camera`, `raillabel.format.Lidar`, `raillabel.format.Radar` and `raillabel.format.GpsImu` diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 574351c..7eb05d3 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -5,11 +5,11 @@ from dataclasses import dataclass, field +from .camera import Camera from .frame import Frame from .frame_interval import FrameInterval from .metadata import Metadata from .object import Object -from .sensor import Sensor @dataclass @@ -35,7 +35,7 @@ class Scene: """ metadata: Metadata - sensors: dict[str, Sensor] = field(default_factory=dict) + sensors: dict[str, Camera] = field(default_factory=dict) objects: dict[str, Object] = field(default_factory=dict) frames: dict[int, Frame] = field(default_factory=dict) diff --git a/raillabel/format/sensor.py b/raillabel/format/sensor.py deleted file mode 100644 index f922cd1..0000000 --- a/raillabel/format/sensor.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from dataclasses import dataclass -from enum import Enum - -from .intrinsics_pinhole import IntrinsicsPinhole -from .intrinsics_radar import IntrinsicsRadar -from .transform import Transform - - -@dataclass -class Sensor: - """A reference to a physical sensor on the train. - - A sensor in the devkit corresponds to one coordinate_system and one stream in the data format. - This distinction is set by the OpenLABEL standard, but is not relevant for our data. - Therefore, we decided to combine these fields. - - Parameters - ---------- - uid: str - This is the friendly name of the sensor as well as its identifier. Must be - unique. - extrinsics: raillabel.format.Transform, optional - The external calibration of the sensor defined by the 3D transform to the coordinate - system origin. Default is None. - intrinsics: raillabel.format.IntrinsicsPinhole or raillabel.format.IntrinsicsRadar, optional - The intrinsic calibration of the sensor. Default is None. - type: raillabel.format.SensorType, optional - Information about the kind of sensor. Default is None. - uri: str, optional - Name of the subdirectory containing the sensor files. Default is None. - description: str, optional - Description of the sensor. Default is None. - - """ - - uid: str - extrinsics: Transform | None = None - intrinsics: IntrinsicsPinhole | IntrinsicsRadar | None = None - type: SensorType | None = None - uri: str | None = None - description: str | None = None - - -class SensorType(Enum): - """Enumeration representing all possible sensor types.""" - - CAMERA = "camera" - LIDAR = "lidar" - RADAR = "radar" - GPS_IMU = "gps_imu" - OTHER = "other" From f1fd41e08a93da0bf3ddb976b9dde36081b9e6ca Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 20:09:07 +0100 Subject: [PATCH 108/190] feat: implement Radar.from_json() --- raillabel/format/__init__.py | 2 + raillabel/format/radar.py | 46 ++++++++++++++++++ raillabel/format/scene.py | 3 +- tests/test_raillabel/format/test_camera.py | 2 +- tests/test_raillabel/format/test_radar.py | 55 ++++++++++++++++++++++ 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 raillabel/format/radar.py create mode 100644 tests/test_raillabel/format/test_radar.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 4346f99..8aee2cf 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -17,6 +17,7 @@ from .poly2d import Poly2d from .poly3d import Poly3d from .quaternion import Quaternion +from .radar import Radar from .scene import Scene from .seg3d import Seg3d from .sensor_reference import SensorReference @@ -41,6 +42,7 @@ "Poly2d", "Poly3d", "Quaternion", + "Radar", "Scene", "Seg3d", "SensorReference", diff --git a/raillabel/format/radar.py b/raillabel/format/radar.py new file mode 100644 index 0000000..3288c09 --- /dev/null +++ b/raillabel/format/radar.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamRadar, JSONTransformData + +from .intrinsics_radar import IntrinsicsRadar +from .transform import Transform + + +@dataclass +class Radar: + """A radar sensor.""" + + intrinsics: IntrinsicsRadar + "The intrinsic calibration of the sensor." + + extrinsics: Transform | None = None + "External calibration of the sensor defined by the 3D transform to the coordinate system origin." + + uri: str | None = None + "Name of the subdirectory containing the sensor files." + + description: str | None = None + "Additional information about the sensor." + + @classmethod + def from_json( + cls, json_stream: JSONStreamRadar, json_coordinate_system: JSONCoordinateSystem + ) -> Radar: + """Construct an instant of this class from RailLabel JSON data.""" + return Radar( + intrinsics=IntrinsicsRadar.from_json(json_stream.stream_properties.intrinsics_radar), + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) + + +def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: + if json_transform is None: + return None + return Transform.from_json(json_transform) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 7eb05d3..44ab918 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -10,6 +10,7 @@ from .frame_interval import FrameInterval from .metadata import Metadata from .object import Object +from .radar import Radar @dataclass @@ -35,7 +36,7 @@ class Scene: """ metadata: Metadata - sensors: dict[str, Camera] = field(default_factory=dict) + sensors: dict[str, Camera | Radar] = field(default_factory=dict) objects: dict[str, Object] = field(default_factory=dict) frames: dict[int, Frame] = field(default_factory=dict) diff --git a/tests/test_raillabel/format/test_camera.py b/tests/test_raillabel/format/test_camera.py index e29a726..8c9cf18 100644 --- a/tests/test_raillabel/format/test_camera.py +++ b/tests/test_raillabel/format/test_camera.py @@ -46,7 +46,7 @@ def camera(intrinsics_pinhole, transform) -> dict: # == Tests ============================ -def test_from_json__camera(camera, camera_json): +def test_from_json(camera, camera_json): actual = Camera.from_json(camera_json[0], camera_json[1]) assert actual == camera diff --git a/tests/test_raillabel/format/test_radar.py b/tests/test_raillabel/format/test_radar.py new file mode 100644 index 0000000..97cef37 --- /dev/null +++ b/tests/test_raillabel/format/test_radar.py @@ -0,0 +1,55 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Radar +from raillabel.json_format import ( + JSONCoordinateSystem, + JSONStreamRadar, + JSONStreamRadarProperties, + JSONIntrinsicsRadar, +) + +# == Fixtures ========================= + + +@pytest.fixture +def radar_json( + intrinsics_radar_json, transform_json +) -> tuple[JSONStreamRadar, JSONCoordinateSystem]: + return ( + JSONStreamRadar( + type="radar", + stream_properties=JSONStreamRadarProperties(intrinsics_radar=intrinsics_radar_json), + uri="/path/to/sensor/data", + description="A very nice radar", + ), + JSONCoordinateSystem( + parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + ), + ) + + +@pytest.fixture +def radar(intrinsics_radar, transform) -> dict: + return Radar( + intrinsics=intrinsics_radar, + extrinsics=transform, + uri="/path/to/sensor/data", + description="A very nice radar", + ) + + +# == Tests ============================ + + +def test_from_json(radar, radar_json): + actual = Radar.from_json(radar_json[0], radar_json[1]) + assert actual == radar + + +if __name__ == "__main__": + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From f0362c99605801bc9719f1c55cc56223abdb53ff Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 20:15:58 +0100 Subject: [PATCH 109/190] feat: implement Lidar.from_json() --- raillabel/format/__init__.py | 2 + .../format/_sensor_without_intrinsics.py | 41 +++++++++++++++++ raillabel/format/lidar.py | 8 ++++ tests/test_raillabel/format/test_lidar.py | 46 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 raillabel/format/_sensor_without_intrinsics.py create mode 100644 raillabel/format/lidar.py create mode 100644 tests/test_raillabel/format/test_lidar.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 8aee2cf..8dfd849 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -9,6 +9,7 @@ from .frame_interval import FrameInterval from .intrinsics_pinhole import IntrinsicsPinhole from .intrinsics_radar import IntrinsicsRadar +from .lidar import Lidar from .metadata import Metadata from .num import Num from .object import Object @@ -34,6 +35,7 @@ "FrameInterval", "IntrinsicsPinhole", "IntrinsicsRadar", + "Lidar", "Metadata", "Num", "Object", diff --git a/raillabel/format/_sensor_without_intrinsics.py b/raillabel/format/_sensor_without_intrinsics.py new file mode 100644 index 0000000..f08a035 --- /dev/null +++ b/raillabel/format/_sensor_without_intrinsics.py @@ -0,0 +1,41 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamRadar, JSONTransformData + +from .transform import Transform + + +@dataclass +class _SensorWithoutIntrinsics: + """Parent class of all sensors, that do not have an intrinsic calibration.""" + + extrinsics: Transform | None = None + "External calibration of the sensor defined by the 3D transform to the coordinate system origin." + + uri: str | None = None + "Name of the subdirectory containing the sensor files." + + description: str | None = None + "Additional information about the sensor." + + @classmethod + def from_json( + cls, json_stream: JSONStreamRadar, json_coordinate_system: JSONCoordinateSystem + ) -> _SensorWithoutIntrinsics: + """Construct an instant of this class from RailLabel JSON data.""" + return cls( + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) + + +def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: + if json_transform is None: + return None + return Transform.from_json(json_transform) diff --git a/raillabel/format/lidar.py b/raillabel/format/lidar.py new file mode 100644 index 0000000..40010ff --- /dev/null +++ b/raillabel/format/lidar.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from ._sensor_without_intrinsics import _SensorWithoutIntrinsics + + +class Lidar(_SensorWithoutIntrinsics): + """A lidar sensor.""" diff --git a/tests/test_raillabel/format/test_lidar.py b/tests/test_raillabel/format/test_lidar.py new file mode 100644 index 0000000..6fce3a8 --- /dev/null +++ b/tests/test_raillabel/format/test_lidar.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Lidar +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +# == Fixtures ========================= + + +@pytest.fixture +def lidar_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + return ( + JSONStreamOther( + type="lidar", + uri="/path/to/sensor/data", + description="A very nice lidar", + ), + JSONCoordinateSystem( + parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + ), + ) + + +@pytest.fixture +def lidar(transform) -> dict: + return Lidar( + extrinsics=transform, + uri="/path/to/sensor/data", + description="A very nice lidar", + ) + + +# == Tests ============================ + + +def test_from_json(lidar, lidar_json): + actual = Lidar.from_json(lidar_json[0], lidar_json[1]) + assert actual == lidar + + +if __name__ == "__main__": + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 6885445ea8e4f3ea9124a0833e6246fb68f77bee Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 20:17:44 +0100 Subject: [PATCH 110/190] feat: implement GpsImu.from_json() --- raillabel/format/__init__.py | 2 + raillabel/format/gps_imu.py | 8 ++++ tests/test_raillabel/format/test_gps_imu.py | 46 +++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 raillabel/format/gps_imu.py create mode 100644 tests/test_raillabel/format/test_gps_imu.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index 8dfd849..cb56171 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -7,6 +7,7 @@ from .cuboid import Cuboid from .frame import Frame from .frame_interval import FrameInterval +from .gps_imu import GpsImu from .intrinsics_pinhole import IntrinsicsPinhole from .intrinsics_radar import IntrinsicsRadar from .lidar import Lidar @@ -33,6 +34,7 @@ "ElementDataPointer", "Frame", "FrameInterval", + "GpsImu", "IntrinsicsPinhole", "IntrinsicsRadar", "Lidar", diff --git a/raillabel/format/gps_imu.py b/raillabel/format/gps_imu.py new file mode 100644 index 0000000..3d6a57a --- /dev/null +++ b/raillabel/format/gps_imu.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from ._sensor_without_intrinsics import _SensorWithoutIntrinsics + + +class GpsImu(_SensorWithoutIntrinsics): + """A gps sensor with inertial measurement unit.""" diff --git a/tests/test_raillabel/format/test_gps_imu.py b/tests/test_raillabel/format/test_gps_imu.py new file mode 100644 index 0000000..e918878 --- /dev/null +++ b/tests/test_raillabel/format/test_gps_imu.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import GpsImu +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +# == Fixtures ========================= + + +@pytest.fixture +def gps_imu_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + return ( + JSONStreamOther( + type="gps_imu", + uri="/path/to/sensor/data", + description="A very nice gps_imu", + ), + JSONCoordinateSystem( + parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + ), + ) + + +@pytest.fixture +def gps_imu(transform) -> dict: + return GpsImu( + extrinsics=transform, + uri="/path/to/sensor/data", + description="A very nice gps_imu", + ) + + +# == Tests ============================ + + +def test_from_json(gps_imu, gps_imu_json): + actual = GpsImu.from_json(gps_imu_json[0], gps_imu_json[1]) + assert actual == gps_imu + + +if __name__ == "__main__": + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From a3a9da048b0359c03bd5de5e524c3524825e61d5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Mon, 11 Nov 2024 20:21:32 +0100 Subject: [PATCH 111/190] feat: implement OtherSensor.from_json() --- raillabel/format/__init__.py | 2 + raillabel/format/other_sensor.py | 8 ++++ raillabel/format/scene.py | 5 +- .../format/test_other_sensor.py | 46 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 raillabel/format/other_sensor.py create mode 100644 tests/test_raillabel/format/test_other_sensor.py diff --git a/raillabel/format/__init__.py b/raillabel/format/__init__.py index cb56171..caab9fc 100644 --- a/raillabel/format/__init__.py +++ b/raillabel/format/__init__.py @@ -14,6 +14,7 @@ from .metadata import Metadata from .num import Num from .object import Object +from .other_sensor import OtherSensor from .point2d import Point2d from .point3d import Point3d from .poly2d import Poly2d @@ -41,6 +42,7 @@ "Metadata", "Num", "Object", + "OtherSensor", "Point2d", "Point3d", "Poly2d", diff --git a/raillabel/format/other_sensor.py b/raillabel/format/other_sensor.py new file mode 100644 index 0000000..385214c --- /dev/null +++ b/raillabel/format/other_sensor.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from ._sensor_without_intrinsics import _SensorWithoutIntrinsics + + +class OtherSensor(_SensorWithoutIntrinsics): + """A sensor that is not represented by the available options.""" diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 44ab918..d163de1 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -8,8 +8,11 @@ from .camera import Camera from .frame import Frame from .frame_interval import FrameInterval +from .gps_imu import GpsImu +from .lidar import Lidar from .metadata import Metadata from .object import Object +from .other_sensor import OtherSensor from .radar import Radar @@ -36,7 +39,7 @@ class Scene: """ metadata: Metadata - sensors: dict[str, Camera | Radar] = field(default_factory=dict) + sensors: dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor] = field(default_factory=dict) objects: dict[str, Object] = field(default_factory=dict) frames: dict[int, Frame] = field(default_factory=dict) diff --git a/tests/test_raillabel/format/test_other_sensor.py b/tests/test_raillabel/format/test_other_sensor.py new file mode 100644 index 0000000..25e30b5 --- /dev/null +++ b/tests/test_raillabel/format/test_other_sensor.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import OtherSensor +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +# == Fixtures ========================= + + +@pytest.fixture +def other_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + return ( + JSONStreamOther( + type="other", + uri="/path/to/sensor/data", + description="A very nice generic sensor", + ), + JSONCoordinateSystem( + parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + ), + ) + + +@pytest.fixture +def other(transform) -> dict: + return OtherSensor( + extrinsics=transform, + uri="/path/to/sensor/data", + description="A very nice generic sensor", + ) + + +# == Tests ============================ + + +def test_from_json(other, other_json): + actual = OtherSensor.from_json(other_json[0], other_json[1]) + assert actual == other + + +if __name__ == "__main__": + pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 838b7f4d6f4dc3cf235fccfdcdb46643ddb074df Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 12 Nov 2024 10:01:46 +0100 Subject: [PATCH 112/190] feat: implement Metadata.from_json() --- raillabel/format/metadata.py | 74 +++++++++----------- raillabel/json_format/metadata.py | 3 + tests/test_raillabel/format/test_metadata.py | 51 ++++++++++++++ 3 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 tests/test_raillabel/format/test_metadata.py diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index a27e46e..2e45054 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -5,57 +5,47 @@ from dataclasses import dataclass +from raillabel.json_format import JSONMetadata + @dataclass class Metadata: - """Container for metadata information about the scene itself. - - As the OpenLABEL metadata object accepts additional properties, so does this class. Any - properties present in the JSON will be added to the Metadata() object when read through - Metadata.fromdict(). Conversely, all attributes from the Metadata() object will be stored - into the JSON when using Metadata.asdict(). You can therefore just add attributes to the - Python object and have them stored. - Example: - m = Metadata.fromdict( - { - "schema_version": "1.0.0", - "some_additional_property": "Some Value" - } - ) - m.another_additional_property = "Another Value" - m.asdict() - -> { - "schema_version": "1.0.0", - "some_additional_property": "Some Value", - "another_additional_property": "Another Value" - } - - Parameters - ---------- - schema_version: str - Version number of the OpenLABEL schema this annotation object follows. - annotator: str, optional - Name or description of the annotator that created the annotations. Default is None. - comment: str, optional - Additional information or description about the annotation content. Default is None. - exporter_version: str, optional - Version of the raillabel-devkit, that last exported the scene. Default is None. - file_version: str, optional - Version number of the raillabel annotation content. Default is None. - name: str, optional - Name of the raillabel annotation content. Default is None. - subschema_version: str, optional - Version number of the RailLabel schema this annotation object follows. Default is None. - tagged_file: str, optional - Directory with the exported data_dict (e.g. images, point clouds). Default is None. - - """ + """Container for metadata information about the scene itself.""" schema_version: str + "Version number of the OpenLABEL schema this annotation object follows." + annotator: str | None = None + "Name or description of the annotator that created the annotations." + comment: str | None = None + "Additional information or description about the annotation content." + exporter_version: str | None = None + "Version of the raillabel-devkit, that last exported the scene." + file_version: str | None = None + "Version number of the raillabel annotation content." + name: str | None = None + "Name of the raillabel annotation content." + subschema_version: str | None = None + "Version number of the RailLabel schema this annotation object follows." + tagged_file: str | None = None + "Directory with the exported data_dict (e.g. images, point clouds)." + + @classmethod + def from_json(cls, json: JSONMetadata) -> Metadata: + """Construct an instant of this class from RailLabel JSON data.""" + return Metadata( + schema_version=json.schema_version, + name=json.name, + subschema_version=json.subschema_version, + exporter_version=json.exporter_version, + file_version=json.file_version, + tagged_file=json.tagged_file, + annotator=json.annotator, + comment=json.comment, + ) diff --git a/raillabel/json_format/metadata.py b/raillabel/json_format/metadata.py index a24e117..84fca90 100644 --- a/raillabel/json_format/metadata.py +++ b/raillabel/json_format/metadata.py @@ -29,5 +29,8 @@ class JSONMetadata(BaseModel): tagged_file: str | None = None "File name or URI of the data file being tagged." + annotator: str | None = None + "The person or organization responsible for annotating the file." + comment: str | None = None "Additional information or description about the annotation content." diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py new file mode 100644 index 0000000..ac56bc3 --- /dev/null +++ b/tests/test_raillabel/format/test_metadata.py @@ -0,0 +1,51 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Metadata +from raillabel.json_format import JSONMetadata + +# == Fixtures ========================= + + +@pytest.fixture +def metadata_json() -> JSONMetadata: + return JSONMetadata( + schema_version="1.0.0", + name="some_file", + subschema_version="4.0.0", + exporter_version="1.2.3", + file_version="0.1.5", + tagged_file="path/to/data", + annotator="John Doe", + comment="this is a very nice annotation file", + ) + + +@pytest.fixture +def metadata() -> dict: + return Metadata( + schema_version="1.0.0", + name="some_file", + subschema_version="4.0.0", + exporter_version="1.2.3", + file_version="0.1.5", + tagged_file="path/to/data", + annotator="John Doe", + comment="this is a very nice annotation file", + ) + + +# == Tests ============================ + + +def test_from_json(metadata, metadata_json): + actual = Metadata.from_json(metadata_json) + assert actual == metadata + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 3f51b483ba0621606af21dcbcb03ed81aab54f96 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 12 Nov 2024 10:09:33 +0100 Subject: [PATCH 113/190] feat: implement additional parameters in Metadata --- raillabel/format/metadata.py | 8 +++++++- raillabel/json_format/metadata.py | 4 ++-- tests/test_raillabel/format/test_metadata.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index 2e45054..24a23c7 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -39,7 +39,7 @@ class Metadata: @classmethod def from_json(cls, json: JSONMetadata) -> Metadata: """Construct an instant of this class from RailLabel JSON data.""" - return Metadata( + metadata = Metadata( schema_version=json.schema_version, name=json.name, subschema_version=json.subschema_version, @@ -49,3 +49,9 @@ def from_json(cls, json: JSONMetadata) -> Metadata: annotator=json.annotator, comment=json.comment, ) + + if json.model_extra is not None: + for extra_field, extra_value in json.model_extra.items(): + setattr(metadata, extra_field, extra_value) + + return metadata diff --git a/raillabel/json_format/metadata.py b/raillabel/json_format/metadata.py index 84fca90..a60d3d9 100644 --- a/raillabel/json_format/metadata.py +++ b/raillabel/json_format/metadata.py @@ -5,10 +5,10 @@ from typing import Literal -from pydantic import BaseModel +from pydantic import BaseModel, Extra -class JSONMetadata(BaseModel): +class JSONMetadata(BaseModel, extra=Extra.allow): """Metadata about the annotation file itself.""" schema_version: Literal["1.0.0"] diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py index ac56bc3..6f67c0c 100644 --- a/tests/test_raillabel/format/test_metadata.py +++ b/tests/test_raillabel/format/test_metadata.py @@ -47,5 +47,19 @@ def test_from_json(metadata, metadata_json): assert actual == metadata +def test_from_json__extra_fields(): + json_metadata = JSONMetadata( + **{ + "schema_version": "1.0.0", + "ADDITIONAL_STR": "SOME_VALUE", + "ADDITIONAL_OBJECT": {"first_field": 2, "second_field": [1, 2, 3]}, + } + ) + + actual = Metadata.from_json(json_metadata) + assert actual.ADDITIONAL_STR == "SOME_VALUE" + assert actual.ADDITIONAL_OBJECT == {"first_field": 2, "second_field": [1, 2, 3]} + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 1f4b99579652241974b08710cb7ed34349f725bf Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 12 Nov 2024 10:10:27 +0100 Subject: [PATCH 114/190] fix: remove deprecated pydantic expression --- raillabel/json_format/metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raillabel/json_format/metadata.py b/raillabel/json_format/metadata.py index a60d3d9..2862a25 100644 --- a/raillabel/json_format/metadata.py +++ b/raillabel/json_format/metadata.py @@ -5,10 +5,10 @@ from typing import Literal -from pydantic import BaseModel, Extra +from pydantic import BaseModel -class JSONMetadata(BaseModel, extra=Extra.allow): +class JSONMetadata(BaseModel, extra="allow"): """Metadata about the annotation file itself.""" schema_version: Literal["1.0.0"] From 6bccf446e657c43da1402e86ec0d5dcb763cd064 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Tue, 12 Nov 2024 17:49:16 +0100 Subject: [PATCH 115/190] feat: implement Scene.from_json() for sensors --- .../format/_sensor_without_intrinsics.py | 13 +-- raillabel/format/gps_imu.py | 17 +++- raillabel/format/lidar.py | 17 +++- raillabel/format/other_sensor.py | 17 +++- raillabel/format/scene.py | 80 +++++++++++++------ tests/test_raillabel/format/conftest.py | 4 + tests/test_raillabel/format/test_scene.py | 65 +++++++++++++++ 7 files changed, 173 insertions(+), 40 deletions(-) create mode 100644 tests/test_raillabel/format/test_scene.py diff --git a/raillabel/format/_sensor_without_intrinsics.py b/raillabel/format/_sensor_without_intrinsics.py index f08a035..24522ff 100644 --- a/raillabel/format/_sensor_without_intrinsics.py +++ b/raillabel/format/_sensor_without_intrinsics.py @@ -5,7 +5,7 @@ from dataclasses import dataclass -from raillabel.json_format import JSONCoordinateSystem, JSONStreamRadar, JSONTransformData +from raillabel.json_format import JSONTransformData from .transform import Transform @@ -23,17 +23,6 @@ class _SensorWithoutIntrinsics: description: str | None = None "Additional information about the sensor." - @classmethod - def from_json( - cls, json_stream: JSONStreamRadar, json_coordinate_system: JSONCoordinateSystem - ) -> _SensorWithoutIntrinsics: - """Construct an instant of this class from RailLabel JSON data.""" - return cls( - extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), - uri=json_stream.uri, - description=json_stream.description, - ) - def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: if json_transform is None: diff --git a/raillabel/format/gps_imu.py b/raillabel/format/gps_imu.py index 3d6a57a..7ecf88a 100644 --- a/raillabel/format/gps_imu.py +++ b/raillabel/format/gps_imu.py @@ -1,8 +1,23 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -from ._sensor_without_intrinsics import _SensorWithoutIntrinsics +from __future__ import annotations + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +from ._sensor_without_intrinsics import _extrinsics_from_json, _SensorWithoutIntrinsics class GpsImu(_SensorWithoutIntrinsics): """A gps sensor with inertial measurement unit.""" + + @classmethod + def from_json( + cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem + ) -> GpsImu: + """Construct an instant of this class from RailLabel JSON data.""" + return GpsImu( + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) diff --git a/raillabel/format/lidar.py b/raillabel/format/lidar.py index 40010ff..d1e3dfa 100644 --- a/raillabel/format/lidar.py +++ b/raillabel/format/lidar.py @@ -1,8 +1,23 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -from ._sensor_without_intrinsics import _SensorWithoutIntrinsics +from __future__ import annotations + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +from ._sensor_without_intrinsics import _extrinsics_from_json, _SensorWithoutIntrinsics class Lidar(_SensorWithoutIntrinsics): """A lidar sensor.""" + + @classmethod + def from_json( + cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem + ) -> Lidar: + """Construct an instant of this class from RailLabel JSON data.""" + return Lidar( + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) diff --git a/raillabel/format/other_sensor.py b/raillabel/format/other_sensor.py index 385214c..5736ab7 100644 --- a/raillabel/format/other_sensor.py +++ b/raillabel/format/other_sensor.py @@ -1,8 +1,23 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -from ._sensor_without_intrinsics import _SensorWithoutIntrinsics +from __future__ import annotations + +from raillabel.json_format import JSONCoordinateSystem, JSONStreamOther + +from ._sensor_without_intrinsics import _extrinsics_from_json, _SensorWithoutIntrinsics class OtherSensor(_SensorWithoutIntrinsics): """A sensor that is not represented by the available options.""" + + @classmethod + def from_json( + cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem + ) -> OtherSensor: + """Construct an instant of this class from RailLabel JSON data.""" + return OtherSensor( + extrinsics=_extrinsics_from_json(json_coordinate_system.pose_wrt_parent), + uri=json_stream.uri, + description=json_stream.description, + ) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index d163de1..78d976d 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -4,10 +4,18 @@ from __future__ import annotations from dataclasses import dataclass, field +from uuid import UUID + +from raillabel.json_format import ( + JSONCoordinateSystem, + JSONScene, + JSONStreamCamera, + JSONStreamOther, + JSONStreamRadar, +) from .camera import Camera from .frame import Frame -from .frame_interval import FrameInterval from .gps_imu import GpsImu from .lidar import Lidar from .metadata import Metadata @@ -18,32 +26,54 @@ @dataclass class Scene: - """The root RailLabel class, which contains all data. - - Parameters - ---------- - metadata: raillabel.format.Metadata - This object contains information, that is, metadata, about the annotation file itself. - sensors: dict of raillabel.format.Sensor, optional - Dictionary of raillabel.format.Sensors. Dictionary keys are the sensor uids. Default is {}. - objects: dict of raillabel.format.Object, optional - Dictionary of raillabel.format.Objects. Dictionary keys are the object uids. Default is {}. - frames: dict of raillabel.format.Frame, optional - Dict of frames in the scene. Dictionary keys are the frame uids. Default is {}. - - Properties (read-only) - ---------------------- - frame_intervals: list[FrameIntervals] - List of frame intervals describing the frames present in this scene. - - """ + """The root RailLabel class, which contains all data.""" metadata: Metadata + "Container of information about the annotation file itself." + sensors: dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor] = field(default_factory=dict) - objects: dict[str, Object] = field(default_factory=dict) + "The sensors used in this scene. Keys are sensor names." + + objects: dict[UUID, Object] = field(default_factory=dict) + "Unique objects (like a specific person) in this scene. Keys are object uuids" + frames: dict[int, Frame] = field(default_factory=dict) + "A container of dynamic, timewise, information. Keys are the frame integer number." + + @classmethod + def from_json(cls, json: JSONScene) -> Scene: + """Construct a scene from a json object.""" + return Scene( + metadata=Metadata.from_json(json.openlabel.metadata), + sensors=_sensors_from_json(json.openlabel.streams, json.openlabel.coordinate_systems), + ) + + +def _sensors_from_json( + json_streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None, + json_coordinate_systems: dict[str, JSONCoordinateSystem] | None, +) -> dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor]: + sensors: dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor] = {} + + if json_streams is None or json_coordinate_systems is None: + return sensors + + for sensor_id, json_stream in json_streams.items(): + json_coordinate_system = json_coordinate_systems[sensor_id] + + if isinstance(json_stream, JSONStreamCamera): + sensors[sensor_id] = Camera.from_json(json_stream, json_coordinate_system) + + if isinstance(json_stream, JSONStreamRadar): + sensors[sensor_id] = Radar.from_json(json_stream, json_coordinate_system) + + if isinstance(json_stream, JSONStreamOther) and json_stream.type == "lidar": + sensors[sensor_id] = Lidar.from_json(json_stream, json_coordinate_system) + + if isinstance(json_stream, JSONStreamOther) and json_stream.type == "gps_imu": + sensors[sensor_id] = GpsImu.from_json(json_stream, json_coordinate_system) + + if isinstance(json_stream, JSONStreamOther) and json_stream.type == "other": + sensors[sensor_id] = OtherSensor.from_json(json_stream, json_coordinate_system) - @property - def frame_intervals(self) -> list[FrameInterval]: - """Return frame intervals of the present frames.""" - return FrameInterval.from_frame_uids(list(self.frames.keys())) + return sensors diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 00b915b..265e9a4 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -2,16 +2,20 @@ # SPDX-License-Identifier: Apache-2.0 from .test_attributes import attributes_multiple_types, attributes_multiple_types_json from .test_bbox import bbox, bbox_json, bbox_uid +from .test_camera import camera, camera_json from .test_cuboid import cuboid, cuboid_json, cuboid_uid from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json +from .test_lidar import lidar, lidar_json +from .test_metadata import metadata, metadata_json from .test_num import num, num_json from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json from .test_poly2d import poly2d, poly2d_json, poly2d_uid from .test_poly3d import poly3d, poly3d_json, poly3d_uid from .test_quaternion import quaternion, quaternion_json +from .test_radar import radar, radar_json from .test_size2d import size2d, size2d_json from .test_size3d import size3d, size3d_json from .test_seg3d import seg3d, seg3d_json, seg3d_uid diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py new file mode 100644 index 0000000..bb11a13 --- /dev/null +++ b/tests/test_raillabel/format/test_scene.py @@ -0,0 +1,65 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +from raillabel.format import Scene +from raillabel.json_format import JSONScene, JSONSceneContent, JSONCoordinateSystem + +# == Fixtures ========================= + + +@pytest.fixture +def scene_json(metadata_json, camera_json, lidar_json, radar_json) -> JSONScene: + return JSONScene( + openlabel=JSONSceneContent( + metadata=metadata_json, + coordinate_systems={ + "base": JSONCoordinateSystem( + parent="", + type="local", + pose_wrt_parent=None, + children=["rgb_middle", "lidar", "radar"], + ), + "rgb_middle": camera_json[1], + "lidar": lidar_json[1], + "radar": radar_json[1], + }, + streams={ + "rgb_middle": camera_json[0], + "lidar": lidar_json[0], + "radar": radar_json[0], + }, + ) + ) + + +@pytest.fixture +def scene( + metadata, + camera, + lidar, + radar, +) -> Scene: + return Scene( + metadata=metadata, + sensors={ + "rgb_middle": camera, + "lidar": lidar, + "radar": radar, + }, + ) + + +# == Tests ============================ + + +def test_from_json(scene, scene_json): + actual = Scene.from_json(scene_json) + assert actual == scene + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From c7dc1458a5d8a3ec6645b5d963cea5d8b3d16df6 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 08:43:25 +0100 Subject: [PATCH 116/190] fix: typing for python 3.8 and 3.9 --- CONTRIBUTING.md | 2 +- raillabel/format/point2d.py | 6 +++--- raillabel/format/size2d.py | 6 +++--- raillabel/json_format/num_attribute.py | 2 +- raillabel/json_format/vec_attribute.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b57e49..e04361d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,7 +138,7 @@ The key differences are: etc. - For classes that are not builtin (e.g. `Iterable`), `import collections.abc as cabc` and then use them like `cabc.Iterable`. - - Use [PEP-604-style unions], e.g. `int | float` instead of + - Use [PEP-604-style unions], e.g. `float` instead of `t.Union[int, float]`. - Use `... | None` (with `None` always as the last union member) instead of `t.Optional[...]` and always explicitly annotate where `None` is possible. diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index e1934ba..467a2a5 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -10,13 +10,13 @@ class Point2d: """A 2d point in an image.""" - x: int | float + x: float "The x-coordinate of the point in the image in pixels from the left." - y: int | float + y: float "The y-coordinate of the point in the image in pixels from the top." @classmethod - def from_json(cls, json: tuple[int | float, int | float]) -> Point2d: + def from_json(cls, json: tuple[float, float]) -> Point2d: """Construct an instant of this class from RailLabel JSON data.""" return Point2d(x=json[0], y=json[1]) diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index 7ddf07f..e378e7d 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -10,13 +10,13 @@ class Size2d: """The size of a rectangle in a 2d image.""" - x: int | float + x: float "The size along the x-axis." - y: int | float + y: float "The size along the y-axis." @classmethod - def from_json(cls, json: tuple[int | float, int | float]) -> Size2d: + def from_json(cls, json: tuple[float, float]) -> Size2d: """Construct an instant of this class from RailLabel JSON data.""" return Size2d(x=json[0], y=json[1]) diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py index 44693e9..700209c 100644 --- a/raillabel/json_format/num_attribute.py +++ b/raillabel/json_format/num_attribute.py @@ -13,5 +13,5 @@ class JSONNumAttribute(BaseModel): """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" - val: int | float + val: float "The number value." diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py index 65f23ee..49abce7 100644 --- a/raillabel/json_format/vec_attribute.py +++ b/raillabel/json_format/vec_attribute.py @@ -13,5 +13,5 @@ class JSONVecAttribute(BaseModel): """Friendly identifier describing the attribute. Used to track the attribute throughout annotations and frames.""" - val: list[int | float | str] + val: list[float | str] "The value vector of the attribute." From 9c8b7ab320201edbb44ca6897d64940507b18f1a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 08:47:01 +0100 Subject: [PATCH 117/190] fix: union types in python 3.8 and 3.9 with eval-type-backport --- pyproject.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7bda5fc..7a0562d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,12 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] -dependencies = ["jsonschema>=4.4.0", "fastjsonschema>=2.16.2", "pydantic<3.0.0"] +dependencies = [ + "jsonschema>=4.4.0", + "fastjsonschema>=2.16.2", + "pydantic<3.0.0", + "eval-type-backport=0.2.0" +] [project.urls] Homepage = "https://github.com/DSD-DBS/raillabel" From 50b5b00d81637ee8353aafac311d2bf432fe3280 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 08:58:32 +0100 Subject: [PATCH 118/190] fix: dependency definition of eval-type-backport --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7a0562d..8bee0ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "jsonschema>=4.4.0", "fastjsonschema>=2.16.2", "pydantic<3.0.0", - "eval-type-backport=0.2.0" + "eval-type-backport==0.2.0" ] [project.urls] From 0fa89ecd99fe698ff8f772fd08798f6cdaa608a7 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:02:37 +0100 Subject: [PATCH 119/190] refactor: remove unused dependencies --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8bee0ee..7f07378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,8 +29,6 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "jsonschema>=4.4.0", - "fastjsonschema>=2.16.2", "pydantic<3.0.0", "eval-type-backport==0.2.0" ] @@ -40,7 +38,7 @@ Homepage = "https://github.com/DSD-DBS/raillabel" Documentation = "https://dsd-dbs.github.io/raillabel" [project.optional-dependencies] -docs = ["furo", "sphinx", "sphinx-copybutton", "tomli; python_version<'3.11'"] +docs = ["furo", "sphinx", "sphinx-copybutton", "tomli; python_version<'3.14'"] test = ["pytest", "pytest-cov", "json5"] From 68b463c5566f4f1c1d15e7d5458b084e930953b4 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:16:17 +0100 Subject: [PATCH 120/190] test: extract object uids into separate fixtures --- tests/test_raillabel/format/conftest.py | 8 +++++ tests/test_raillabel/format/test_bbox.py | 7 ++-- tests/test_raillabel/format/test_cuboid.py | 7 ++-- tests/test_raillabel/format/test_object.py | 39 ++++++++++++++++++++-- tests/test_raillabel/format/test_poly2d.py | 7 ++-- tests/test_raillabel/format/test_poly3d.py | 7 ++-- tests/test_raillabel/format/test_seg3d.py | 7 ++-- 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 265e9a4..328cc33 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -10,6 +10,14 @@ from .test_lidar import lidar, lidar_json from .test_metadata import metadata, metadata_json from .test_num import num, num_json +from .test_object import ( + object_person, + object_person_json, + object_person_uid, + object_track, + object_track_json, + object_track_uid, +) from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json from .test_poly2d import poly2d, poly2d_json, poly2d_uid diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index b615508..d6c670f 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -38,21 +38,22 @@ def bbox( point2d, size2d, attributes_multiple_types, + object_person_uid, ) -> Bbox: return Bbox( pos=point2d, size=size2d, sensor="rgb_middle", attributes=attributes_multiple_types, - object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + object=object_person_uid, ) # == Tests ============================ -def test_from_json(bbox, bbox_json): - actual = Bbox.from_json(bbox_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) +def test_from_json(bbox, bbox_json, object_person_uid): + actual = Bbox.from_json(bbox_json, object_person_uid) assert actual == bbox diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index b8937b3..855777c 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -40,6 +40,7 @@ def cuboid( size3d, quaternion, attributes_multiple_types, + object_person_uid, ) -> Cuboid: return Cuboid( pos=point3d, @@ -47,15 +48,15 @@ def cuboid( size=size3d, sensor="lidar", attributes=attributes_multiple_types, - object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + object=object_person_uid, ) # == Tests ============================ -def test_from_json(cuboid, cuboid_json): - actual = Cuboid.from_json(cuboid_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) +def test_from_json(cuboid, cuboid_json, object_person_uid): + actual = Cuboid.from_json(cuboid_json, object_person_uid) assert actual == cuboid diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 65d37a5..0f94c7a 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -3,6 +3,8 @@ from __future__ import annotations +from uuid import UUID + import pytest from raillabel.json_format import JSONObject @@ -14,7 +16,7 @@ @pytest.fixture def object_person_json() -> JSONObject: return JSONObject( - name="person0032", + name="person_0032", type="person", ) @@ -22,18 +24,49 @@ def object_person_json() -> JSONObject: @pytest.fixture def object_person() -> Object: return Object( - name="person0032", + name="person_0032", type="person", ) +@pytest.fixture +def object_person_uid() -> UUID: + return UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934") + + +@pytest.fixture +def object_track_json() -> JSONObject: + return JSONObject( + name="track_0001", + type="track", + ) + + +@pytest.fixture +def object_track() -> Object: + return Object( + name="track_0001", + type="track", + ) + + +@pytest.fixture +def object_track_uid() -> UUID: + return UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a") + + # == Tests ============================ -def test_from_json(object_person, object_person_json): +def test_from_json__person(object_person, object_person_json): actual = Object.from_json(object_person_json) assert actual == object_person +def test_from_json__track(object_track, object_track_json): + actual = Object.from_json(object_track_json) + assert actual == object_track + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index f707bd9..d021f15 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -40,21 +40,22 @@ def poly2d( point2d, another_point2d, attributes_multiple_types, + object_track_uid, ) -> Poly2d: return Poly2d( points=[point2d, another_point2d], closed=True, sensor="rgb_middle", attributes=attributes_multiple_types, - object=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a"), + object=object_track_uid, ) # == Tests ============================ -def test_from_json(poly2d, poly2d_json): - actual = Poly2d.from_json(poly2d_json, object_uid=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a")) +def test_from_json(poly2d, poly2d_json, object_track_uid): + actual = Poly2d.from_json(poly2d_json, object_track_uid) assert actual == poly2d diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 9fa0a6c..1467037 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -39,21 +39,22 @@ def poly3d( point3d, another_point3d, attributes_multiple_types, + object_track_uid, ) -> Poly3d: return Poly3d( points=[point3d, another_point3d], closed=True, sensor="lidar", attributes=attributes_multiple_types, - object=UUID("cfcf9750-3bc3-4077-9079-a82c0c63976a"), + object=object_track_uid, ) # == Tests ============================ -def test_from_json(poly3d, poly3d_json): - actual = Poly3d.from_json(poly3d_json, object_uid=UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a")) +def test_from_json(poly3d, poly3d_json, object_track_uid): + actual = Poly3d.from_json(poly3d_json, object_track_uid) assert actual == poly3d diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index f025e10..cd91e03 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -34,20 +34,21 @@ def seg3d_uid() -> UUID: @pytest.fixture def seg3d( attributes_multiple_types, + object_person_uid, ) -> Seg3d: return Seg3d( point_ids=[1234, 5678], sensor="lidar", attributes=attributes_multiple_types, - object=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934"), + object=object_person_uid, ) # == Tests ============================ -def test_from_json(seg3d, seg3d_json): - actual = Seg3d.from_json(seg3d_json, object_uid=UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934")) +def test_from_json(seg3d, seg3d_json, object_person_uid): + actual = Seg3d.from_json(seg3d_json, object_person_uid) assert actual == seg3d From f63f2baf9eeb9b093bc6ecbddf6bfc9e9bc6ddbc Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:31:13 +0100 Subject: [PATCH 121/190] feat: implement Scene.from_json() for objects --- raillabel/format/scene.py | 9 +++++++++ tests/test_raillabel/format/test_scene.py | 23 ++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 78d976d..c6fbc04 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -8,6 +8,7 @@ from raillabel.json_format import ( JSONCoordinateSystem, + JSONObject, JSONScene, JSONStreamCamera, JSONStreamOther, @@ -46,6 +47,7 @@ def from_json(cls, json: JSONScene) -> Scene: return Scene( metadata=Metadata.from_json(json.openlabel.metadata), sensors=_sensors_from_json(json.openlabel.streams, json.openlabel.coordinate_systems), + objects=_objects_from_json(json.openlabel.objects), ) @@ -77,3 +79,10 @@ def _sensors_from_json( sensors[sensor_id] = OtherSensor.from_json(json_stream, json_coordinate_system) return sensors + + +def _objects_from_json(json_objects: dict[UUID, JSONObject] | None) -> dict[UUID, Object]: + if json_objects is None: + return {} + + return {obj_uid: Object.from_json(json_obj) for obj_uid, json_obj in json_objects.items()} diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index bb11a13..087b053 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -12,7 +12,16 @@ @pytest.fixture -def scene_json(metadata_json, camera_json, lidar_json, radar_json) -> JSONScene: +def scene_json( + metadata_json, + camera_json, + lidar_json, + radar_json, + object_person_uid, + object_person_json, + object_track_uid, + object_track_json, +) -> JSONScene: return JSONScene( openlabel=JSONSceneContent( metadata=metadata_json, @@ -32,6 +41,10 @@ def scene_json(metadata_json, camera_json, lidar_json, radar_json) -> JSONScene: "lidar": lidar_json[0], "radar": radar_json[0], }, + objects={ + object_person_uid: object_person_json, + object_track_uid: object_track_json, + }, ) ) @@ -42,6 +55,10 @@ def scene( camera, lidar, radar, + object_person_uid, + object_person, + object_track_uid, + object_track, ) -> Scene: return Scene( metadata=metadata, @@ -50,6 +67,10 @@ def scene( "lidar": lidar, "radar": radar, }, + objects={ + object_person_uid: object_person, + object_track_uid: object_track, + }, ) From 64f00eaf9afd743fe2d5f393ae3f2b6b1c07ae6d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:41:18 +0100 Subject: [PATCH 122/190] feat: implement Scene.from_json() for frames --- raillabel/format/scene.py | 9 +++++++++ tests/test_raillabel/format/conftest.py | 1 + tests/test_raillabel/format/test_scene.py | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index c6fbc04..3c1b03e 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -8,6 +8,7 @@ from raillabel.json_format import ( JSONCoordinateSystem, + JSONFrame, JSONObject, JSONScene, JSONStreamCamera, @@ -48,6 +49,7 @@ def from_json(cls, json: JSONScene) -> Scene: metadata=Metadata.from_json(json.openlabel.metadata), sensors=_sensors_from_json(json.openlabel.streams, json.openlabel.coordinate_systems), objects=_objects_from_json(json.openlabel.objects), + frames=_frames_from_json(json.openlabel.frames), ) @@ -86,3 +88,10 @@ def _objects_from_json(json_objects: dict[UUID, JSONObject] | None) -> dict[UUID return {} return {obj_uid: Object.from_json(json_obj) for obj_uid, json_obj in json_objects.items()} + + +def _frames_from_json(json_frames: dict[int, JSONFrame] | None) -> dict[int, Frame]: + if json_frames is None: + return {} + + return {frame_uid: Frame.from_json(json_frame) for frame_uid, json_frame in json_frames.items()} diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 328cc33..b51eafb 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -4,6 +4,7 @@ from .test_bbox import bbox, bbox_json, bbox_uid from .test_camera import camera, camera_json from .test_cuboid import cuboid, cuboid_json, cuboid_uid +from .test_frame import frame, frame_json from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index 087b053..ea2539f 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -21,6 +21,7 @@ def scene_json( object_person_json, object_track_uid, object_track_json, + frame_json, ) -> JSONScene: return JSONScene( openlabel=JSONSceneContent( @@ -45,6 +46,7 @@ def scene_json( object_person_uid: object_person_json, object_track_uid: object_track_json, }, + frames={1: frame_json}, ) ) @@ -59,6 +61,7 @@ def scene( object_person, object_track_uid, object_track, + frame, ) -> Scene: return Scene( metadata=metadata, @@ -71,6 +74,7 @@ def scene( object_person_uid: object_person, object_track_uid: object_track, }, + frames={1: frame}, ) From db19b750cc96eae7348f9d0e0ad468d2764ba42e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:48:44 +0100 Subject: [PATCH 123/190] feat: implement raillabel.load() --- raillabel/load/load.py | 24 ++++++++---------------- tests/test_raillabel/load/test_load.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 tests/test_raillabel/load/test_load.py diff --git a/raillabel/load/load.py b/raillabel/load/load.py index b59a4d8..1011347 100644 --- a/raillabel/load/load.py +++ b/raillabel/load/load.py @@ -3,23 +3,15 @@ from __future__ import annotations +import json from pathlib import Path -from raillabel.format import Metadata, Scene +from raillabel.format import Scene +from raillabel.json_format import JSONScene -def load(_path: Path | str) -> Scene: - """Load an annotation file of any supported type. - - Parameters - ---------- - path: str - Path to the annotation file. - - Returns - ------- - scene: raillabel.Scene - Scene with the loaded data. - - """ - return Scene(metadata=Metadata("1.0.0")) +def load(path: Path | str) -> Scene: + """Load an annotation file as a scene.""" + with Path(path).open() as annotation_file: + json_data = json.load(annotation_file) + return Scene.from_json(JSONScene(**json_data)) diff --git a/tests/test_raillabel/load/test_load.py b/tests/test_raillabel/load/test_load.py new file mode 100644 index 0000000..a80670a --- /dev/null +++ b/tests/test_raillabel/load/test_load.py @@ -0,0 +1,19 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +import raillabel + + +def test_load(json_paths): + actual = raillabel.load(json_paths["openlabel_v1_short"]) + assert len(actual.sensors) == 4 + assert len(actual.objects) == 3 + assert len(actual.frames) == 2 + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 956d9d2e5139d10bfffaaab379c559a0dfffd983 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 09:58:12 +0100 Subject: [PATCH 124/190] feat: implement FrameInterval.to_json() --- raillabel/format/frame_interval.py | 41 +++++++++++-------- .../format/test_frame_interval.py | 5 +++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index 33231a0..de0c851 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -42,34 +42,41 @@ def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: """ sorted_frame_uids = sorted(frame_uids) - frame_uid_intervals = cls._slice_into_intervals(sorted_frame_uids) + frame_uid_intervals = _slice_into_intervals(sorted_frame_uids) return [ FrameInterval(start=interval[0], end=interval[-1]) for interval in frame_uid_intervals ] + def to_json(self) -> JSONFrameInterval: + """Export this object into the RailLabel JSON format.""" + return JSONFrameInterval( + frame_start=self.start, + frame_end=self.end, + ) + def __len__(self) -> int: """Return the length in frames.""" return abs(self.start - self.end) + 1 - @classmethod - def _slice_into_intervals(cls, sorted_frame_uids: list[int]) -> list[list[int]]: - if len(sorted_frame_uids) == 0: - return [] - if len(sorted_frame_uids) == 1: - return [sorted_frame_uids] +def _slice_into_intervals(sorted_frame_uids: list[int]) -> list[list[int]]: + if len(sorted_frame_uids) == 0: + return [] + + if len(sorted_frame_uids) == 1: + return [sorted_frame_uids] - intervals = [] - interval_start_i = 0 - for i, frame_uid in enumerate(sorted_frame_uids[1:]): - previous_frame_uid = sorted_frame_uids[i] + intervals = [] + interval_start_i = 0 + for i, frame_uid in enumerate(sorted_frame_uids[1:]): + previous_frame_uid = sorted_frame_uids[i] - if frame_uid - previous_frame_uid > 1: - intervals.append(sorted_frame_uids[interval_start_i : i + 1]) - interval_start_i = i + 1 + if frame_uid - previous_frame_uid > 1: + intervals.append(sorted_frame_uids[interval_start_i : i + 1]) + interval_start_i = i + 1 - intervals.append(sorted_frame_uids[interval_start_i : len(sorted_frame_uids)]) - interval_start_i = len(sorted_frame_uids) + intervals.append(sorted_frame_uids[interval_start_i : len(sorted_frame_uids)]) + interval_start_i = len(sorted_frame_uids) - return intervals + return intervals diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index c55b9cd..4508421 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -79,5 +79,10 @@ def test_from_frame_uids_unsorted(): ] +def test_to_json(frame_interval, frame_interval_json): + actual = frame_interval.to_json() + assert actual == frame_interval_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From b00df0c0e8666dfd673df1b027db066c255bad95 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:01:26 +0100 Subject: [PATCH 125/190] feat: implement Point2d.to_json() --- raillabel/format/point2d.py | 4 ++++ tests/test_raillabel/format/test_point2d.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/raillabel/format/point2d.py b/raillabel/format/point2d.py index 467a2a5..d50b7bd 100644 --- a/raillabel/format/point2d.py +++ b/raillabel/format/point2d.py @@ -20,3 +20,7 @@ class Point2d: def from_json(cls, json: tuple[float, float]) -> Point2d: """Construct an instant of this class from RailLabel JSON data.""" return Point2d(x=json[0], y=json[1]) + + def to_json(self) -> tuple[float, float]: + """Export this object into the RailLabel JSON format.""" + return (self.x, self.y) diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 04d5165..6d1ca72 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -12,7 +12,7 @@ @pytest.fixture def point2d_json() -> tuple[float, float]: - return [1.5, 222] + return (1.5, 222) @pytest.fixture @@ -22,7 +22,7 @@ def point2d() -> Point2d: @pytest.fixture def another_point2d_json() -> tuple[float, float]: - return [1.7, 222.2] + return (1.7, 222.2) @pytest.fixture @@ -43,5 +43,15 @@ def test_from_json__another(another_point2d, another_point2d_json): assert actual == another_point2d +def test_to_json(point2d, point2d_json): + actual = point2d.to_json() + assert actual == point2d_json + + +def test_to_json__another(another_point2d, another_point2d_json): + actual = another_point2d.to_json() + assert actual == another_point2d_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 078d7a9f76def4a6ecd9669c8f6997e4489d6a3e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:02:42 +0100 Subject: [PATCH 126/190] feat: implement Point3d.to_json() --- raillabel/format/point3d.py | 4 ++++ tests/test_raillabel/format/test_point3d.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/raillabel/format/point3d.py b/raillabel/format/point3d.py index 9fcabf1..efca84a 100644 --- a/raillabel/format/point3d.py +++ b/raillabel/format/point3d.py @@ -23,3 +23,7 @@ class Point3d: def from_json(cls, json: tuple[float, float, float]) -> Point3d: """Construct an instant of this class from RailLabel JSON data.""" return Point3d(x=json[0], y=json[1], z=json[2]) + + def to_json(self) -> tuple[float, float, float]: + """Export this object into the RailLabel JSON format.""" + return (self.x, self.y, self.z) diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index f4404f4..1a162d5 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -12,7 +12,7 @@ @pytest.fixture def point3d_json() -> dict: - return [419, 3.14, 0] + return (419, 3.14, 0) @pytest.fixture @@ -22,7 +22,7 @@ def point3d() -> dict: @pytest.fixture def another_point3d_json() -> dict: - return [419.2, 3.34, 0.2] + return (419.2, 3.34, 0.2) @pytest.fixture @@ -43,5 +43,15 @@ def test_from_json__another(another_point3d, another_point3d_json): assert actual == another_point3d +def test_to_json(point3d, point3d_json): + actual = point3d.to_json() + assert actual == point3d_json + + +def test_to_json__another(another_point3d, another_point3d_json): + actual = another_point3d.to_json() + assert actual == another_point3d_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 97c4d26cd26839c676c97452a8c5e9b41c2a79c1 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:07:00 +0100 Subject: [PATCH 127/190] feat: implement Size2d.to_json() --- raillabel/format/size2d.py | 4 ++++ tests/test_raillabel/format/test_point2d.py | 8 ++++---- tests/test_raillabel/format/test_point3d.py | 8 ++++---- tests/test_raillabel/format/test_size2d.py | 5 +++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/raillabel/format/size2d.py b/raillabel/format/size2d.py index e378e7d..5afe2a6 100644 --- a/raillabel/format/size2d.py +++ b/raillabel/format/size2d.py @@ -20,3 +20,7 @@ class Size2d: def from_json(cls, json: tuple[float, float]) -> Size2d: """Construct an instant of this class from RailLabel JSON data.""" return Size2d(x=json[0], y=json[1]) + + def to_json(self) -> tuple[float, float]: + """Export this object into the RailLabel JSON format.""" + return (self.x, self.y) diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/test_raillabel/format/test_point2d.py index 6d1ca72..54d3302 100644 --- a/tests/test_raillabel/format/test_point2d.py +++ b/tests/test_raillabel/format/test_point2d.py @@ -12,7 +12,7 @@ @pytest.fixture def point2d_json() -> tuple[float, float]: - return (1.5, 222) + return [1.5, 222] @pytest.fixture @@ -22,7 +22,7 @@ def point2d() -> Point2d: @pytest.fixture def another_point2d_json() -> tuple[float, float]: - return (1.7, 222.2) + return [1.7, 222.2] @pytest.fixture @@ -45,12 +45,12 @@ def test_from_json__another(another_point2d, another_point2d_json): def test_to_json(point2d, point2d_json): actual = point2d.to_json() - assert actual == point2d_json + assert actual == tuple(point2d_json) def test_to_json__another(another_point2d, another_point2d_json): actual = another_point2d.to_json() - assert actual == another_point2d_json + assert actual == tuple(another_point2d_json) if __name__ == "__main__": diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/test_raillabel/format/test_point3d.py index 1a162d5..1fef803 100644 --- a/tests/test_raillabel/format/test_point3d.py +++ b/tests/test_raillabel/format/test_point3d.py @@ -12,7 +12,7 @@ @pytest.fixture def point3d_json() -> dict: - return (419, 3.14, 0) + return [419, 3.14, 0] @pytest.fixture @@ -22,7 +22,7 @@ def point3d() -> dict: @pytest.fixture def another_point3d_json() -> dict: - return (419.2, 3.34, 0.2) + return [419.2, 3.34, 0.2] @pytest.fixture @@ -45,12 +45,12 @@ def test_from_json__another(another_point3d, another_point3d_json): def test_to_json(point3d, point3d_json): actual = point3d.to_json() - assert actual == point3d_json + assert actual == tuple(point3d_json) def test_to_json__another(another_point3d, another_point3d_json): actual = another_point3d.to_json() - assert actual == another_point3d_json + assert actual == tuple(another_point3d_json) if __name__ == "__main__": diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/test_raillabel/format/test_size2d.py index 3e4b3a3..3df8424 100644 --- a/tests/test_raillabel/format/test_size2d.py +++ b/tests/test_raillabel/format/test_size2d.py @@ -28,5 +28,10 @@ def test_from_json(size2d, size2d_json): assert actual == size2d +def test_to_json(size2d, size2d_json): + actual = size2d.to_json() + assert actual == tuple(size2d_json) + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 3caefffe7df26c955cd27ad55b21b0abead128a7 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:18:32 +0100 Subject: [PATCH 128/190] feat: implement Size3d.to_json() --- raillabel/format/size3d.py | 4 ++++ tests/test_raillabel/format/test_size3d.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/raillabel/format/size3d.py b/raillabel/format/size3d.py index 3c0054d..7810689 100644 --- a/raillabel/format/size3d.py +++ b/raillabel/format/size3d.py @@ -23,3 +23,7 @@ class Size3d: def from_json(cls, json: tuple[float, float, float]) -> Size3d: """Construct an instant of this class from RailLabel JSON data.""" return Size3d(x=json[0], y=json[1], z=json[2]) + + def to_json(self) -> tuple[float, float, float]: + """Export this object into the RailLabel JSON format.""" + return (self.x, self.y, self.z) diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/test_raillabel/format/test_size3d.py index d38e9d2..801c4fa 100644 --- a/tests/test_raillabel/format/test_size3d.py +++ b/tests/test_raillabel/format/test_size3d.py @@ -28,5 +28,10 @@ def test_from_json(size3d, size3d_json): assert actual == size3d +def test_to_json(size3d, size3d_json): + actual = size3d.to_json() + assert actual == tuple(size3d_json) + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From c9214e4716073ffc2f915661bb294f33b07dc1dd Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:31:45 +0100 Subject: [PATCH 129/190] feat: implement Quaternion.to_json() --- raillabel/format/quaternion.py | 4 ++++ tests/test_raillabel/format/test_quaternion.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/raillabel/format/quaternion.py b/raillabel/format/quaternion.py index d325cba..a6a4df5 100644 --- a/raillabel/format/quaternion.py +++ b/raillabel/format/quaternion.py @@ -26,3 +26,7 @@ class Quaternion: def from_json(cls, json: tuple[float, float, float, float]) -> Quaternion: """Construct an instant of this class from RailLabel JSON data.""" return Quaternion(x=json[0], y=json[1], z=json[2], w=json[3]) + + def to_json(self) -> tuple[float, float, float, float]: + """Export this object into the RailLabel JSON format.""" + return (self.x, self.y, self.z, self.w) diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/test_raillabel/format/test_quaternion.py index 78ebab4..bff5656 100644 --- a/tests/test_raillabel/format/test_quaternion.py +++ b/tests/test_raillabel/format/test_quaternion.py @@ -28,5 +28,10 @@ def test_from_json(quaternion, quaternion_json): assert actual == quaternion +def test_to_json(quaternion, quaternion_json): + actual = quaternion.to_json() + assert actual == tuple(quaternion_json) + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 738aa434a4b47bf027b7bb589f86a71a10d144af Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:33:48 +0100 Subject: [PATCH 130/190] feat: implement Transform.to_json() --- raillabel/format/transform.py | 7 +++++++ tests/test_raillabel/format/test_transform.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 68fb971..7e4fa4a 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -28,3 +28,10 @@ def from_json(cls, json: JSONTransformData) -> Transform: position=Point3d.from_json(json.translation), quaternion=Quaternion.from_json(json.quaternion), ) + + def to_json(self) -> JSONTransformData: + """Export this object into the RailLabel JSON format.""" + return JSONTransformData( + translation=self.position.to_json(), + quaternion=self.quaternion.to_json(), + ) diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index db97033..d6c7c3e 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -30,5 +30,10 @@ def test_from_json(transform_json, transform): assert actual == transform +def test_to_json(transform, transform_json): + actual = transform.to_json() + assert actual == transform_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 04b104482b21e98c928b0bcc50da0ad474f46446 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:53:28 +0100 Subject: [PATCH 131/190] feat: implement _attributes_to_json() --- raillabel/format/_attributes.py | 46 ++++++++++++++++++- .../test_raillabel/format/test_attributes.py | 23 +++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/raillabel/format/_attributes.py b/raillabel/format/_attributes.py index 5c8824e..a0bd42c 100644 --- a/raillabel/format/_attributes.py +++ b/raillabel/format/_attributes.py @@ -3,7 +3,13 @@ from __future__ import annotations -from raillabel.json_format import JSONAttributes +from raillabel.json_format import ( + JSONAttributes, + JSONBooleanAttribute, + JSONNumAttribute, + JSONTextAttribute, + JSONVecAttribute, +) def _attributes_from_json(json: JSONAttributes | None) -> dict[str, float | bool | str | list]: @@ -30,3 +36,41 @@ def _attributes_from_json(json: JSONAttributes | None) -> dict[str, float | bool attributes[vec_attribute.name] = vec_attribute.val return attributes + + +def _attributes_to_json(attributes: dict[str, float | bool | str | list]) -> JSONAttributes | None: + if len(attributes) == 0: + return None + + boolean_attributes = [] + num_attributes = [] + text_attributes = [] + vec_attributes = [] + + for name, value in attributes.items(): + if isinstance(value, bool): + boolean_attributes.append(JSONBooleanAttribute(name=name, val=value)) + + elif isinstance(value, (int, float)): + num_attributes.append(JSONNumAttribute(name=name, val=value)) + + elif isinstance(value, str): + text_attributes.append(JSONTextAttribute(name=name, val=value)) + + elif isinstance(value, list): + vec_attributes.append(JSONVecAttribute(name=name, val=value)) + + else: + raise UnsupportedAttributeTypeError(name, value) + + return JSONAttributes( + boolean=boolean_attributes, num=num_attributes, text=text_attributes, vec=vec_attributes + ) + + +class UnsupportedAttributeTypeError(TypeError): + def __init__(self, attribute_name: str, attribute_value: object) -> None: + super().__init__( + f"{attribute_value.__class__.__name__} of attribute {attribute_name} " + "is not a supported attribute type" + ) diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/test_raillabel/format/test_attributes.py index df13068..604652e 100644 --- a/tests/test_raillabel/format/test_attributes.py +++ b/tests/test_raillabel/format/test_attributes.py @@ -12,7 +12,11 @@ JSONTextAttribute, JSONVecAttribute, ) -from raillabel.format._attributes import _attributes_from_json +from raillabel.format._attributes import ( + _attributes_from_json, + _attributes_to_json, + UnsupportedAttributeTypeError, +) # == Fixtures ========================= @@ -72,5 +76,22 @@ def test_attributes_from_json__multiple_types( assert actual == attributes_multiple_types +def test_attributes_to_json__empty(): + actual = _attributes_to_json({}) + assert actual == None + + +def test_attributes_to_json__multiple_types( + attributes_multiple_types, attributes_multiple_types_json +): + actual = _attributes_to_json(attributes_multiple_types) + assert actual == attributes_multiple_types_json + + +def test_attributes_to_json__unsupported_type(): + with pytest.raises(UnsupportedAttributeTypeError): + _attributes_to_json({"attribute_with_unsupported_type": object}) + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 8e489088219f1dda24c92d52f89ce8e1c70003de Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 10:54:23 +0100 Subject: [PATCH 132/190] refactor: rename Transform fields --- raillabel/format/transform.py | 12 ++++++------ tests/test_raillabel/format/test_transform.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/raillabel/format/transform.py b/raillabel/format/transform.py index 7e4fa4a..defe708 100644 --- a/raillabel/format/transform.py +++ b/raillabel/format/transform.py @@ -15,23 +15,23 @@ class Transform: """A transformation between two coordinate systems.""" - position: Point3d + pos: Point3d "Translation with regards to the parent coordinate system." - quaternion: Quaternion + quat: Quaternion "Rotation quaternion with regards to the parent coordinate system." @classmethod def from_json(cls, json: JSONTransformData) -> Transform: """Construct an instant of this class from RailLabel JSON data.""" return Transform( - position=Point3d.from_json(json.translation), - quaternion=Quaternion.from_json(json.quaternion), + pos=Point3d.from_json(json.translation), + quat=Quaternion.from_json(json.quaternion), ) def to_json(self) -> JSONTransformData: """Export this object into the RailLabel JSON format.""" return JSONTransformData( - translation=self.position.to_json(), - quaternion=self.quaternion.to_json(), + translation=self.pos.to_json(), + quaternion=self.quat.to_json(), ) diff --git a/tests/test_raillabel/format/test_transform.py b/tests/test_raillabel/format/test_transform.py index d6c7c3e..9f816dc 100644 --- a/tests/test_raillabel/format/test_transform.py +++ b/tests/test_raillabel/format/test_transform.py @@ -19,7 +19,7 @@ def transform_json(point3d_json, quaternion_json) -> JSONTransformData: @pytest.fixture def transform(point3d, quaternion) -> Transform: - return Transform(position=point3d, quaternion=quaternion) + return Transform(pos=point3d, quat=quaternion) # == Tests ============================ From b1ebe2604feaf3d672e1d0c5c3591a2f922682e0 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 11:03:22 +0100 Subject: [PATCH 133/190] feat: implement Bbox.to_json() --- raillabel/format/bbox.py | 12 +++++++++++- tests/test_raillabel/format/test_bbox.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 682c704..b30b1e6 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -8,7 +8,7 @@ from raillabel.json_format import JSONBbox -from ._attributes import _attributes_from_json +from ._attributes import _attributes_from_json, _attributes_to_json from .point2d import Point2d from .size2d import Size2d @@ -43,6 +43,16 @@ def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: attributes=_attributes_from_json(json.attributes), ) + def to_json(self, uid: UUID, object_type: str) -> JSONBbox: + """Export this object into the RailLabel JSON format.""" + return JSONBbox( + name=self.name(object_type), + val=list(self.pos.to_json()) + list(self.size.to_json()), + coordinate_system=self.sensor, + uid=uid, + attributes=_attributes_to_json(self.attributes), + ) + def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" return f"{self.sensor}__bbox__{object_type}" diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index d6c670f..fb39bd2 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -62,5 +62,10 @@ def test_name(bbox): assert actual == "rgb_middle__bbox__person" +def test_to_json(bbox, bbox_json, bbox_uid): + actual = bbox.to_json(bbox_uid, object_type="person") + assert actual == bbox_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From f6bd567c2d4e4bfc7780350d0a8da4940d45b604 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 11:07:29 +0100 Subject: [PATCH 134/190] refactor: rename object field to object_uid in Annotation classes --- raillabel/format/bbox.py | 4 ++-- raillabel/format/cuboid.py | 4 ++-- raillabel/format/poly2d.py | 4 ++-- raillabel/format/poly3d.py | 4 ++-- raillabel/format/seg3d.py | 4 ++-- tests/test_raillabel/format/test_bbox.py | 2 +- tests/test_raillabel/format/test_cuboid.py | 2 +- tests/test_raillabel/format/test_poly2d.py | 2 +- tests/test_raillabel/format/test_poly3d.py | 2 +- tests/test_raillabel/format/test_seg3d.py | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index b30b1e6..c07939a 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -23,7 +23,7 @@ class Bbox: size: Size2d "The dimensions of the bbox in pixels from the top left corner to the bottom right corner." - object: UUID + object_uid: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -38,7 +38,7 @@ def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: return Bbox( pos=Point2d.from_json((json.val[0], json.val[1])), size=Size2d.from_json((json.val[2], json.val[3])), - object=object_uid, + object_uid=object_uid, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index ff6f9bc..886ea3f 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -28,7 +28,7 @@ class Cuboid: size: Size3d "The size of the cuboid in meters." - object: UUID + object_uid: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -44,7 +44,7 @@ def from_json(cls, json: JSONCuboid, object_uid: UUID) -> Cuboid: pos=Point3d.from_json((json.val[0], json.val[1], json.val[2])), quat=Quaternion.from_json((json.val[3], json.val[4], json.val[5], json.val[6])), size=Size3d.from_json((json.val[7], json.val[8], json.val[9])), - object=object_uid, + object_uid=object_uid, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 1a99b3d..cbf5b6f 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -22,7 +22,7 @@ class Poly2d: closed: bool "If True, this object represents a polygon and if False, it represents a polyline." - object: UUID + object_uid: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -40,7 +40,7 @@ def from_json(cls, json: JSONPoly2d, object_uid: UUID) -> Poly2d: for i in range(0, len(json.val), 2) ], closed=json.closed, - object=object_uid, + object_uid=object_uid, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 45d7f34..3561d61 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -22,7 +22,7 @@ class Poly3d: closed: bool "If True, this object represents a polygon and if False, it represents a polyline." - object: UUID + object_uid: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -40,7 +40,7 @@ def from_json(cls, json: JSONPoly3d, object_uid: UUID) -> Poly3d: for i in range(0, len(json.val), 3) ], closed=json.closed, - object=object_uid, + object_uid=object_uid, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index c4647e0..c201f4f 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -18,7 +18,7 @@ class Seg3d: point_ids: list[int] "The list of point indices." - object: UUID + object_uid: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -32,7 +32,7 @@ def from_json(cls, json: JSONVec, object_uid: UUID) -> Seg3d: """Construct an instant of this class from RailLabel JSON data.""" return Seg3d( point_ids=[int(point_id) for point_id in json.val], - object=object_uid, + object_uid=object_uid, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index fb39bd2..a22a368 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -45,7 +45,7 @@ def bbox( size=size2d, sensor="rgb_middle", attributes=attributes_multiple_types, - object=object_person_uid, + object_uid=object_person_uid, ) diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index 855777c..7d6614b 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -48,7 +48,7 @@ def cuboid( size=size3d, sensor="lidar", attributes=attributes_multiple_types, - object=object_person_uid, + object_uid=object_person_uid, ) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index d021f15..8ead75a 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -47,7 +47,7 @@ def poly2d( closed=True, sensor="rgb_middle", attributes=attributes_multiple_types, - object=object_track_uid, + object_uid=object_track_uid, ) diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 1467037..a3f3b75 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -46,7 +46,7 @@ def poly3d( closed=True, sensor="lidar", attributes=attributes_multiple_types, - object=object_track_uid, + object_uid=object_track_uid, ) diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index cd91e03..24a3853 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -40,7 +40,7 @@ def seg3d( point_ids=[1234, 5678], sensor="lidar", attributes=attributes_multiple_types, - object=object_person_uid, + object_uid=object_person_uid, ) From 2fd4a608252c0c9cfca0f6fb72b46d73e8a21e32 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 11:09:08 +0100 Subject: [PATCH 135/190] refactor: rename all *_uid fields to *_id --- CHANGELOG.md | 2 +- raillabel/format/bbox.py | 6 +-- raillabel/format/cuboid.py | 6 +-- raillabel/format/frame.py | 12 +++--- raillabel/format/frame_interval.py | 30 ++++++------- raillabel/format/poly2d.py | 6 +-- raillabel/format/poly3d.py | 6 +-- raillabel/format/scene.py | 4 +- raillabel/format/seg3d.py | 6 +-- .../__test_assets__/openlabel_v1_schema.json | 42 +++++++++---------- tests/test_raillabel/format/conftest.py | 14 +++---- tests/test_raillabel/format/test_bbox.py | 14 +++---- tests/test_raillabel/format/test_cuboid.py | 10 ++--- tests/test_raillabel/format/test_frame.py | 20 ++++----- .../format/test_frame_interval.py | 30 ++++++------- tests/test_raillabel/format/test_object.py | 4 +- tests/test_raillabel/format/test_poly2d.py | 10 ++--- tests/test_raillabel/format/test_poly3d.py | 10 ++--- tests/test_raillabel/format/test_scene.py | 16 +++---- tests/test_raillabel/format/test_seg3d.py | 10 ++--- 20 files changed, 129 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d37eb74..7986c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ Release - ```frame_data``` can only contain ```Num``` instances - ```object_data``` can not contain ```Num``` instances anymore - Major restructuring of the project directories -- ```FrameInterval.from_frame_uids()```: create ```FrameIntervals``` by providing a list of frame uids +- ```FrameInterval.from_frame_ids()```: create ```FrameIntervals``` by providing a list of frame uids - ```Object.object_data_pointers()```: generate ```ElementDataPointers``` - ```Scene.frame_intervals()```, ```Object.frame_intervals()```: generate ```FrameIntervals``` - ```Object.asdict()``` now provides also frame intervals and object data pointers, if the frames from the scene are provided diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index c07939a..6dddffb 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -23,7 +23,7 @@ class Bbox: size: Size2d "The dimensions of the bbox in pixels from the top left corner to the bottom right corner." - object_uid: UUID + object_id: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -33,12 +33,12 @@ class Bbox: "Additional information associated with the annotation." @classmethod - def from_json(cls, json: JSONBbox, object_uid: UUID) -> Bbox: + def from_json(cls, json: JSONBbox, object_id: UUID) -> Bbox: """Construct an instant of this class from RailLabel JSON data.""" return Bbox( pos=Point2d.from_json((json.val[0], json.val[1])), size=Size2d.from_json((json.val[2], json.val[3])), - object_uid=object_uid, + object_id=object_id, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 886ea3f..75d6d15 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -28,7 +28,7 @@ class Cuboid: size: Size3d "The size of the cuboid in meters." - object_uid: UUID + object_id: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -38,13 +38,13 @@ class Cuboid: "Additional information associated with the annotation." @classmethod - def from_json(cls, json: JSONCuboid, object_uid: UUID) -> Cuboid: + def from_json(cls, json: JSONCuboid, object_id: UUID) -> Cuboid: """Construct an instant of this class from RailLabel JSON data.""" return Cuboid( pos=Point3d.from_json((json.val[0], json.val[1], json.val[2])), quat=Quaternion.from_json((json.val[3], json.val[4], json.val[5], json.val[6])), size=Size3d.from_json((json.val[7], json.val[8], json.val[9])), - object_uid=object_uid, + object_id=object_id, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 168c658..182523b 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -90,21 +90,21 @@ def _annotations_from_json( annotations: dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d] = {} - for object_uid, object_data in json_object_data.items(): + for object_id, object_data in json_object_data.items(): for json_bbox in _resolve_none_to_empty_list(object_data.bbox): - annotations[json_bbox.uid] = Bbox.from_json(json_bbox, object_uid) + annotations[json_bbox.uid] = Bbox.from_json(json_bbox, object_id) for json_cuboid in _resolve_none_to_empty_list(object_data.cuboid): - annotations[json_cuboid.uid] = Cuboid.from_json(json_cuboid, object_uid) + annotations[json_cuboid.uid] = Cuboid.from_json(json_cuboid, object_id) for json_poly2d in _resolve_none_to_empty_list(object_data.poly2d): - annotations[json_poly2d.uid] = Poly2d.from_json(json_poly2d, object_uid) + annotations[json_poly2d.uid] = Poly2d.from_json(json_poly2d, object_id) for json_poly3d in _resolve_none_to_empty_list(object_data.poly3d): - annotations[json_poly3d.uid] = Poly3d.from_json(json_poly3d, object_uid) + annotations[json_poly3d.uid] = Poly3d.from_json(json_poly3d, object_id) for json_seg3d in _resolve_none_to_empty_list(object_data.vec): - annotations[json_seg3d.uid] = Seg3d.from_json(json_seg3d, object_uid) + annotations[json_seg3d.uid] = Seg3d.from_json(json_seg3d, object_id) return annotations diff --git a/raillabel/format/frame_interval.py b/raillabel/format/frame_interval.py index de0c851..6b9cd68 100644 --- a/raillabel/format/frame_interval.py +++ b/raillabel/format/frame_interval.py @@ -27,13 +27,13 @@ def from_json(cls, json: JSONFrameInterval) -> FrameInterval: ) @classmethod - def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: + def from_frame_ids(cls, frame_ids: list[int]) -> list[FrameInterval]: """Convert a list of frame uids into FrameIntervals. Example: ------- ```python - FrameInterval.from_frame_uids([0, 1, 2, 3, 9, 12, 13, 14]) == [ + FrameInterval.from_frame_ids([0, 1, 2, 3, 9, 12, 13, 14]) == [ FrameInterval(0, 3), FrameInterval(9, 9), FrameInterval(12, 14), @@ -41,11 +41,11 @@ def from_frame_uids(cls, frame_uids: list[int]) -> list[FrameInterval]: ``` """ - sorted_frame_uids = sorted(frame_uids) - frame_uid_intervals = _slice_into_intervals(sorted_frame_uids) + sorted_frame_ids = sorted(frame_ids) + frame_id_intervals = _slice_into_intervals(sorted_frame_ids) return [ - FrameInterval(start=interval[0], end=interval[-1]) for interval in frame_uid_intervals + FrameInterval(start=interval[0], end=interval[-1]) for interval in frame_id_intervals ] def to_json(self) -> JSONFrameInterval: @@ -60,23 +60,23 @@ def __len__(self) -> int: return abs(self.start - self.end) + 1 -def _slice_into_intervals(sorted_frame_uids: list[int]) -> list[list[int]]: - if len(sorted_frame_uids) == 0: +def _slice_into_intervals(sorted_frame_ids: list[int]) -> list[list[int]]: + if len(sorted_frame_ids) == 0: return [] - if len(sorted_frame_uids) == 1: - return [sorted_frame_uids] + if len(sorted_frame_ids) == 1: + return [sorted_frame_ids] intervals = [] interval_start_i = 0 - for i, frame_uid in enumerate(sorted_frame_uids[1:]): - previous_frame_uid = sorted_frame_uids[i] + for i, frame_id in enumerate(sorted_frame_ids[1:]): + previous_frame_id = sorted_frame_ids[i] - if frame_uid - previous_frame_uid > 1: - intervals.append(sorted_frame_uids[interval_start_i : i + 1]) + if frame_id - previous_frame_id > 1: + intervals.append(sorted_frame_ids[interval_start_i : i + 1]) interval_start_i = i + 1 - intervals.append(sorted_frame_uids[interval_start_i : len(sorted_frame_uids)]) - interval_start_i = len(sorted_frame_uids) + intervals.append(sorted_frame_ids[interval_start_i : len(sorted_frame_ids)]) + interval_start_i = len(sorted_frame_ids) return intervals diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index cbf5b6f..c89e530 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -22,7 +22,7 @@ class Poly2d: closed: bool "If True, this object represents a polygon and if False, it represents a polyline." - object_uid: UUID + object_id: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -32,7 +32,7 @@ class Poly2d: "Additional information associated with the annotation." @classmethod - def from_json(cls, json: JSONPoly2d, object_uid: UUID) -> Poly2d: + def from_json(cls, json: JSONPoly2d, object_id: UUID) -> Poly2d: """Construct an instant of this class from RailLabel JSON data.""" return Poly2d( points=[ @@ -40,7 +40,7 @@ def from_json(cls, json: JSONPoly2d, object_uid: UUID) -> Poly2d: for i in range(0, len(json.val), 2) ], closed=json.closed, - object_uid=object_uid, + object_id=object_id, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 3561d61..aa801ee 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -22,7 +22,7 @@ class Poly3d: closed: bool "If True, this object represents a polygon and if False, it represents a polyline." - object_uid: UUID + object_id: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -32,7 +32,7 @@ class Poly3d: "Additional information associated with the annotation." @classmethod - def from_json(cls, json: JSONPoly3d, object_uid: UUID) -> Poly3d: + def from_json(cls, json: JSONPoly3d, object_id: UUID) -> Poly3d: """Construct an instant of this class from RailLabel JSON data.""" return Poly3d( points=[ @@ -40,7 +40,7 @@ def from_json(cls, json: JSONPoly3d, object_uid: UUID) -> Poly3d: for i in range(0, len(json.val), 3) ], closed=json.closed, - object_uid=object_uid, + object_id=object_id, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 3c1b03e..c88f4a5 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -87,11 +87,11 @@ def _objects_from_json(json_objects: dict[UUID, JSONObject] | None) -> dict[UUID if json_objects is None: return {} - return {obj_uid: Object.from_json(json_obj) for obj_uid, json_obj in json_objects.items()} + return {obj_id: Object.from_json(json_obj) for obj_id, json_obj in json_objects.items()} def _frames_from_json(json_frames: dict[int, JSONFrame] | None) -> dict[int, Frame]: if json_frames is None: return {} - return {frame_uid: Frame.from_json(json_frame) for frame_uid, json_frame in json_frames.items()} + return {frame_id: Frame.from_json(json_frame) for frame_id, json_frame in json_frames.items()} diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index c201f4f..d040237 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -18,7 +18,7 @@ class Seg3d: point_ids: list[int] "The list of point indices." - object_uid: UUID + object_id: UUID "The uid of the object, this annotation belongs to." sensor: str @@ -28,11 +28,11 @@ class Seg3d: "Additional information associated with the annotation." @classmethod - def from_json(cls, json: JSONVec, object_uid: UUID) -> Seg3d: + def from_json(cls, json: JSONVec, object_id: UUID) -> Seg3d: """Construct an instant of this class from RailLabel JSON data.""" return Seg3d( point_ids=[int(point_id) for point_id in json.val], - object_uid=object_uid, + object_id=object_id, sensor=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) diff --git a/tests/__test_assets__/openlabel_v1_schema.json b/tests/__test_assets__/openlabel_v1_schema.json index 45997a6..8d0021e 100644 --- a/tests/__test_assets__/openlabel_v1_schema.json +++ b/tests/__test_assets__/openlabel_v1_schema.json @@ -24,12 +24,12 @@ "description": "Name of the action. It is a friendly name and not used for indexing.", "type": "string" }, - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this action is defined.", "type": "string" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "type": { "description": "The type of an action defines the class the action corresponds to.", @@ -253,12 +253,12 @@ "description": "Name of the context. It is a friendly name and not used for indexing.", "type": "string" }, - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this context is defined.", "type": "string" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "type": { "description": "The type of a context defines the class the context corresponds to.", @@ -469,12 +469,12 @@ "description": "Name of the event. It is a friendly name and not used for indexing.", "type": "string" }, - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this event is defined.", "type": "string" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "type": { "description": "The type of an event defines the class the event corresponds to.", @@ -907,12 +907,12 @@ "object_data_pointers": { "$ref": "#/definitions/element_data_pointers" }, - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this object is defined.", "type": "string" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "type": { "description": "The type of an object defines the class the object corresponds to.", @@ -1427,7 +1427,7 @@ "description": "Name of the relation. It is a friendly name and not used for indexing.", "type": "string" }, - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this relation is defined.", "type": "string" }, @@ -1445,8 +1445,8 @@ }, "type": "array" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "type": { "description": "The type of a relation defines the class the predicated of the relation corresponds to.", @@ -1461,8 +1461,8 @@ ], "type": "object" }, - "resource_uid": { - "description": "This is a JSON object that contains links to external resources. Resource_uid keys are strings containing numerical UIDs or 32 bytes UUIDs. Resource_uid values are strings describing the identifier of the element in the external resource.", + "resource_id": { + "description": "This is a JSON object that contains links to external resources. Resource_id keys are strings containing numerical UIDs or 32 bytes UUIDs. Resource_id values are strings describing the identifier of the element in the external resource.", "patternProperties": { "^(-?[0-9]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$": { "type": "string" @@ -1652,12 +1652,12 @@ "additionalProperties": true, "description": "A tag is a special type of label that can be attached to any type of content, such as images, data containers, folders. In ASAM OpenLABEL the main purpose of a tag is to allow adding metadata to scenario descriptions.", "properties": { - "ontology_uid": { + "ontology_id": { "description": "This is the UID of the ontology where the type of this tag is defined.", "type": "string" }, - "resource_uid": { - "$ref": "#/definitions/resource_uid" + "resource_id": { + "$ref": "#/definitions/resource_id" }, "tag_data": { "$ref": "#/definitions/tag_data" @@ -1668,7 +1668,7 @@ } }, "required": [ - "ontology_uid", + "ontology_id", "type" ], "type": "object" diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index b51eafb..0bc295a 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -1,9 +1,9 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 from .test_attributes import attributes_multiple_types, attributes_multiple_types_json -from .test_bbox import bbox, bbox_json, bbox_uid +from .test_bbox import bbox, bbox_json, bbox_id from .test_camera import camera, camera_json -from .test_cuboid import cuboid, cuboid_json, cuboid_uid +from .test_cuboid import cuboid, cuboid_json, cuboid_id from .test_frame import frame, frame_json from .test_frame_interval import frame_interval, frame_interval_json from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json @@ -14,20 +14,20 @@ from .test_object import ( object_person, object_person_json, - object_person_uid, + object_person_id, object_track, object_track_json, - object_track_uid, + object_track_id, ) from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json -from .test_poly2d import poly2d, poly2d_json, poly2d_uid -from .test_poly3d import poly3d, poly3d_json, poly3d_uid +from .test_poly2d import poly2d, poly2d_json, poly2d_id +from .test_poly3d import poly3d, poly3d_json, poly3d_id from .test_quaternion import quaternion, quaternion_json from .test_radar import radar, radar_json from .test_size2d import size2d, size2d_json from .test_size3d import size3d, size3d_json -from .test_seg3d import seg3d, seg3d_json, seg3d_uid +from .test_seg3d import seg3d, seg3d_json, seg3d_id from .test_sensor_reference import ( another_sensor_reference, another_sensor_reference_json, diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index a22a368..cc534e0 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -29,7 +29,7 @@ def bbox_json( @pytest.fixture -def bbox_uid() -> UUID: +def bbox_id() -> UUID: return UUID("2811f67c-124C-4fac-a275-20807d0471de") @@ -38,22 +38,22 @@ def bbox( point2d, size2d, attributes_multiple_types, - object_person_uid, + object_person_id, ) -> Bbox: return Bbox( pos=point2d, size=size2d, sensor="rgb_middle", attributes=attributes_multiple_types, - object_uid=object_person_uid, + object_id=object_person_id, ) # == Tests ============================ -def test_from_json(bbox, bbox_json, object_person_uid): - actual = Bbox.from_json(bbox_json, object_person_uid) +def test_from_json(bbox, bbox_json, object_person_id): + actual = Bbox.from_json(bbox_json, object_person_id) assert actual == bbox @@ -62,8 +62,8 @@ def test_name(bbox): assert actual == "rgb_middle__bbox__person" -def test_to_json(bbox, bbox_json, bbox_uid): - actual = bbox.to_json(bbox_uid, object_type="person") +def test_to_json(bbox, bbox_json, bbox_id): + actual = bbox.to_json(bbox_id, object_type="person") assert actual == bbox_json diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index 7d6614b..ce7bfe8 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -30,7 +30,7 @@ def cuboid_json( @pytest.fixture -def cuboid_uid() -> UUID: +def cuboid_id() -> UUID: return UUID("51def938-20BA-4699-95be-d6330c44cb77") @@ -40,7 +40,7 @@ def cuboid( size3d, quaternion, attributes_multiple_types, - object_person_uid, + object_person_id, ) -> Cuboid: return Cuboid( pos=point3d, @@ -48,15 +48,15 @@ def cuboid( size=size3d, sensor="lidar", attributes=attributes_multiple_types, - object_uid=object_person_uid, + object_id=object_person_id, ) # == Tests ============================ -def test_from_json(cuboid, cuboid_json, object_person_uid): - actual = Cuboid.from_json(cuboid_json, object_person_uid) +def test_from_json(cuboid, cuboid_json, object_person_id): + actual = Cuboid.from_json(cuboid_json, object_person_id) assert actual == cuboid diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index 26af71d..44814c1 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -53,15 +53,15 @@ def frame( another_sensor_reference, num, bbox, - bbox_uid, + bbox_id, cuboid, - cuboid_uid, + cuboid_id, poly2d, - poly2d_uid, + poly2d_id, poly3d, - poly3d_uid, + poly3d_id, seg3d, - seg3d_uid, + seg3d_id, ) -> dict: return Frame( timestamp=Decimal("1631337747.123123123"), @@ -71,11 +71,11 @@ def frame( }, frame_data={num.name: num}, annotations={ - bbox_uid: bbox, - cuboid_uid: cuboid, - poly2d_uid: poly2d, - poly3d_uid: poly3d, - seg3d_uid: seg3d, + bbox_id: bbox, + cuboid_id: cuboid, + poly2d_id: poly2d, + poly3d_id: poly3d, + seg3d_id: seg3d, }, ) diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/test_raillabel/format/test_frame_interval.py index 4508421..7633ec7 100644 --- a/tests/test_raillabel/format/test_frame_interval.py +++ b/tests/test_raillabel/format/test_frame_interval.py @@ -41,28 +41,28 @@ def test_len(): assert len(frame_interval) == 5 -def test_from_frame_uids_empty(): - frame_uids = [] +def test_from_frame_ids_empty(): + frame_ids = [] - assert FrameInterval.from_frame_uids(frame_uids) == [] + assert FrameInterval.from_frame_ids(frame_ids) == [] -def test_from_frame_uids_one_frame(): - frame_uids = [1] +def test_from_frame_ids_one_frame(): + frame_ids = [1] - assert FrameInterval.from_frame_uids(frame_uids) == [FrameInterval(1, 1)] + assert FrameInterval.from_frame_ids(frame_ids) == [FrameInterval(1, 1)] -def test_from_frame_uids_one_interval(): - frame_uids = [1, 2, 3, 4] +def test_from_frame_ids_one_interval(): + frame_ids = [1, 2, 3, 4] - assert FrameInterval.from_frame_uids(frame_uids) == [FrameInterval(1, 4)] + assert FrameInterval.from_frame_ids(frame_ids) == [FrameInterval(1, 4)] -def test_from_frame_uids_multiple_intervals(): - frame_uids = [0, 1, 2, 3, 6, 7, 9, 12, 13, 14] +def test_from_frame_ids_multiple_intervals(): + frame_ids = [0, 1, 2, 3, 6, 7, 9, 12, 13, 14] - assert FrameInterval.from_frame_uids(frame_uids) == [ + assert FrameInterval.from_frame_ids(frame_ids) == [ FrameInterval(0, 3), FrameInterval(6, 7), FrameInterval(9, 9), @@ -70,10 +70,10 @@ def test_from_frame_uids_multiple_intervals(): ] -def test_from_frame_uids_unsorted(): - frame_uids = [5, 2, 1, 3] +def test_from_frame_ids_unsorted(): + frame_ids = [5, 2, 1, 3] - assert FrameInterval.from_frame_uids(frame_uids) == [ + assert FrameInterval.from_frame_ids(frame_ids) == [ FrameInterval(1, 3), FrameInterval(5, 5), ] diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 0f94c7a..ae6b0da 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -30,7 +30,7 @@ def object_person() -> Object: @pytest.fixture -def object_person_uid() -> UUID: +def object_person_id() -> UUID: return UUID("b40ba3ad-0327-46ff-9c28-2506cfd6d934") @@ -51,7 +51,7 @@ def object_track() -> Object: @pytest.fixture -def object_track_uid() -> UUID: +def object_track_id() -> UUID: return UUID("cfcf9750-3BC3-4077-9079-a82c0c63976a") diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index 8ead75a..b91a7aa 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -31,7 +31,7 @@ def poly2d_json( @pytest.fixture -def poly2d_uid() -> UUID: +def poly2d_id() -> UUID: return UUID("013e7b34-62E5-435c-9412-87318c50f6d8") @@ -40,22 +40,22 @@ def poly2d( point2d, another_point2d, attributes_multiple_types, - object_track_uid, + object_track_id, ) -> Poly2d: return Poly2d( points=[point2d, another_point2d], closed=True, sensor="rgb_middle", attributes=attributes_multiple_types, - object_uid=object_track_uid, + object_id=object_track_id, ) # == Tests ============================ -def test_from_json(poly2d, poly2d_json, object_track_uid): - actual = Poly2d.from_json(poly2d_json, object_track_uid) +def test_from_json(poly2d, poly2d_json, object_track_id): + actual = Poly2d.from_json(poly2d_json, object_track_id) assert actual == poly2d diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index a3f3b75..b8752d0 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -30,7 +30,7 @@ def poly3d_json( @pytest.fixture -def poly3d_uid() -> UUID: +def poly3d_id() -> UUID: return UUID("0da87210-46F1-40e5-b661-20ea1c392f50") @@ -39,22 +39,22 @@ def poly3d( point3d, another_point3d, attributes_multiple_types, - object_track_uid, + object_track_id, ) -> Poly3d: return Poly3d( points=[point3d, another_point3d], closed=True, sensor="lidar", attributes=attributes_multiple_types, - object_uid=object_track_uid, + object_id=object_track_id, ) # == Tests ============================ -def test_from_json(poly3d, poly3d_json, object_track_uid): - actual = Poly3d.from_json(poly3d_json, object_track_uid) +def test_from_json(poly3d, poly3d_json, object_track_id): + actual = Poly3d.from_json(poly3d_json, object_track_id) assert actual == poly3d diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index ea2539f..c8a0f63 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -17,9 +17,9 @@ def scene_json( camera_json, lidar_json, radar_json, - object_person_uid, + object_person_id, object_person_json, - object_track_uid, + object_track_id, object_track_json, frame_json, ) -> JSONScene: @@ -43,8 +43,8 @@ def scene_json( "radar": radar_json[0], }, objects={ - object_person_uid: object_person_json, - object_track_uid: object_track_json, + object_person_id: object_person_json, + object_track_id: object_track_json, }, frames={1: frame_json}, ) @@ -57,9 +57,9 @@ def scene( camera, lidar, radar, - object_person_uid, + object_person_id, object_person, - object_track_uid, + object_track_id, object_track, frame, ) -> Scene: @@ -71,8 +71,8 @@ def scene( "radar": radar, }, objects={ - object_person_uid: object_person, - object_track_uid: object_track, + object_person_id: object_person, + object_track_id: object_track, }, frames={1: frame}, ) diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index 24a3853..8be83c6 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -27,28 +27,28 @@ def seg3d_json( @pytest.fixture -def seg3d_uid() -> UUID: +def seg3d_id() -> UUID: return UUID("d52e2b25-0B48-4899-86d5-4bc41be6b7d3") @pytest.fixture def seg3d( attributes_multiple_types, - object_person_uid, + object_person_id, ) -> Seg3d: return Seg3d( point_ids=[1234, 5678], sensor="lidar", attributes=attributes_multiple_types, - object_uid=object_person_uid, + object_id=object_person_id, ) # == Tests ============================ -def test_from_json(seg3d, seg3d_json, object_person_uid): - actual = Seg3d.from_json(seg3d_json, object_person_uid) +def test_from_json(seg3d, seg3d_json, object_person_id): + actual = Seg3d.from_json(seg3d_json, object_person_id) assert actual == seg3d From ef1fac615c20d901fd127887d54a73c518de1c07 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 11:10:06 +0100 Subject: [PATCH 136/190] docs: adapt annotation class object_id description --- raillabel/format/bbox.py | 2 +- raillabel/format/cuboid.py | 2 +- raillabel/format/poly2d.py | 2 +- raillabel/format/poly3d.py | 2 +- raillabel/format/seg3d.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index 6dddffb..b4ffe69 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -24,7 +24,7 @@ class Bbox: "The dimensions of the bbox in pixels from the top left corner to the bottom right corner." object_id: UUID - "The uid of the object, this annotation belongs to." + "The unique identifyer of the real-life object, this annotation belongs to." sensor: str "The uid of the sensor, this annotation is labeled in." diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 75d6d15..3422c43 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -29,7 +29,7 @@ class Cuboid: "The size of the cuboid in meters." object_id: UUID - "The uid of the object, this annotation belongs to." + "The unique identifyer of the real-life object, this annotation belongs to." sensor: str "The uid of the sensor, this annotation is labeled in." diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index c89e530..8cfd9a6 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -23,7 +23,7 @@ class Poly2d: "If True, this object represents a polygon and if False, it represents a polyline." object_id: UUID - "The uid of the object, this annotation belongs to." + "The unique identifyer of the real-life object, this annotation belongs to." sensor: str "The uid of the sensor, this annotation is labeled in." diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index aa801ee..f7b54a4 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -23,7 +23,7 @@ class Poly3d: "If True, this object represents a polygon and if False, it represents a polyline." object_id: UUID - "The uid of the object, this annotation belongs to." + "The unique identifyer of the real-life object, this annotation belongs to." sensor: str "The uid of the sensor, this annotation is labeled in." diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index d040237..8e8ff25 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -19,7 +19,7 @@ class Seg3d: "The list of point indices." object_id: UUID - "The uid of the object, this annotation belongs to." + "The unique identifyer of the real-life object, this annotation belongs to." sensor: str "The uid of the sensor, this annotation is labeled in." From 1ba0221c257997b327119788690a0597bb984584 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 13 Nov 2024 11:12:35 +0100 Subject: [PATCH 137/190] refactor: rename sensor field to sensor_id in Annotation classes --- raillabel/format/bbox.py | 10 +++++----- raillabel/format/cuboid.py | 8 ++++---- raillabel/format/num.py | 4 ++-- raillabel/format/poly2d.py | 8 ++++---- raillabel/format/poly3d.py | 8 ++++---- raillabel/format/seg3d.py | 8 ++++---- tests/test_raillabel/format/test_bbox.py | 2 +- tests/test_raillabel/format/test_cuboid.py | 2 +- tests/test_raillabel/format/test_num.py | 2 +- tests/test_raillabel/format/test_poly2d.py | 2 +- tests/test_raillabel/format/test_poly3d.py | 2 +- tests/test_raillabel/format/test_seg3d.py | 2 +- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/raillabel/format/bbox.py b/raillabel/format/bbox.py index b4ffe69..cd58b41 100644 --- a/raillabel/format/bbox.py +++ b/raillabel/format/bbox.py @@ -26,8 +26,8 @@ class Bbox: object_id: UUID "The unique identifyer of the real-life object, this annotation belongs to." - sensor: str - "The uid of the sensor, this annotation is labeled in." + sensor_id: str + "The unique identifyer of the sensor this annotation is labeled in." attributes: dict[str, float | bool | str | list] "Additional information associated with the annotation." @@ -39,7 +39,7 @@ def from_json(cls, json: JSONBbox, object_id: UUID) -> Bbox: pos=Point2d.from_json((json.val[0], json.val[1])), size=Size2d.from_json((json.val[2], json.val[3])), object_id=object_id, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) @@ -48,11 +48,11 @@ def to_json(self, uid: UUID, object_type: str) -> JSONBbox: return JSONBbox( name=self.name(object_type), val=list(self.pos.to_json()) + list(self.size.to_json()), - coordinate_system=self.sensor, + coordinate_system=self.sensor_id, uid=uid, attributes=_attributes_to_json(self.attributes), ) def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" - return f"{self.sensor}__bbox__{object_type}" + return f"{self.sensor_id}__bbox__{object_type}" diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 3422c43..2ca34d9 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -31,8 +31,8 @@ class Cuboid: object_id: UUID "The unique identifyer of the real-life object, this annotation belongs to." - sensor: str - "The uid of the sensor, this annotation is labeled in." + sensor_id: str + "The unique identifyer of the sensor this annotation is labeled in." attributes: dict[str, float | bool | str | list] "Additional information associated with the annotation." @@ -45,10 +45,10 @@ def from_json(cls, json: JSONCuboid, object_id: UUID) -> Cuboid: quat=Quaternion.from_json((json.val[3], json.val[4], json.val[5], json.val[6])), size=Size3d.from_json((json.val[7], json.val[8], json.val[9])), object_id=object_id, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" - return f"{self.sensor}__cuboid__{object_type}" + return f"{self.sensor_id}__cuboid__{object_type}" diff --git a/raillabel/format/num.py b/raillabel/format/num.py index d92aff6..21e7a23 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -18,7 +18,7 @@ class Num: val: float "This is the value of the number object." - sensor: str | None + sensor_id: str | None "A reference to the sensor, this value is represented in." @classmethod @@ -27,5 +27,5 @@ def from_json(cls, json: JSONNum) -> Num: return Num( name=json.name, val=json.val, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, ) diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index 8cfd9a6..c8030b4 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -25,8 +25,8 @@ class Poly2d: object_id: UUID "The unique identifyer of the real-life object, this annotation belongs to." - sensor: str - "The uid of the sensor, this annotation is labeled in." + sensor_id: str + "The unique identifyer of the sensor this annotation is labeled in." attributes: dict[str, float | bool | str | list] "Additional information associated with the annotation." @@ -41,10 +41,10 @@ def from_json(cls, json: JSONPoly2d, object_id: UUID) -> Poly2d: ], closed=json.closed, object_id=object_id, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" - return f"{self.sensor}__poly2d__{object_type}" + return f"{self.sensor_id}__poly2d__{object_type}" diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index f7b54a4..6c4777a 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -25,8 +25,8 @@ class Poly3d: object_id: UUID "The unique identifyer of the real-life object, this annotation belongs to." - sensor: str - "The uid of the sensor, this annotation is labeled in." + sensor_id: str + "The unique identifyer of the sensor this annotation is labeled in." attributes: dict[str, float | bool | str | list] "Additional information associated with the annotation." @@ -41,10 +41,10 @@ def from_json(cls, json: JSONPoly3d, object_id: UUID) -> Poly3d: ], closed=json.closed, object_id=object_id, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" - return f"{self.sensor}__poly3d__{object_type}" + return f"{self.sensor_id}__poly3d__{object_type}" diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 8e8ff25..76ef176 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -21,8 +21,8 @@ class Seg3d: object_id: UUID "The unique identifyer of the real-life object, this annotation belongs to." - sensor: str - "The uid of the sensor, this annotation is labeled in." + sensor_id: str + "The unique identifyer of the sensor this annotation is labeled in." attributes: dict[str, float | bool | str | list] "Additional information associated with the annotation." @@ -33,10 +33,10 @@ def from_json(cls, json: JSONVec, object_id: UUID) -> Seg3d: return Seg3d( point_ids=[int(point_id) for point_id in json.val], object_id=object_id, - sensor=json.coordinate_system, + sensor_id=json.coordinate_system, attributes=_attributes_from_json(json.attributes), ) def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" - return f"{self.sensor}__vec__{object_type}" + return f"{self.sensor_id}__vec__{object_type}" diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/test_raillabel/format/test_bbox.py index cc534e0..7c061de 100644 --- a/tests/test_raillabel/format/test_bbox.py +++ b/tests/test_raillabel/format/test_bbox.py @@ -43,7 +43,7 @@ def bbox( return Bbox( pos=point2d, size=size2d, - sensor="rgb_middle", + sensor_id="rgb_middle", attributes=attributes_multiple_types, object_id=object_person_id, ) diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index ce7bfe8..9cfeeb6 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -46,7 +46,7 @@ def cuboid( pos=point3d, quat=quaternion, size=size3d, - sensor="lidar", + sensor_id="lidar", attributes=attributes_multiple_types, object_id=object_person_id, ) diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py index dcde12d..2e0fae7 100644 --- a/tests/test_raillabel/format/test_num.py +++ b/tests/test_raillabel/format/test_num.py @@ -26,7 +26,7 @@ def num_json() -> JSONNum: @pytest.fixture def num() -> Num: return Num( - sensor="gps_imu", + sensor_id="gps_imu", name="velocity", val=49.21321, ) diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index b91a7aa..7773df9 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -45,7 +45,7 @@ def poly2d( return Poly2d( points=[point2d, another_point2d], closed=True, - sensor="rgb_middle", + sensor_id="rgb_middle", attributes=attributes_multiple_types, object_id=object_track_id, ) diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index b8752d0..6119d38 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -44,7 +44,7 @@ def poly3d( return Poly3d( points=[point3d, another_point3d], closed=True, - sensor="lidar", + sensor_id="lidar", attributes=attributes_multiple_types, object_id=object_track_id, ) diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index 8be83c6..0dd2716 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -38,7 +38,7 @@ def seg3d( ) -> Seg3d: return Seg3d( point_ids=[1234, 5678], - sensor="lidar", + sensor_id="lidar", attributes=attributes_multiple_types, object_id=object_person_id, ) From 7397672e5ba0c088b9f49eebbbfa8db7f81c7206 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:12:15 +0100 Subject: [PATCH 138/190] feat: implement Cuboid.to_json() --- raillabel/format/cuboid.py | 12 +++++++++++- tests/test_raillabel/format/test_cuboid.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/raillabel/format/cuboid.py b/raillabel/format/cuboid.py index 2ca34d9..227b9f9 100644 --- a/raillabel/format/cuboid.py +++ b/raillabel/format/cuboid.py @@ -8,7 +8,7 @@ from raillabel.json_format import JSONCuboid -from ._attributes import _attributes_from_json +from ._attributes import _attributes_from_json, _attributes_to_json from .point3d import Point3d from .quaternion import Quaternion from .size3d import Size3d @@ -49,6 +49,16 @@ def from_json(cls, json: JSONCuboid, object_id: UUID) -> Cuboid: attributes=_attributes_from_json(json.attributes), ) + def to_json(self, uid: UUID, object_type: str) -> JSONCuboid: + """Export this object into the RailLabel JSON format.""" + return JSONCuboid( + name=self.name(object_type), + val=list(self.pos.to_json()) + list(self.quat.to_json()) + list(self.size.to_json()), + coordinate_system=self.sensor_id, + uid=uid, + attributes=_attributes_to_json(self.attributes), + ) + def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" return f"{self.sensor_id}__cuboid__{object_type}" diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/test_raillabel/format/test_cuboid.py index 9cfeeb6..b71d46f 100644 --- a/tests/test_raillabel/format/test_cuboid.py +++ b/tests/test_raillabel/format/test_cuboid.py @@ -65,5 +65,10 @@ def test_name(cuboid): assert actual == "lidar__cuboid__person" +def test_to_json(cuboid, cuboid_json, cuboid_id): + actual = cuboid.to_json(cuboid_id, object_type="person") + assert actual == cuboid_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 342f6271504894e59b971d3eae1ddfa9d99f5e03 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:18:42 +0100 Subject: [PATCH 139/190] feat: implement Poly2d.to_json() --- raillabel/format/_util.py | 8 ++++++++ raillabel/format/poly2d.py | 15 ++++++++++++++- tests/test_raillabel/format/test_poly2d.py | 5 +++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 raillabel/format/_util.py diff --git a/raillabel/format/_util.py b/raillabel/format/_util.py new file mode 100644 index 0000000..dfb567e --- /dev/null +++ b/raillabel/format/_util.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + + +def _flatten_list(list_of_tuples: list[tuple]) -> list: + return [item for tup in list_of_tuples for item in tup] diff --git a/raillabel/format/poly2d.py b/raillabel/format/poly2d.py index c8030b4..ed20388 100644 --- a/raillabel/format/poly2d.py +++ b/raillabel/format/poly2d.py @@ -8,7 +8,8 @@ from raillabel.json_format import JSONPoly2d -from ._attributes import _attributes_from_json +from ._attributes import _attributes_from_json, _attributes_to_json +from ._util import _flatten_list from .point2d import Point2d @@ -45,6 +46,18 @@ def from_json(cls, json: JSONPoly2d, object_id: UUID) -> Poly2d: attributes=_attributes_from_json(json.attributes), ) + def to_json(self, uid: UUID, object_type: str) -> JSONPoly2d: + """Export this object into the RailLabel JSON format.""" + return JSONPoly2d( + name=self.name(object_type), + val=_flatten_list([point.to_json() for point in self.points]), + closed=self.closed, + mode="MODE_POLY2D_ABSOLUTE", + coordinate_system=self.sensor_id, + uid=uid, + attributes=_attributes_to_json(self.attributes), + ) + def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" return f"{self.sensor_id}__poly2d__{object_type}" diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index 7773df9..11da96a 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -64,5 +64,10 @@ def test_name(poly2d): assert actual == "rgb_middle__poly2d__person" +def test_to_json(poly2d, poly2d_json, poly2d_id): + actual = poly2d.to_json(poly2d_id, object_type="person") + assert actual == poly2d_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 6d354b5f6ff312cb8069a2fa8bf6bf270f8391dc Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:20:29 +0100 Subject: [PATCH 140/190] feat: implement Poly3d.to_json() --- raillabel/format/poly3d.py | 14 +++++++++++++- tests/test_raillabel/format/test_poly3d.py | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/raillabel/format/poly3d.py b/raillabel/format/poly3d.py index 6c4777a..b49ff4e 100644 --- a/raillabel/format/poly3d.py +++ b/raillabel/format/poly3d.py @@ -8,7 +8,8 @@ from raillabel.json_format import JSONPoly3d -from ._attributes import _attributes_from_json +from ._attributes import _attributes_from_json, _attributes_to_json +from ._util import _flatten_list from .point3d import Point3d @@ -45,6 +46,17 @@ def from_json(cls, json: JSONPoly3d, object_id: UUID) -> Poly3d: attributes=_attributes_from_json(json.attributes), ) + def to_json(self, uid: UUID, object_type: str) -> JSONPoly3d: + """Export this object into the RailLabel JSON format.""" + return JSONPoly3d( + name=self.name(object_type), + val=_flatten_list([point.to_json() for point in self.points]), + closed=self.closed, + coordinate_system=self.sensor_id, + uid=uid, + attributes=_attributes_to_json(self.attributes), + ) + def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" return f"{self.sensor_id}__poly3d__{object_type}" diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 6119d38..66288f5 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -63,5 +63,10 @@ def test_name(poly3d): assert actual == "lidar__poly3d__person" +def test_to_json(poly3d, poly3d_json, poly3d_id): + actual = poly3d.to_json(poly3d_id, object_type="person") + assert actual == poly3d_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 6db3bed3df50604ce15aa3056a9588e0f5f23d65 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:23:37 +0100 Subject: [PATCH 141/190] feat: implement Seg3d.to_json() --- raillabel/format/seg3d.py | 12 +++++++++++- tests/test_raillabel/format/test_seg3d.py | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/raillabel/format/seg3d.py b/raillabel/format/seg3d.py index 76ef176..31dd7c0 100644 --- a/raillabel/format/seg3d.py +++ b/raillabel/format/seg3d.py @@ -8,7 +8,7 @@ from raillabel.json_format import JSONVec -from ._attributes import _attributes_from_json +from ._attributes import _attributes_from_json, _attributes_to_json @dataclass @@ -37,6 +37,16 @@ def from_json(cls, json: JSONVec, object_id: UUID) -> Seg3d: attributes=_attributes_from_json(json.attributes), ) + def to_json(self, uid: UUID, object_type: str) -> JSONVec: + """Export this object into the RailLabel JSON format.""" + return JSONVec( + name=self.name(object_type), + val=self.point_ids, + coordinate_system=self.sensor_id, + uid=uid, + attributes=_attributes_to_json(self.attributes), + ) + def name(self, object_type: str) -> str: """Return the name of the annotation used for indexing in the object data pointers.""" return f"{self.sensor_id}__vec__{object_type}" diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/test_raillabel/format/test_seg3d.py index 0dd2716..a290aa6 100644 --- a/tests/test_raillabel/format/test_seg3d.py +++ b/tests/test_raillabel/format/test_seg3d.py @@ -19,7 +19,7 @@ def seg3d_json( ) -> JSONVec: return JSONVec( uid="d52e2b25-0B48-4899-86d5-4bc41be6b7d3", - name="rgb_middle__seg3d__person", + name="lidar__vec__person", val=[1234, 5678], coordinate_system="lidar", attributes=attributes_multiple_types_json, @@ -57,5 +57,10 @@ def test_name(seg3d): assert actual == "lidar__vec__person" +def test_to_json(seg3d, seg3d_json, seg3d_id): + actual = seg3d.to_json(seg3d_id, object_type="person") + assert actual == seg3d_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 43db3fc5d1d287236209de88031ab4373f655c6e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:27:30 +0100 Subject: [PATCH 142/190] feat: implement Num.to_json() --- raillabel/format/num.py | 10 ++++++++++ tests/test_raillabel/format/test_num.py | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/raillabel/format/num.py b/raillabel/format/num.py index 21e7a23..acd2d62 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -4,6 +4,7 @@ from __future__ import annotations from dataclasses import dataclass +from uuid import UUID from raillabel.json_format import JSONNum @@ -29,3 +30,12 @@ def from_json(cls, json: JSONNum) -> Num: val=json.val, sensor_id=json.coordinate_system, ) + + def to_json(self, uid: UUID) -> JSONNum: + """Export this object into the RailLabel JSON format.""" + return JSONNum( + name=self.name, + val=self.val, + coordinate_system=self.sensor_id, + uid=uid, + ) diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py index 2e0fae7..092c700 100644 --- a/tests/test_raillabel/format/test_num.py +++ b/tests/test_raillabel/format/test_num.py @@ -14,15 +14,20 @@ @pytest.fixture -def num_json() -> JSONNum: +def num_json(num_id) -> JSONNum: return JSONNum( - uid="78f0ad89-2750-4a30-9d66-44c9da73a714", + uid=num_id, name="velocity", val=49.21321, coordinate_system="gps_imu", ) +@pytest.fixture +def num_id() -> UUID: + return UUID("78f0ad89-2750-4a30-9d66-44c9da73a714") + + @pytest.fixture def num() -> Num: return Num( @@ -40,5 +45,10 @@ def test_from_json(num, num_json): assert actual == num +def test_to_json(num, num_json, num_id): + actual = num.to_json(num_id) + assert actual == num_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 5e6e4c74a6480014acab931f05f92d76a7383d48 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 05:31:06 +0100 Subject: [PATCH 143/190] feat: implement SensorReference.to_json() --- raillabel/format/sensor_reference.py | 11 ++++++++++- tests/test_raillabel/format/test_sensor_reference.py | 12 +++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/raillabel/format/sensor_reference.py b/raillabel/format/sensor_reference.py index 34c37ae..3a547c8 100644 --- a/raillabel/format/sensor_reference.py +++ b/raillabel/format/sensor_reference.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from decimal import Decimal -from raillabel.json_format import JSONStreamSync +from raillabel.json_format import JSONStreamSync, JSONStreamSyncProperties, JSONStreamSyncTimestamp @dataclass @@ -27,3 +27,12 @@ def from_json(cls, json: JSONStreamSync) -> SensorReference: timestamp=Decimal(json.stream_properties.sync.timestamp), uri=json.uri, ) + + def to_json(self) -> JSONStreamSync: + """Export this object into the RailLabel JSON format.""" + return JSONStreamSync( + stream_properties=JSONStreamSyncProperties( + sync=JSONStreamSyncTimestamp(timestamp=str(self.timestamp)), + ), + uri=self.uri, + ) diff --git a/tests/test_raillabel/format/test_sensor_reference.py b/tests/test_raillabel/format/test_sensor_reference.py index 8078274..95ed9d3 100644 --- a/tests/test_raillabel/format/test_sensor_reference.py +++ b/tests/test_raillabel/format/test_sensor_reference.py @@ -50,10 +50,20 @@ def test_from_json(sensor_reference, sensor_reference_json): assert actual == sensor_reference -def test_from_json(another_sensor_reference, another_sensor_reference_json): +def test_from_json__another(another_sensor_reference, another_sensor_reference_json): actual = SensorReference.from_json(another_sensor_reference_json) assert actual == another_sensor_reference +def test_to_json(sensor_reference, sensor_reference_json): + actual = sensor_reference.to_json() + assert actual == sensor_reference_json + + +def test_to_json__another(another_sensor_reference, another_sensor_reference_json): + actual = another_sensor_reference.to_json() + assert actual == another_sensor_reference_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From d1bd3b977b6258ad5bca95ca5f1889a9b42682b5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 06:08:25 +0100 Subject: [PATCH 144/190] fix: extra fields allowed in json format classes --- raillabel/format/frame.py | 10 ++++----- raillabel/json_format/__init__.py | 3 ++- raillabel/json_format/attributes.py | 2 +- raillabel/json_format/bbox.py | 2 +- raillabel/json_format/boolean_attribute.py | 2 +- raillabel/json_format/coordinate_system.py | 2 +- raillabel/json_format/cuboid.py | 2 +- raillabel/json_format/element_data_pointer.py | 2 +- raillabel/json_format/frame.py | 6 ++--- raillabel/json_format/frame_interval.py | 2 +- raillabel/json_format/num.py | 2 +- raillabel/json_format/num_attribute.py | 2 +- raillabel/json_format/object.py | 2 +- raillabel/json_format/object_data.py | 8 ++++++- raillabel/json_format/poly2d.py | 2 +- raillabel/json_format/poly3d.py | 2 +- raillabel/json_format/scene.py | 4 ++-- raillabel/json_format/stream_camera.py | 6 ++--- raillabel/json_format/stream_other.py | 2 +- raillabel/json_format/stream_radar.py | 6 ++--- raillabel/json_format/stream_sync.py | 6 ++--- raillabel/json_format/text_attribute.py | 2 +- raillabel/json_format/transform_data.py | 2 +- raillabel/json_format/vec.py | 2 +- raillabel/json_format/vec_attribute.py | 2 +- tests/test_raillabel/format/conftest.py | 2 +- tests/test_raillabel/format/test_frame.py | 22 ++++++++++++++----- 27 files changed, 62 insertions(+), 45 deletions(-) diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 182523b..686715e 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -91,19 +91,19 @@ def _annotations_from_json( annotations: dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d] = {} for object_id, object_data in json_object_data.items(): - for json_bbox in _resolve_none_to_empty_list(object_data.bbox): + for json_bbox in _resolve_none_to_empty_list(object_data.object_data.bbox): annotations[json_bbox.uid] = Bbox.from_json(json_bbox, object_id) - for json_cuboid in _resolve_none_to_empty_list(object_data.cuboid): + for json_cuboid in _resolve_none_to_empty_list(object_data.object_data.cuboid): annotations[json_cuboid.uid] = Cuboid.from_json(json_cuboid, object_id) - for json_poly2d in _resolve_none_to_empty_list(object_data.poly2d): + for json_poly2d in _resolve_none_to_empty_list(object_data.object_data.poly2d): annotations[json_poly2d.uid] = Poly2d.from_json(json_poly2d, object_id) - for json_poly3d in _resolve_none_to_empty_list(object_data.poly3d): + for json_poly3d in _resolve_none_to_empty_list(object_data.object_data.poly3d): annotations[json_poly3d.uid] = Poly3d.from_json(json_poly3d, object_id) - for json_seg3d in _resolve_none_to_empty_list(object_data.vec): + for json_seg3d in _resolve_none_to_empty_list(object_data.object_data.vec): annotations[json_seg3d.uid] = Seg3d.from_json(json_seg3d, object_id) return annotations diff --git a/raillabel/json_format/__init__.py b/raillabel/json_format/__init__.py index 059d7d1..9ad9f51 100644 --- a/raillabel/json_format/__init__.py +++ b/raillabel/json_format/__init__.py @@ -14,7 +14,7 @@ from .num import JSONNum from .num_attribute import JSONNumAttribute from .object import JSONObject -from .object_data import JSONObjectData +from .object_data import JSONAnnotations, JSONObjectData from .poly2d import JSONPoly2d from .poly3d import JSONPoly3d from .scene import JSONScene, JSONSceneContent @@ -28,6 +28,7 @@ from .vec_attribute import JSONVecAttribute __all__ = [ + "JSONAnnotations", "JSONAttributes", "JSONBbox", "JSONBooleanAttribute", diff --git a/raillabel/json_format/attributes.py b/raillabel/json_format/attributes.py index 976c44d..68aaacb 100644 --- a/raillabel/json_format/attributes.py +++ b/raillabel/json_format/attributes.py @@ -11,7 +11,7 @@ from .vec_attribute import JSONVecAttribute -class JSONAttributes(BaseModel): +class JSONAttributes(BaseModel, extra="forbid"): """Attributes is the alias of element data that can be nested inside geometric object data. For example, a certain bounding box can have attributes related to its score, visibility, etc. diff --git a/raillabel/json_format/bbox.py b/raillabel/json_format/bbox.py index f82c8c5..944db2c 100644 --- a/raillabel/json_format/bbox.py +++ b/raillabel/json_format/bbox.py @@ -10,7 +10,7 @@ from .attributes import JSONAttributes -class JSONBbox(BaseModel): +class JSONBbox(BaseModel, extra="forbid"): """A 2D bounding box is defined as a 4-dimensional vector [x, y, w, h]. [x, y] is the center of the bounding box and [w, h] represent the width (horizontal, diff --git a/raillabel/json_format/boolean_attribute.py b/raillabel/json_format/boolean_attribute.py index afda14b..91c86ea 100644 --- a/raillabel/json_format/boolean_attribute.py +++ b/raillabel/json_format/boolean_attribute.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONBooleanAttribute(BaseModel): +class JSONBooleanAttribute(BaseModel, extra="forbid"): """A boolean attribute.""" name: str diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py index e1d664a..fedc174 100644 --- a/raillabel/json_format/coordinate_system.py +++ b/raillabel/json_format/coordinate_system.py @@ -10,7 +10,7 @@ from .transform_data import JSONTransformData -class JSONCoordinateSystem(BaseModel): +class JSONCoordinateSystem(BaseModel, extra="forbid"): """A 3D reference frame.""" parent: Literal["base", ""] diff --git a/raillabel/json_format/cuboid.py b/raillabel/json_format/cuboid.py index 1a918a4..a58431a 100644 --- a/raillabel/json_format/cuboid.py +++ b/raillabel/json_format/cuboid.py @@ -10,7 +10,7 @@ from .attributes import JSONAttributes -class JSONCuboid(BaseModel): +class JSONCuboid(BaseModel, extra="forbid"): """A cuboid or 3D bounding box. It is defined by the position of its center, the rotation in 3D, and its dimensions. diff --git a/raillabel/json_format/element_data_pointer.py b/raillabel/json_format/element_data_pointer.py index 48cfa14..745c9c0 100644 --- a/raillabel/json_format/element_data_pointer.py +++ b/raillabel/json_format/element_data_pointer.py @@ -10,7 +10,7 @@ from .frame_interval import JSONFrameInterval -class JSONElementDataPointer(BaseModel): +class JSONElementDataPointer(BaseModel, extra="forbid"): """A pointer to element data of elements. It is indexed by 'name', and containing information about the element data type, for example, diff --git a/raillabel/json_format/frame.py b/raillabel/json_format/frame.py index 797baec..cfc1386 100644 --- a/raillabel/json_format/frame.py +++ b/raillabel/json_format/frame.py @@ -13,7 +13,7 @@ from .stream_sync import JSONStreamSync -class JSONFrame(BaseModel): +class JSONFrame(BaseModel, extra="forbid"): """A frame is a container of dynamic, timewise, information.""" frame_properties: JSONFrameProperties | None = None @@ -24,7 +24,7 @@ class JSONFrame(BaseModel): strings containing 32 bytes UUIDs. Object values contain an 'object_data' JSON object.""" -class JSONFrameProperties(BaseModel): +class JSONFrameProperties(BaseModel, extra="forbid"): """Container of frame information other than annotations.""" timestamp: Decimal | str | None = None @@ -38,7 +38,7 @@ class JSONFrameProperties(BaseModel): "Additional data to describe attributes of the frame (like GPS position)." -class JSONFrameData(BaseModel): +class JSONFrameData(BaseModel, extra="forbid"): """Additional data to describe attributes of the frame (like GPS position).""" num: list[JSONNum] | None = None diff --git a/raillabel/json_format/frame_interval.py b/raillabel/json_format/frame_interval.py index b56cbe9..d7f9034 100644 --- a/raillabel/json_format/frame_interval.py +++ b/raillabel/json_format/frame_interval.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONFrameInterval(BaseModel): +class JSONFrameInterval(BaseModel, extra="forbid"): """A frame interval defines a starting and ending frame number as a closed interval. That means the interval includes the limit frame numbers. diff --git a/raillabel/json_format/num.py b/raillabel/json_format/num.py index b51ac76..be9089b 100644 --- a/raillabel/json_format/num.py +++ b/raillabel/json_format/num.py @@ -8,7 +8,7 @@ from pydantic import BaseModel -class JSONNum(BaseModel): +class JSONNum(BaseModel, extra="forbid"): """A number.""" name: str diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py index 700209c..e829712 100644 --- a/raillabel/json_format/num_attribute.py +++ b/raillabel/json_format/num_attribute.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONNumAttribute(BaseModel): +class JSONNumAttribute(BaseModel, extra="forbid"): """A number attribute.""" name: str diff --git a/raillabel/json_format/object.py b/raillabel/json_format/object.py index 091843a..514f1e8 100644 --- a/raillabel/json_format/object.py +++ b/raillabel/json_format/object.py @@ -9,7 +9,7 @@ from .frame_interval import JSONFrameInterval -class JSONObject(BaseModel): +class JSONObject(BaseModel, extra="forbid"): """An object is the main type of annotation element. Object is designed to represent spatiotemporal entities, such as physical objects in the real diff --git a/raillabel/json_format/object_data.py b/raillabel/json_format/object_data.py index bffa98a..04609e7 100644 --- a/raillabel/json_format/object_data.py +++ b/raillabel/json_format/object_data.py @@ -12,9 +12,15 @@ from .vec import JSONVec -class JSONObjectData(BaseModel): +class JSONObjectData(BaseModel, extra="forbid"): """Container of annotations of an object in a frame.""" + object_data: JSONAnnotations + + +class JSONAnnotations(BaseModel, extra="forbid"): + """Container of the annotations by type.""" + bbox: list[JSONBbox] | None = None cuboid: list[JSONCuboid] | None = None poly2d: list[JSONPoly2d] | None = None diff --git a/raillabel/json_format/poly2d.py b/raillabel/json_format/poly2d.py index 9d31a16..5ccda8c 100644 --- a/raillabel/json_format/poly2d.py +++ b/raillabel/json_format/poly2d.py @@ -11,7 +11,7 @@ from .attributes import JSONAttributes -class JSONPoly2d(BaseModel): +class JSONPoly2d(BaseModel, extra="forbid"): """A 2D polyline defined as a sequence of 2D points.""" name: str diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py index eb72dd2..714204a 100644 --- a/raillabel/json_format/poly3d.py +++ b/raillabel/json_format/poly3d.py @@ -10,7 +10,7 @@ from .attributes import JSONAttributes -class JSONPoly3d(BaseModel): +class JSONPoly3d(BaseModel, extra="forbid"): """A 3D polyline defined as a sequence of 3D points.""" name: str diff --git a/raillabel/json_format/scene.py b/raillabel/json_format/scene.py index 65326ec..36135d6 100644 --- a/raillabel/json_format/scene.py +++ b/raillabel/json_format/scene.py @@ -17,13 +17,13 @@ from .stream_radar import JSONStreamRadar -class JSONScene(BaseModel): +class JSONScene(BaseModel, extra="forbid"): """Root RailLabel object.""" openlabel: JSONSceneContent -class JSONSceneContent(BaseModel): +class JSONSceneContent(BaseModel, extra="forbid"): """Container of all scene content.""" metadata: JSONMetadata diff --git a/raillabel/json_format/stream_camera.py b/raillabel/json_format/stream_camera.py index b8f27ec..1541c64 100644 --- a/raillabel/json_format/stream_camera.py +++ b/raillabel/json_format/stream_camera.py @@ -8,7 +8,7 @@ from pydantic import BaseModel -class JSONStreamCamera(BaseModel): +class JSONStreamCamera(BaseModel, extra="forbid"): """A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a camera sensor. @@ -27,13 +27,13 @@ class JSONStreamCamera(BaseModel): "Description of the stream." -class JSONStreamCameraProperties(BaseModel): +class JSONStreamCameraProperties(BaseModel, extra="forbid"): """Intrinsic calibration of the stream.""" intrinsics_pinhole: JSONIntrinsicsPinhole -class JSONIntrinsicsPinhole(BaseModel): +class JSONIntrinsicsPinhole(BaseModel, extra="forbid"): """JSON object defining an instance of the intrinsic parameters of a pinhole camera.""" camera_matrix: tuple[ diff --git a/raillabel/json_format/stream_other.py b/raillabel/json_format/stream_other.py index a1d47ba..75fd241 100644 --- a/raillabel/json_format/stream_other.py +++ b/raillabel/json_format/stream_other.py @@ -8,7 +8,7 @@ from pydantic import BaseModel -class JSONStreamOther(BaseModel): +class JSONStreamOther(BaseModel, extra="forbid"): """A stream describes the source of a data sequence, usually a sensor. This specific object describes a sensor without intrinsic calibration. diff --git a/raillabel/json_format/stream_radar.py b/raillabel/json_format/stream_radar.py index 3c4a19c..1ac48e3 100644 --- a/raillabel/json_format/stream_radar.py +++ b/raillabel/json_format/stream_radar.py @@ -8,7 +8,7 @@ from pydantic import BaseModel -class JSONStreamRadar(BaseModel): +class JSONStreamRadar(BaseModel, extra="forbid"): """A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a radar sensor. @@ -27,13 +27,13 @@ class JSONStreamRadar(BaseModel): "Description of the stream." -class JSONStreamRadarProperties(BaseModel): +class JSONStreamRadarProperties(BaseModel, extra="forbid"): """Intrinsic calibration of the stream.""" intrinsics_radar: JSONIntrinsicsRadar -class JSONIntrinsicsRadar(BaseModel): +class JSONIntrinsicsRadar(BaseModel, extra="forbid"): """JSON object defining an instance of the intrinsic parameters of a radar.""" resolution_px_per_m: float diff --git a/raillabel/json_format/stream_sync.py b/raillabel/json_format/stream_sync.py index 2757924..5e48974 100644 --- a/raillabel/json_format/stream_sync.py +++ b/raillabel/json_format/stream_sync.py @@ -8,20 +8,20 @@ from pydantic import BaseModel -class JSONStreamSync(BaseModel): +class JSONStreamSync(BaseModel, extra="forbid"): """Syncronization information of a stream in a frame.""" stream_properties: JSONStreamSyncProperties uri: str | None = None -class JSONStreamSyncProperties(BaseModel): +class JSONStreamSyncProperties(BaseModel, extra="forbid"): """The sync information.""" sync: JSONStreamSyncTimestamp -class JSONStreamSyncTimestamp(BaseModel): +class JSONStreamSyncTimestamp(BaseModel, extra="forbid"): """The timestamp of a stream sync.""" timestamp: Decimal | str diff --git a/raillabel/json_format/text_attribute.py b/raillabel/json_format/text_attribute.py index cb1fb23..24cc389 100644 --- a/raillabel/json_format/text_attribute.py +++ b/raillabel/json_format/text_attribute.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONTextAttribute(BaseModel): +class JSONTextAttribute(BaseModel, extra="forbid"): """A text attribute.""" name: str diff --git a/raillabel/json_format/transform_data.py b/raillabel/json_format/transform_data.py index edf7c87..4d5a235 100644 --- a/raillabel/json_format/transform_data.py +++ b/raillabel/json_format/transform_data.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONTransformData(BaseModel): +class JSONTransformData(BaseModel, extra="forbid"): """The translation and rotation of one coordinate system to another.""" translation: tuple[float, float, float] diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py index daa01c8..d9dec89 100644 --- a/raillabel/json_format/vec.py +++ b/raillabel/json_format/vec.py @@ -11,7 +11,7 @@ from .attributes import JSONAttributes -class JSONVec(BaseModel): +class JSONVec(BaseModel, extra="forbid"): """A vector (list) of numbers.""" name: str diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py index 49abce7..c6df6a2 100644 --- a/raillabel/json_format/vec_attribute.py +++ b/raillabel/json_format/vec_attribute.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class JSONVecAttribute(BaseModel): +class JSONVecAttribute(BaseModel, extra="forbid"): """A vec attribute.""" name: str diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 0bc295a..8ba0856 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -10,7 +10,7 @@ from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json from .test_lidar import lidar, lidar_json from .test_metadata import metadata, metadata_json -from .test_num import num, num_json +from .test_num import num, num_json, num_id from .test_object import ( object_person, object_person_json, diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index 44814c1..07cd720 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -8,7 +8,13 @@ import pytest from raillabel.format import Frame -from raillabel.json_format import JSONFrame, JSONFrameData, JSONFrameProperties, JSONObjectData +from raillabel.json_format import ( + JSONFrame, + JSONFrameData, + JSONFrameProperties, + JSONObjectData, + JSONAnnotations, +) # == Fixtures ========================= @@ -35,13 +41,17 @@ def frame_json( ), objects={ "cfcf9750-3bc3-4077-9079-a82c0c63976a": JSONObjectData( - poly2d=[poly2d_json], - poly3d=[poly3d_json], + object_data=JSONAnnotations( + poly2d=[poly2d_json], + poly3d=[poly3d_json], + ) ), "b40ba3ad-0327-46ff-9c28-2506cfd6d934": JSONObjectData( - bbox=[bbox_json], - cuboid=[cuboid_json], - vec=[seg3d_json], + object_data=JSONAnnotations( + bbox=[bbox_json], + cuboid=[cuboid_json], + vec=[seg3d_json], + ) ), }, ) From 07428e12633de36d397d361c5b53d64b77d0a689 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 06:49:10 +0100 Subject: [PATCH 145/190] feat: implement Frame.to_json() --- raillabel/format/_util.py | 8 ++ raillabel/format/frame.py | 87 +++++++++++++++++++++- raillabel/format/num.py | 10 ++- tests/test_raillabel/format/conftest.py | 1 + tests/test_raillabel/format/test_frame.py | 7 +- tests/test_raillabel/format/test_num.py | 7 +- tests/test_raillabel/format/test_object.py | 8 ++ tests/test_raillabel/format/test_poly2d.py | 8 +- tests/test_raillabel/format/test_poly3d.py | 8 +- 9 files changed, 127 insertions(+), 17 deletions(-) diff --git a/raillabel/format/_util.py b/raillabel/format/_util.py index dfb567e..02f5e57 100644 --- a/raillabel/format/_util.py +++ b/raillabel/format/_util.py @@ -4,5 +4,13 @@ from __future__ import annotations +def _empty_list_to_none(collection: list | None) -> list | None: + if collection is None: + return None + if len(collection) == 0: + return None + return collection + + def _flatten_list(list_of_tuples: list[tuple]) -> list: return [item for tup in list_of_tuples for item in tup] diff --git a/raillabel/format/frame.py b/raillabel/format/frame.py index 686715e..91fb7c3 100644 --- a/raillabel/format/frame.py +++ b/raillabel/format/frame.py @@ -7,11 +7,19 @@ from decimal import Decimal from uuid import UUID -from raillabel.json_format import JSONFrame, JSONFrameProperties, JSONObjectData - +from raillabel.json_format import ( + JSONAnnotations, + JSONFrame, + JSONFrameData, + JSONFrameProperties, + JSONObjectData, +) + +from ._util import _empty_list_to_none from .bbox import Bbox from .cuboid import Cuboid from .num import Num +from .object import Object from .poly2d import Poly2d from .poly3d import Poly3d from .seg3d import Seg3d @@ -45,6 +53,19 @@ def from_json(cls, json: JSONFrame) -> Frame: annotations=_annotations_from_json(json.objects), ) + def to_json(self, objects: dict[UUID, Object]) -> JSONFrame: + """Export this object into the RailLabel JSON format.""" + return JSONFrame( + frame_properties=JSONFrameProperties( + timestamp=self.timestamp, + streams={ + sensor_id: sensor_ref.to_json() for sensor_id, sensor_ref in self.sensors.items() + }, + frame_data=JSONFrameData(num=[num.to_json() for num in self.frame_data.values()]), + ), + objects=_objects_to_json(self.annotations, objects), + ) + def _timestamp_from_dict(frame_properties: JSONFrameProperties | None) -> Decimal | None: if frame_properties is None: @@ -113,3 +134,65 @@ def _resolve_none_to_empty_list(optional_list: list | None) -> list: if optional_list is None: return [] return optional_list + + +def _objects_to_json( + annotations: dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d], objects: dict[UUID, Object] +) -> dict[str, JSONObjectData] | None: + if len(annotations) == 0: + return None + + object_data = {} + + for ann_id, annotation in annotations.items(): + object_id = str(annotation.object_id) + + if object_id not in object_data: + object_data[object_id] = JSONObjectData( + object_data=JSONAnnotations( + bbox=[], + cuboid=[], + poly2d=[], + poly3d=[], + vec=[], + ) + ) + + json_annotation = annotation.to_json(ann_id, objects[UUID(object_id)].type) + + if isinstance(annotation, Bbox): + object_data[object_id].object_data.bbox.append(json_annotation) # type: ignore + + elif isinstance(annotation, Cuboid): + object_data[object_id].object_data.cuboid.append(json_annotation) # type: ignore + + elif isinstance(annotation, Poly2d): + object_data[object_id].object_data.poly2d.append(json_annotation) # type: ignore + + elif isinstance(annotation, Poly3d): + object_data[object_id].object_data.poly3d.append(json_annotation) # type: ignore + + elif isinstance(annotation, Seg3d): + object_data[object_id].object_data.vec.append(json_annotation) # type: ignore + + else: + raise TypeError + + for object_id in object_data: + object_data[object_id].object_data.bbox = _empty_list_to_none( + object_data[object_id].object_data.bbox + ) + object_data[object_id].object_data.cuboid = _empty_list_to_none( + object_data[object_id].object_data.cuboid + ) + object_data[object_id].object_data.poly2d = _empty_list_to_none( + object_data[object_id].object_data.poly2d + ) + object_data[object_id].object_data.poly3d = _empty_list_to_none( + object_data[object_id].object_data.poly3d + ) + object_data[object_id].object_data.vec = _empty_list_to_none( + object_data[object_id].object_data.vec + ) + + return object_data diff --git a/raillabel/format/num.py b/raillabel/format/num.py index acd2d62..e77d1c0 100644 --- a/raillabel/format/num.py +++ b/raillabel/format/num.py @@ -19,7 +19,10 @@ class Num: val: float "This is the value of the number object." - sensor_id: str | None + id: UUID | None = None + "The unique identifyer of the Num." + + sensor_id: str | None = None "A reference to the sensor, this value is represented in." @classmethod @@ -28,14 +31,15 @@ def from_json(cls, json: JSONNum) -> Num: return Num( name=json.name, val=json.val, + id=json.uid, sensor_id=json.coordinate_system, ) - def to_json(self, uid: UUID) -> JSONNum: + def to_json(self) -> JSONNum: """Export this object into the RailLabel JSON format.""" return JSONNum( name=self.name, val=self.val, coordinate_system=self.sensor_id, - uid=uid, + uid=self.id, ) diff --git a/tests/test_raillabel/format/conftest.py b/tests/test_raillabel/format/conftest.py index 8ba0856..4f75bde 100644 --- a/tests/test_raillabel/format/conftest.py +++ b/tests/test_raillabel/format/conftest.py @@ -12,6 +12,7 @@ from .test_metadata import metadata, metadata_json from .test_num import num, num_json, num_id from .test_object import ( + objects, object_person, object_person_json, object_person_id, diff --git a/tests/test_raillabel/format/test_frame.py b/tests/test_raillabel/format/test_frame.py index 07cd720..1b85ba6 100644 --- a/tests/test_raillabel/format/test_frame.py +++ b/tests/test_raillabel/format/test_frame.py @@ -98,5 +98,10 @@ def test_from_json(frame, frame_json): assert actual == frame +def test_to_json(frame, frame_json, objects): + actual = frame.to_json(objects) + assert actual == frame_json + + if __name__ == "__main__": - pytest.main([__file__, "-v"]) + pytest.main([__file__, "-vv"]) diff --git a/tests/test_raillabel/format/test_num.py b/tests/test_raillabel/format/test_num.py index 092c700..2779c86 100644 --- a/tests/test_raillabel/format/test_num.py +++ b/tests/test_raillabel/format/test_num.py @@ -29,11 +29,12 @@ def num_id() -> UUID: @pytest.fixture -def num() -> Num: +def num(num_id) -> Num: return Num( sensor_id="gps_imu", name="velocity", val=49.21321, + id=num_id, ) @@ -45,8 +46,8 @@ def test_from_json(num, num_json): assert actual == num -def test_to_json(num, num_json, num_id): - actual = num.to_json(num_id) +def test_to_json(num, num_json): + actual = num.to_json() assert actual == num_json diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index ae6b0da..9931826 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -13,6 +13,14 @@ # == Fixtures ========================= +@pytest.fixture +def objects(object_person, object_person_id, object_track, object_track_id) -> dict[UUID, Object]: + return { + object_person_id: object_person, + object_track_id: object_track, + } + + @pytest.fixture def object_person_json() -> JSONObject: return JSONObject( diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/test_raillabel/format/test_poly2d.py index 11da96a..c193197 100644 --- a/tests/test_raillabel/format/test_poly2d.py +++ b/tests/test_raillabel/format/test_poly2d.py @@ -21,7 +21,7 @@ def poly2d_json( ) -> JSONPoly2d: return JSONPoly2d( uid="013e7b34-62E5-435c-9412-87318c50f6d8", - name="rgb_middle__poly2d__person", + name="rgb_middle__poly2d__track", closed=True, mode="MODE_POLY2D_ABSOLUTE", val=point2d_json + another_point2d_json, @@ -60,12 +60,12 @@ def test_from_json(poly2d, poly2d_json, object_track_id): def test_name(poly2d): - actual = poly2d.name("person") - assert actual == "rgb_middle__poly2d__person" + actual = poly2d.name("track") + assert actual == "rgb_middle__poly2d__track" def test_to_json(poly2d, poly2d_json, poly2d_id): - actual = poly2d.to_json(poly2d_id, object_type="person") + actual = poly2d.to_json(poly2d_id, object_type="track") assert actual == poly2d_json diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/test_raillabel/format/test_poly3d.py index 66288f5..e62d77e 100644 --- a/tests/test_raillabel/format/test_poly3d.py +++ b/tests/test_raillabel/format/test_poly3d.py @@ -21,7 +21,7 @@ def poly3d_json( ) -> JSONPoly3d: return JSONPoly3d( uid="0da87210-46F1-40e5-b661-20ea1c392f50", - name="lidar__poly3d__person", + name="lidar__poly3d__track", closed=True, val=point3d_json + another_point3d_json, coordinate_system="lidar", @@ -59,12 +59,12 @@ def test_from_json(poly3d, poly3d_json, object_track_id): def test_name(poly3d): - actual = poly3d.name("person") - assert actual == "lidar__poly3d__person" + actual = poly3d.name("track") + assert actual == "lidar__poly3d__track" def test_to_json(poly3d, poly3d_json, poly3d_id): - actual = poly3d.to_json(poly3d_id, object_type="person") + actual = poly3d.to_json(poly3d_id, object_type="track") assert actual == poly3d_json From 7d8c225d570cbc884ee776b751411badd316412a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 06:59:34 +0100 Subject: [PATCH 146/190] feat: implement Camera.to_json() --- raillabel/format/camera.py | 25 ++++++++++++++++++- raillabel/format/intrinsics_pinhole.py | 9 +++++++ tests/test_raillabel/format/test_camera.py | 9 +++++-- .../format/test_intrinsics_pinhole.py | 5 ++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/raillabel/format/camera.py b/raillabel/format/camera.py index 3b67a45..b692afe 100644 --- a/raillabel/format/camera.py +++ b/raillabel/format/camera.py @@ -5,7 +5,12 @@ from dataclasses import dataclass -from raillabel.json_format import JSONCoordinateSystem, JSONStreamCamera, JSONTransformData +from raillabel.json_format import ( + JSONCoordinateSystem, + JSONStreamCamera, + JSONStreamCameraProperties, + JSONTransformData, +) from .intrinsics_pinhole import IntrinsicsPinhole from .transform import Transform @@ -39,6 +44,24 @@ def from_json( description=json_stream.description, ) + def to_json(self) -> tuple[JSONStreamCamera, JSONCoordinateSystem]: + """Export this object into the RailLabel JSON format.""" + return ( + JSONStreamCamera( + type="camera", + stream_properties=JSONStreamCameraProperties( + intrinsics_pinhole=self.intrinsics.to_json() + ), + uri=self.uri, + description=self.description, + ), + JSONCoordinateSystem( + parent="base", + type="sensor", + pose_wrt_parent=self.extrinsics.to_json() if self.extrinsics is not None else None, + ), + ) + def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: if json_transform is None: diff --git a/raillabel/format/intrinsics_pinhole.py b/raillabel/format/intrinsics_pinhole.py index 357bd4e..d0b8165 100644 --- a/raillabel/format/intrinsics_pinhole.py +++ b/raillabel/format/intrinsics_pinhole.py @@ -39,3 +39,12 @@ def from_json(cls, json: JSONIntrinsicsPinhole) -> IntrinsicsPinhole: width_px=json.width_px, height_px=json.height_px, ) + + def to_json(self) -> JSONIntrinsicsPinhole: + """Export this object into the RailLabel JSON format.""" + return JSONIntrinsicsPinhole( + camera_matrix=self.camera_matrix, + distortion_coeffs=self.distortion, + width_px=self.width_px, + height_px=self.height_px, + ) diff --git a/tests/test_raillabel/format/test_camera.py b/tests/test_raillabel/format/test_camera.py index 8c9cf18..969f4a2 100644 --- a/tests/test_raillabel/format/test_camera.py +++ b/tests/test_raillabel/format/test_camera.py @@ -28,7 +28,7 @@ def camera_json( description="A very nice camera", ), JSONCoordinateSystem( - parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + parent="base", type="sensor", pose_wrt_parent=transform_json, children=None ), ) @@ -51,5 +51,10 @@ def test_from_json(camera, camera_json): assert actual == camera +def test_to_json(camera, camera_json): + actual = camera.to_json() + assert actual == camera_json + + if __name__ == "__main__": - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-vv"]) diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/test_raillabel/format/test_intrinsics_pinhole.py index 852ce2b..521a6e6 100644 --- a/tests/test_raillabel/format/test_intrinsics_pinhole.py +++ b/tests/test_raillabel/format/test_intrinsics_pinhole.py @@ -39,5 +39,10 @@ def test_from_json(intrinsics_pinhole, intrinsics_pinhole_json): assert actual == intrinsics_pinhole +def test_to_json(intrinsics_pinhole, intrinsics_pinhole_json): + actual = intrinsics_pinhole.to_json() + assert actual == intrinsics_pinhole_json + + if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 3807d93a707ba8b8d140f484ab53346cc77fe86c Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 07:02:36 +0100 Subject: [PATCH 147/190] feat: implement Radar.to_json() --- raillabel/format/intrinsics_radar.py | 8 ++++++ raillabel/format/radar.py | 25 ++++++++++++++++++- .../format/test_intrinsics_radar.py | 5 ++++ tests/test_raillabel/format/test_radar.py | 9 +++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/raillabel/format/intrinsics_radar.py b/raillabel/format/intrinsics_radar.py index 04ba839..579d6ae 100644 --- a/raillabel/format/intrinsics_radar.py +++ b/raillabel/format/intrinsics_radar.py @@ -30,3 +30,11 @@ def from_json(cls, json: JSONIntrinsicsRadar) -> IntrinsicsRadar: width_px=json.width_px, height_px=json.height_px, ) + + def to_json(self) -> JSONIntrinsicsRadar: + """Export this object into the RailLabel JSON format.""" + return JSONIntrinsicsRadar( + resolution_px_per_m=self.resolution_px_per_m, + width_px=self.width_px, + height_px=self.height_px, + ) diff --git a/raillabel/format/radar.py b/raillabel/format/radar.py index 3288c09..702aa32 100644 --- a/raillabel/format/radar.py +++ b/raillabel/format/radar.py @@ -5,7 +5,12 @@ from dataclasses import dataclass -from raillabel.json_format import JSONCoordinateSystem, JSONStreamRadar, JSONTransformData +from raillabel.json_format import ( + JSONCoordinateSystem, + JSONStreamRadar, + JSONStreamRadarProperties, + JSONTransformData, +) from .intrinsics_radar import IntrinsicsRadar from .transform import Transform @@ -39,6 +44,24 @@ def from_json( description=json_stream.description, ) + def to_json(self) -> tuple[JSONStreamRadar, JSONCoordinateSystem]: + """Export this object into the RailLabel JSON format.""" + return ( + JSONStreamRadar( + type="radar", + stream_properties=JSONStreamRadarProperties( + intrinsics_radar=self.intrinsics.to_json() + ), + uri=self.uri, + description=self.description, + ), + JSONCoordinateSystem( + parent="base", + type="sensor", + pose_wrt_parent=self.extrinsics.to_json() if self.extrinsics is not None else None, + ), + ) + def _extrinsics_from_json(json_transform: JSONTransformData | None) -> Transform | None: if json_transform is None: diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/test_raillabel/format/test_intrinsics_radar.py index ed3a8e1..b0043b9 100644 --- a/tests/test_raillabel/format/test_intrinsics_radar.py +++ b/tests/test_raillabel/format/test_intrinsics_radar.py @@ -37,5 +37,10 @@ def test_from_json(intrinsics_radar, intrinsics_radar_json): assert actual == intrinsics_radar +def test_to_json(intrinsics_radar, intrinsics_radar_json): + actual = intrinsics_radar.to_json() + assert actual == intrinsics_radar_json + + if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel/format/test_radar.py b/tests/test_raillabel/format/test_radar.py index 97cef37..5bd8958 100644 --- a/tests/test_raillabel/format/test_radar.py +++ b/tests/test_raillabel/format/test_radar.py @@ -28,7 +28,7 @@ def radar_json( description="A very nice radar", ), JSONCoordinateSystem( - parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + parent="base", type="sensor", pose_wrt_parent=transform_json, children=None ), ) @@ -51,5 +51,10 @@ def test_from_json(radar, radar_json): assert actual == radar +def test_to_json(radar, radar_json): + actual = radar.to_json() + assert actual == radar_json + + if __name__ == "__main__": - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) + pytest.main([__file__, "-vv"]) From e1c61218a2def714fd5376cc1a7db9185ccdf01b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 07:05:59 +0100 Subject: [PATCH 148/190] feat: implement Lidar.to_json() --- raillabel/format/lidar.py | 15 +++++++++++++++ tests/test_raillabel/format/test_lidar.py | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/raillabel/format/lidar.py b/raillabel/format/lidar.py index d1e3dfa..e69e26a 100644 --- a/raillabel/format/lidar.py +++ b/raillabel/format/lidar.py @@ -21,3 +21,18 @@ def from_json( uri=json_stream.uri, description=json_stream.description, ) + + def to_json(self) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + """Export this object into the RailLabel JSON format.""" + return ( + JSONStreamOther( + type="lidar", + uri=self.uri, + description=self.description, + ), + JSONCoordinateSystem( + parent="base", + type="sensor", + pose_wrt_parent=self.extrinsics.to_json() if self.extrinsics is not None else None, + ), + ) diff --git a/tests/test_raillabel/format/test_lidar.py b/tests/test_raillabel/format/test_lidar.py index 6fce3a8..a4d151d 100644 --- a/tests/test_raillabel/format/test_lidar.py +++ b/tests/test_raillabel/format/test_lidar.py @@ -20,7 +20,7 @@ def lidar_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem]: description="A very nice lidar", ), JSONCoordinateSystem( - parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + parent="base", type="sensor", pose_wrt_parent=transform_json, children=None ), ) @@ -42,5 +42,10 @@ def test_from_json(lidar, lidar_json): assert actual == lidar +def test_to_json(lidar, lidar_json): + actual = lidar.to_json() + assert actual == lidar_json + + if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 0060945db20379b33070418114417994d679f6dc Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 07:06:46 +0100 Subject: [PATCH 149/190] feat: implement GpsImu.to_json() --- raillabel/format/gps_imu.py | 15 +++++++++++++++ tests/test_raillabel/format/test_gps_imu.py | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/raillabel/format/gps_imu.py b/raillabel/format/gps_imu.py index 7ecf88a..ad3610d 100644 --- a/raillabel/format/gps_imu.py +++ b/raillabel/format/gps_imu.py @@ -21,3 +21,18 @@ def from_json( uri=json_stream.uri, description=json_stream.description, ) + + def to_json(self) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + """Export this object into the RailLabel JSON format.""" + return ( + JSONStreamOther( + type="gps_imu", + uri=self.uri, + description=self.description, + ), + JSONCoordinateSystem( + parent="base", + type="sensor", + pose_wrt_parent=self.extrinsics.to_json() if self.extrinsics is not None else None, + ), + ) diff --git a/tests/test_raillabel/format/test_gps_imu.py b/tests/test_raillabel/format/test_gps_imu.py index e918878..6bc33e1 100644 --- a/tests/test_raillabel/format/test_gps_imu.py +++ b/tests/test_raillabel/format/test_gps_imu.py @@ -20,7 +20,7 @@ def gps_imu_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem] description="A very nice gps_imu", ), JSONCoordinateSystem( - parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + parent="base", type="sensor", pose_wrt_parent=transform_json, children=None ), ) @@ -42,5 +42,10 @@ def test_from_json(gps_imu, gps_imu_json): assert actual == gps_imu +def test_to_json(gps_imu, gps_imu_json): + actual = gps_imu.to_json() + assert actual == gps_imu_json + + if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 43d91f935855739fcb06b2751df76de88e698aba Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 07:07:51 +0100 Subject: [PATCH 150/190] feat: implement OtherSensor.to_json() --- raillabel/format/other_sensor.py | 15 +++++++++++++++ tests/test_raillabel/format/test_other_sensor.py | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/raillabel/format/other_sensor.py b/raillabel/format/other_sensor.py index 5736ab7..5a1b353 100644 --- a/raillabel/format/other_sensor.py +++ b/raillabel/format/other_sensor.py @@ -21,3 +21,18 @@ def from_json( uri=json_stream.uri, description=json_stream.description, ) + + def to_json(self) -> tuple[JSONStreamOther, JSONCoordinateSystem]: + """Export this object into the RailLabel JSON format.""" + return ( + JSONStreamOther( + type="other", + uri=self.uri, + description=self.description, + ), + JSONCoordinateSystem( + parent="base", + type="sensor", + pose_wrt_parent=self.extrinsics.to_json() if self.extrinsics is not None else None, + ), + ) diff --git a/tests/test_raillabel/format/test_other_sensor.py b/tests/test_raillabel/format/test_other_sensor.py index 25e30b5..2cb693a 100644 --- a/tests/test_raillabel/format/test_other_sensor.py +++ b/tests/test_raillabel/format/test_other_sensor.py @@ -20,7 +20,7 @@ def other_json(transform_json) -> tuple[JSONStreamOther, JSONCoordinateSystem]: description="A very nice generic sensor", ), JSONCoordinateSystem( - parent="base", type="sensor", pose_wrt_parent=transform_json, children=[] + parent="base", type="sensor", pose_wrt_parent=transform_json, children=None ), ) @@ -42,5 +42,10 @@ def test_from_json(other, other_json): assert actual == other +def test_to_json(other, other_json): + actual = other.to_json() + assert actual == other_json + + if __name__ == "__main__": pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) From 69e08a80fb824eecc4a8434e8d0b5776b184166a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 09:22:24 +0100 Subject: [PATCH 151/190] feat: implement Object.to_json() --- raillabel/format/object.py | 78 +++++++++++++++++++++- tests/test_raillabel/format/test_object.py | 45 ++++++++++++- 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/raillabel/format/object.py b/raillabel/format/object.py index a0b9989..6a3eaa3 100644 --- a/raillabel/format/object.py +++ b/raillabel/format/object.py @@ -4,8 +4,16 @@ from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING +from uuid import UUID -from raillabel.json_format import JSONObject +from raillabel.json_format import JSONElementDataPointer, JSONFrameInterval, JSONObject + +from ._attributes import _attributes_to_json +from .frame_interval import FrameInterval + +if TYPE_CHECKING: + from .frame import Frame @dataclass @@ -26,3 +34,71 @@ def from_json(cls, json: JSONObject) -> Object: name=json.name, type=json.type, ) + + def to_json(self, object_id: UUID, frames: dict[int, Frame]) -> JSONObject: + """Export this object into the RailLabel JSON format.""" + return JSONObject( + name=self.name, + type=self.type, + frame_intervals=_frame_intervals_to_json(object_id, frames), + object_data_pointers=_object_data_pointers_to_json(object_id, self.type, frames), + ) + + +def _frame_intervals_to_json(object_id: UUID, frames: dict[int, Frame]) -> list[JSONFrameInterval]: + frames_with_this_object = set() + + for frame_id, frame in frames.items(): + for annotation in frame.annotations.values(): + if annotation.object_id == object_id: + frames_with_this_object.add(frame_id) + continue + + return [fi.to_json() for fi in FrameInterval.from_frame_ids(list(frames_with_this_object))] + + +def _object_data_pointers_to_json( + object_id: UUID, object_type: str, frames: dict[int, Frame] +) -> dict[str, JSONElementDataPointer]: + pointers_raw = {} + + for frame_id, frame in frames.items(): + for annotation in [ann for ann in frame.annotations.values() if ann.object_id == object_id]: + annotation_name = annotation.name(object_type) + if annotation_name not in pointers_raw: + pointers_raw[annotation_name] = { + "frame_intervals": set(), + "type": annotation_name.split("__")[1], + "attribute_pointers": {}, + } + + pointers_raw[annotation_name]["frame_intervals"].add(frame_id) # type: ignore + json_attributes = _attributes_to_json(annotation.attributes) + + if json_attributes is None: + continue + + for attribute in json_attributes.boolean: # type: ignore + pointers_raw[annotation_name]["attribute_pointers"][attribute.name] = "boolean" # type: ignore + + for attribute in json_attributes.num: # type: ignore + pointers_raw[annotation_name]["attribute_pointers"][attribute.name] = "num" # type: ignore + + for attribute in json_attributes.text: # type: ignore + pointers_raw[annotation_name]["attribute_pointers"][attribute.name] = "text" # type: ignore + + for attribute in json_attributes.vec: # type: ignore + pointers_raw[annotation_name]["attribute_pointers"][attribute.name] = "vec" # type: ignore + + object_data_pointers = {} + for annotation_name, object_data_pointer in pointers_raw.items(): + object_data_pointers[annotation_name] = JSONElementDataPointer( + type=object_data_pointer["type"], + frame_intervals=[ + fi.to_json() + for fi in FrameInterval.from_frame_ids(list(object_data_pointer["frame_intervals"])) # type: ignore + ], + attribute_pointers=object_data_pointer["attribute_pointers"], + ) + + return object_data_pointers diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 9931826..98410b1 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -7,7 +7,7 @@ import pytest -from raillabel.json_format import JSONObject +from raillabel.json_format import JSONObject, JSONFrameInterval, JSONElementDataPointer from raillabel.format import Object # == Fixtures ========================= @@ -26,6 +26,42 @@ def object_person_json() -> JSONObject: return JSONObject( name="person_0032", type="person", + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + object_data_pointers={ + "rgb_middle__bbox__person": JSONElementDataPointer( + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + type="bbox", + attribute_pointers={ + "has_red_hat": "boolean", + "has_green_hat": "boolean", + "number_of_red_clothing_items": "num", + "color_of_hat": "text", + "clothing_items": "vec", + }, + ), + "lidar__cuboid__person": JSONElementDataPointer( + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + type="cuboid", + attribute_pointers={ + "has_red_hat": "boolean", + "has_green_hat": "boolean", + "number_of_red_clothing_items": "num", + "color_of_hat": "text", + "clothing_items": "vec", + }, + ), + "lidar__vec__person": JSONElementDataPointer( + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + type="vec", + attribute_pointers={ + "has_red_hat": "boolean", + "has_green_hat": "boolean", + "number_of_red_clothing_items": "num", + "color_of_hat": "text", + "clothing_items": "vec", + }, + ), + }, ) @@ -76,5 +112,10 @@ def test_from_json__track(object_track, object_track_json): assert actual == object_track +def test_to_json__person(object_person, object_person_json, object_person_id, frame): + actual = object_person.to_json(object_person_id, {1: frame}) + assert actual == object_person_json + + if __name__ == "__main__": - pytest.main([__file__, "-v"]) + pytest.main([__file__, "-vv"]) From e4047393e71f9b374dcb95ffc7847400de3f5ab4 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 09:41:56 +0100 Subject: [PATCH 152/190] feat: implement Scene.to_json() --- raillabel/format/metadata.py | 13 +++++++ raillabel/format/scene.py | 38 ++++++++++++++++++++ tests/test_raillabel/format/test_metadata.py | 5 +++ tests/test_raillabel/format/test_object.py | 30 ++++++++++++++++ tests/test_raillabel/format/test_scene.py | 13 ++++++- 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index 24a23c7..b8ae018 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -55,3 +55,16 @@ def from_json(cls, json: JSONMetadata) -> Metadata: setattr(metadata, extra_field, extra_value) return metadata + + def to_json(self) -> JSONMetadata: + """Export this object into the RailLabel JSON format.""" + return JSONMetadata( + schema_version=self.schema_version, + name=self.name, + subschema_version=self.subschema_version, + exporter_version=self.exporter_version, + file_version=self.file_version, + tagged_file=self.tagged_file, + annotator=self.annotator, + comment=self.comment, + ) diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index c88f4a5..9220794 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -11,6 +11,7 @@ JSONFrame, JSONObject, JSONScene, + JSONSceneContent, JSONStreamCamera, JSONStreamOther, JSONStreamRadar, @@ -18,6 +19,7 @@ from .camera import Camera from .frame import Frame +from .frame_interval import FrameInterval from .gps_imu import GpsImu from .lidar import Lidar from .metadata import Metadata @@ -52,6 +54,27 @@ def from_json(cls, json: JSONScene) -> Scene: frames=_frames_from_json(json.openlabel.frames), ) + def to_json(self) -> JSONScene: + """Export this scene into the RailLabel JSON format.""" + return JSONScene( + openlabel=JSONSceneContent( + metadata=self.metadata.to_json(), + streams={ + sensor_id: sensor.to_json()[0] for sensor_id, sensor in self.sensors.items() + }, + coordinate_systems=_coordinate_systems_to_json(self.sensors), + objects={ + obj_id: obj.to_json(obj_id, self.frames) for obj_id, obj in self.objects.items() + }, + frames={ + frame_id: frame.to_json(self.objects) for frame_id, frame in self.frames.items() + }, + frame_intervals=[ + fi.to_json() for fi in FrameInterval.from_frame_ids(list(self.frames.keys())) + ], + ) + ) + def _sensors_from_json( json_streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None, @@ -95,3 +118,18 @@ def _frames_from_json(json_frames: dict[int, JSONFrame] | None) -> dict[int, Fra return {} return {frame_id: Frame.from_json(json_frame) for frame_id, json_frame in json_frames.items()} + + +def _coordinate_systems_to_json( + sensors: dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor], +) -> dict[str, JSONCoordinateSystem]: + json_coordinate_systems = { + sensor_id: sensor.to_json()[1] for sensor_id, sensor in sensors.items() + } + json_coordinate_systems["base"] = JSONCoordinateSystem( + parent="", + type="local", + pose_wrt_parent=None, + children=list(json_coordinate_systems.keys()), + ) + return json_coordinate_systems diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/test_raillabel/format/test_metadata.py index 6f67c0c..6ba5379 100644 --- a/tests/test_raillabel/format/test_metadata.py +++ b/tests/test_raillabel/format/test_metadata.py @@ -61,5 +61,10 @@ def test_from_json__extra_fields(): assert actual.ADDITIONAL_OBJECT == {"first_field": 2, "second_field": [1, 2, 3]} +def test_to_json(metadata, metadata_json): + actual = metadata.to_json() + assert actual == metadata_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tests/test_raillabel/format/test_object.py b/tests/test_raillabel/format/test_object.py index 98410b1..a7b69a1 100644 --- a/tests/test_raillabel/format/test_object.py +++ b/tests/test_raillabel/format/test_object.py @@ -83,6 +83,31 @@ def object_track_json() -> JSONObject: return JSONObject( name="track_0001", type="track", + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + object_data_pointers={ + "rgb_middle__poly2d__track": JSONElementDataPointer( + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + type="poly2d", + attribute_pointers={ + "has_red_hat": "boolean", + "has_green_hat": "boolean", + "number_of_red_clothing_items": "num", + "color_of_hat": "text", + "clothing_items": "vec", + }, + ), + "lidar__poly3d__track": JSONElementDataPointer( + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], + type="poly3d", + attribute_pointers={ + "has_red_hat": "boolean", + "has_green_hat": "boolean", + "number_of_red_clothing_items": "num", + "color_of_hat": "text", + "clothing_items": "vec", + }, + ), + }, ) @@ -117,5 +142,10 @@ def test_to_json__person(object_person, object_person_json, object_person_id, fr assert actual == object_person_json +def test_to_json__track(object_track, object_track_json, object_track_id, frame): + actual = object_track.to_json(object_track_id, {1: frame}) + assert actual == object_track_json + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) diff --git a/tests/test_raillabel/format/test_scene.py b/tests/test_raillabel/format/test_scene.py index c8a0f63..b7dbe24 100644 --- a/tests/test_raillabel/format/test_scene.py +++ b/tests/test_raillabel/format/test_scene.py @@ -6,7 +6,12 @@ import pytest from raillabel.format import Scene -from raillabel.json_format import JSONScene, JSONSceneContent, JSONCoordinateSystem +from raillabel.json_format import ( + JSONScene, + JSONSceneContent, + JSONCoordinateSystem, + JSONFrameInterval, +) # == Fixtures ========================= @@ -47,6 +52,7 @@ def scene_json( object_track_id: object_track_json, }, frames={1: frame_json}, + frame_intervals=[JSONFrameInterval(frame_start=1, frame_end=1)], ) ) @@ -86,5 +92,10 @@ def test_from_json(scene, scene_json): assert actual == scene +def test_to_json(scene, scene_json): + actual = scene.to_json() + assert actual == scene_json + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 342bf96f3e9c49268075fe5b1198734f09c8483d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 10:02:33 +0100 Subject: [PATCH 153/190] refactor: create parent class for all json format classes --- raillabel/json_format/_json_format_base.py | 8 ++++++++ raillabel/json_format/attributes.py | 5 ++--- raillabel/json_format/bbox.py | 5 ++--- raillabel/json_format/boolean_attribute.py | 4 ++-- raillabel/json_format/coordinate_system.py | 5 ++--- raillabel/json_format/cuboid.py | 5 ++--- raillabel/json_format/element_data_pointer.py | 5 ++--- raillabel/json_format/frame.py | 9 ++++----- raillabel/json_format/frame_interval.py | 4 ++-- raillabel/json_format/num.py | 4 ++-- raillabel/json_format/num_attribute.py | 4 ++-- raillabel/json_format/object.py | 5 ++--- raillabel/json_format/object_data.py | 7 +++---- raillabel/json_format/poly2d.py | 5 ++--- raillabel/json_format/poly3d.py | 5 ++--- raillabel/json_format/scene.py | 7 +++---- raillabel/json_format/stream_camera.py | 8 ++++---- raillabel/json_format/stream_other.py | 4 ++-- raillabel/json_format/stream_radar.py | 8 ++++---- raillabel/json_format/stream_sync.py | 8 ++++---- raillabel/json_format/text_attribute.py | 4 ++-- raillabel/json_format/transform_data.py | 4 ++-- raillabel/json_format/vec.py | 5 ++--- raillabel/json_format/vec_attribute.py | 4 ++-- 24 files changed, 64 insertions(+), 68 deletions(-) create mode 100644 raillabel/json_format/_json_format_base.py diff --git a/raillabel/json_format/_json_format_base.py b/raillabel/json_format/_json_format_base.py new file mode 100644 index 0000000..5095d77 --- /dev/null +++ b/raillabel/json_format/_json_format_base.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from pydantic import BaseModel + + +class _JSONFormatBase(BaseModel, extra="forbid"): + pass diff --git a/raillabel/json_format/attributes.py b/raillabel/json_format/attributes.py index 68aaacb..30846e6 100644 --- a/raillabel/json_format/attributes.py +++ b/raillabel/json_format/attributes.py @@ -3,15 +3,14 @@ from __future__ import annotations -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .boolean_attribute import JSONBooleanAttribute from .num_attribute import JSONNumAttribute from .text_attribute import JSONTextAttribute from .vec_attribute import JSONVecAttribute -class JSONAttributes(BaseModel, extra="forbid"): +class JSONAttributes(_JSONFormatBase): """Attributes is the alias of element data that can be nested inside geometric object data. For example, a certain bounding box can have attributes related to its score, visibility, etc. diff --git a/raillabel/json_format/bbox.py b/raillabel/json_format/bbox.py index 944db2c..7fd9f6a 100644 --- a/raillabel/json_format/bbox.py +++ b/raillabel/json_format/bbox.py @@ -5,12 +5,11 @@ from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .attributes import JSONAttributes -class JSONBbox(BaseModel, extra="forbid"): +class JSONBbox(_JSONFormatBase): """A 2D bounding box is defined as a 4-dimensional vector [x, y, w, h]. [x, y] is the center of the bounding box and [w, h] represent the width (horizontal, diff --git a/raillabel/json_format/boolean_attribute.py b/raillabel/json_format/boolean_attribute.py index 91c86ea..a41ca11 100644 --- a/raillabel/json_format/boolean_attribute.py +++ b/raillabel/json_format/boolean_attribute.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONBooleanAttribute(BaseModel, extra="forbid"): +class JSONBooleanAttribute(_JSONFormatBase): """A boolean attribute.""" name: str diff --git a/raillabel/json_format/coordinate_system.py b/raillabel/json_format/coordinate_system.py index fedc174..899581d 100644 --- a/raillabel/json_format/coordinate_system.py +++ b/raillabel/json_format/coordinate_system.py @@ -5,12 +5,11 @@ from typing import Literal -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .transform_data import JSONTransformData -class JSONCoordinateSystem(BaseModel, extra="forbid"): +class JSONCoordinateSystem(_JSONFormatBase): """A 3D reference frame.""" parent: Literal["base", ""] diff --git a/raillabel/json_format/cuboid.py b/raillabel/json_format/cuboid.py index a58431a..0831add 100644 --- a/raillabel/json_format/cuboid.py +++ b/raillabel/json_format/cuboid.py @@ -5,12 +5,11 @@ from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .attributes import JSONAttributes -class JSONCuboid(BaseModel, extra="forbid"): +class JSONCuboid(_JSONFormatBase): """A cuboid or 3D bounding box. It is defined by the position of its center, the rotation in 3D, and its dimensions. diff --git a/raillabel/json_format/element_data_pointer.py b/raillabel/json_format/element_data_pointer.py index 745c9c0..d0dea1d 100644 --- a/raillabel/json_format/element_data_pointer.py +++ b/raillabel/json_format/element_data_pointer.py @@ -5,12 +5,11 @@ from typing import Literal -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .frame_interval import JSONFrameInterval -class JSONElementDataPointer(BaseModel, extra="forbid"): +class JSONElementDataPointer(_JSONFormatBase): """A pointer to element data of elements. It is indexed by 'name', and containing information about the element data type, for example, diff --git a/raillabel/json_format/frame.py b/raillabel/json_format/frame.py index cfc1386..2ec6334 100644 --- a/raillabel/json_format/frame.py +++ b/raillabel/json_format/frame.py @@ -6,14 +6,13 @@ from decimal import Decimal from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .num import JSONNum from .object_data import JSONObjectData from .stream_sync import JSONStreamSync -class JSONFrame(BaseModel, extra="forbid"): +class JSONFrame(_JSONFormatBase): """A frame is a container of dynamic, timewise, information.""" frame_properties: JSONFrameProperties | None = None @@ -24,7 +23,7 @@ class JSONFrame(BaseModel, extra="forbid"): strings containing 32 bytes UUIDs. Object values contain an 'object_data' JSON object.""" -class JSONFrameProperties(BaseModel, extra="forbid"): +class JSONFrameProperties(_JSONFormatBase): """Container of frame information other than annotations.""" timestamp: Decimal | str | None = None @@ -38,7 +37,7 @@ class JSONFrameProperties(BaseModel, extra="forbid"): "Additional data to describe attributes of the frame (like GPS position)." -class JSONFrameData(BaseModel, extra="forbid"): +class JSONFrameData(_JSONFormatBase): """Additional data to describe attributes of the frame (like GPS position).""" num: list[JSONNum] | None = None diff --git a/raillabel/json_format/frame_interval.py b/raillabel/json_format/frame_interval.py index d7f9034..984065c 100644 --- a/raillabel/json_format/frame_interval.py +++ b/raillabel/json_format/frame_interval.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONFrameInterval(BaseModel, extra="forbid"): +class JSONFrameInterval(_JSONFormatBase): """A frame interval defines a starting and ending frame number as a closed interval. That means the interval includes the limit frame numbers. diff --git a/raillabel/json_format/num.py b/raillabel/json_format/num.py index be9089b..ce67b9f 100644 --- a/raillabel/json_format/num.py +++ b/raillabel/json_format/num.py @@ -5,10 +5,10 @@ from uuid import UUID -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONNum(BaseModel, extra="forbid"): +class JSONNum(_JSONFormatBase): """A number.""" name: str diff --git a/raillabel/json_format/num_attribute.py b/raillabel/json_format/num_attribute.py index e829712..edfdfcd 100644 --- a/raillabel/json_format/num_attribute.py +++ b/raillabel/json_format/num_attribute.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONNumAttribute(BaseModel, extra="forbid"): +class JSONNumAttribute(_JSONFormatBase): """A number attribute.""" name: str diff --git a/raillabel/json_format/object.py b/raillabel/json_format/object.py index 514f1e8..777214e 100644 --- a/raillabel/json_format/object.py +++ b/raillabel/json_format/object.py @@ -3,13 +3,12 @@ from __future__ import annotations -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .element_data_pointer import JSONElementDataPointer from .frame_interval import JSONFrameInterval -class JSONObject(BaseModel, extra="forbid"): +class JSONObject(_JSONFormatBase): """An object is the main type of annotation element. Object is designed to represent spatiotemporal entities, such as physical objects in the real diff --git a/raillabel/json_format/object_data.py b/raillabel/json_format/object_data.py index 04609e7..68d4ba9 100644 --- a/raillabel/json_format/object_data.py +++ b/raillabel/json_format/object_data.py @@ -3,8 +3,7 @@ from __future__ import annotations -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .bbox import JSONBbox from .cuboid import JSONCuboid from .poly2d import JSONPoly2d @@ -12,13 +11,13 @@ from .vec import JSONVec -class JSONObjectData(BaseModel, extra="forbid"): +class JSONObjectData(_JSONFormatBase): """Container of annotations of an object in a frame.""" object_data: JSONAnnotations -class JSONAnnotations(BaseModel, extra="forbid"): +class JSONAnnotations(_JSONFormatBase): """Container of the annotations by type.""" bbox: list[JSONBbox] | None = None diff --git a/raillabel/json_format/poly2d.py b/raillabel/json_format/poly2d.py index 5ccda8c..3a0ddcd 100644 --- a/raillabel/json_format/poly2d.py +++ b/raillabel/json_format/poly2d.py @@ -6,12 +6,11 @@ from typing import Literal from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .attributes import JSONAttributes -class JSONPoly2d(BaseModel, extra="forbid"): +class JSONPoly2d(_JSONFormatBase): """A 2D polyline defined as a sequence of 2D points.""" name: str diff --git a/raillabel/json_format/poly3d.py b/raillabel/json_format/poly3d.py index 714204a..b185c93 100644 --- a/raillabel/json_format/poly3d.py +++ b/raillabel/json_format/poly3d.py @@ -5,12 +5,11 @@ from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .attributes import JSONAttributes -class JSONPoly3d(BaseModel, extra="forbid"): +class JSONPoly3d(_JSONFormatBase): """A 3D polyline defined as a sequence of 3D points.""" name: str diff --git a/raillabel/json_format/scene.py b/raillabel/json_format/scene.py index 36135d6..1a618b0 100644 --- a/raillabel/json_format/scene.py +++ b/raillabel/json_format/scene.py @@ -5,8 +5,7 @@ from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .coordinate_system import JSONCoordinateSystem from .frame import JSONFrame from .frame_interval import JSONFrameInterval @@ -17,13 +16,13 @@ from .stream_radar import JSONStreamRadar -class JSONScene(BaseModel, extra="forbid"): +class JSONScene(_JSONFormatBase): """Root RailLabel object.""" openlabel: JSONSceneContent -class JSONSceneContent(BaseModel, extra="forbid"): +class JSONSceneContent(_JSONFormatBase): """Container of all scene content.""" metadata: JSONMetadata diff --git a/raillabel/json_format/stream_camera.py b/raillabel/json_format/stream_camera.py index 1541c64..fbcd980 100644 --- a/raillabel/json_format/stream_camera.py +++ b/raillabel/json_format/stream_camera.py @@ -5,10 +5,10 @@ from typing import Literal -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONStreamCamera(BaseModel, extra="forbid"): +class JSONStreamCamera(_JSONFormatBase): """A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a camera sensor. @@ -27,13 +27,13 @@ class JSONStreamCamera(BaseModel, extra="forbid"): "Description of the stream." -class JSONStreamCameraProperties(BaseModel, extra="forbid"): +class JSONStreamCameraProperties(_JSONFormatBase): """Intrinsic calibration of the stream.""" intrinsics_pinhole: JSONIntrinsicsPinhole -class JSONIntrinsicsPinhole(BaseModel, extra="forbid"): +class JSONIntrinsicsPinhole(_JSONFormatBase): """JSON object defining an instance of the intrinsic parameters of a pinhole camera.""" camera_matrix: tuple[ diff --git a/raillabel/json_format/stream_other.py b/raillabel/json_format/stream_other.py index 75fd241..27fee1b 100644 --- a/raillabel/json_format/stream_other.py +++ b/raillabel/json_format/stream_other.py @@ -5,10 +5,10 @@ from typing import Literal -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONStreamOther(BaseModel, extra="forbid"): +class JSONStreamOther(_JSONFormatBase): """A stream describes the source of a data sequence, usually a sensor. This specific object describes a sensor without intrinsic calibration. diff --git a/raillabel/json_format/stream_radar.py b/raillabel/json_format/stream_radar.py index 1ac48e3..67dfceb 100644 --- a/raillabel/json_format/stream_radar.py +++ b/raillabel/json_format/stream_radar.py @@ -5,10 +5,10 @@ from typing import Literal -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONStreamRadar(BaseModel, extra="forbid"): +class JSONStreamRadar(_JSONFormatBase): """A stream describes the source of a data sequence, usually a sensor. This specific object contains the intrinsics of a radar sensor. @@ -27,13 +27,13 @@ class JSONStreamRadar(BaseModel, extra="forbid"): "Description of the stream." -class JSONStreamRadarProperties(BaseModel, extra="forbid"): +class JSONStreamRadarProperties(_JSONFormatBase): """Intrinsic calibration of the stream.""" intrinsics_radar: JSONIntrinsicsRadar -class JSONIntrinsicsRadar(BaseModel, extra="forbid"): +class JSONIntrinsicsRadar(_JSONFormatBase): """JSON object defining an instance of the intrinsic parameters of a radar.""" resolution_px_per_m: float diff --git a/raillabel/json_format/stream_sync.py b/raillabel/json_format/stream_sync.py index 5e48974..0c7c95f 100644 --- a/raillabel/json_format/stream_sync.py +++ b/raillabel/json_format/stream_sync.py @@ -5,23 +5,23 @@ from decimal import Decimal -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONStreamSync(BaseModel, extra="forbid"): +class JSONStreamSync(_JSONFormatBase): """Syncronization information of a stream in a frame.""" stream_properties: JSONStreamSyncProperties uri: str | None = None -class JSONStreamSyncProperties(BaseModel, extra="forbid"): +class JSONStreamSyncProperties(_JSONFormatBase): """The sync information.""" sync: JSONStreamSyncTimestamp -class JSONStreamSyncTimestamp(BaseModel, extra="forbid"): +class JSONStreamSyncTimestamp(_JSONFormatBase): """The timestamp of a stream sync.""" timestamp: Decimal | str diff --git a/raillabel/json_format/text_attribute.py b/raillabel/json_format/text_attribute.py index 24cc389..ddccbbc 100644 --- a/raillabel/json_format/text_attribute.py +++ b/raillabel/json_format/text_attribute.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONTextAttribute(BaseModel, extra="forbid"): +class JSONTextAttribute(_JSONFormatBase): """A text attribute.""" name: str diff --git a/raillabel/json_format/transform_data.py b/raillabel/json_format/transform_data.py index 4d5a235..c23addb 100644 --- a/raillabel/json_format/transform_data.py +++ b/raillabel/json_format/transform_data.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONTransformData(BaseModel, extra="forbid"): +class JSONTransformData(_JSONFormatBase): """The translation and rotation of one coordinate system to another.""" translation: tuple[float, float, float] diff --git a/raillabel/json_format/vec.py b/raillabel/json_format/vec.py index d9dec89..1d81162 100644 --- a/raillabel/json_format/vec.py +++ b/raillabel/json_format/vec.py @@ -6,12 +6,11 @@ from typing import Literal from uuid import UUID -from pydantic import BaseModel - +from ._json_format_base import _JSONFormatBase from .attributes import JSONAttributes -class JSONVec(BaseModel, extra="forbid"): +class JSONVec(_JSONFormatBase): """A vector (list) of numbers.""" name: str diff --git a/raillabel/json_format/vec_attribute.py b/raillabel/json_format/vec_attribute.py index c6df6a2..9dc1104 100644 --- a/raillabel/json_format/vec_attribute.py +++ b/raillabel/json_format/vec_attribute.py @@ -3,10 +3,10 @@ from __future__ import annotations -from pydantic import BaseModel +from ._json_format_base import _JSONFormatBase -class JSONVecAttribute(BaseModel, extra="forbid"): +class JSONVecAttribute(_JSONFormatBase): """A vec attribute.""" name: str From 737a97b881fc43f1e5ba0a47ae7d9fd42b82fa5a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 14 Nov 2024 10:08:53 +0100 Subject: [PATCH 154/190] feat: implement raillabel.save() --- raillabel/save/save.py | 23 ++++++++--------------- tests/test_raillabel/save/test_save.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 tests/test_raillabel/save/test_save.py diff --git a/raillabel/save/save.py b/raillabel/save/save.py index 94714a9..dbe62fc 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -8,19 +8,12 @@ from raillabel.format import Scene -def save(_scene: Scene, _path: Path | str, _prettify_json: bool = False) -> None: - """Save a raillabel.Scene in a JSON file. +def save(scene: Scene, path: Path | str, prettify_json: bool = False) -> None: + """Save a raillabel.Scene to a JSON file.""" + if prettify_json: + json_data = scene.to_json().model_dump_json(indent=4) + else: + json_data = scene.to_json().model_dump_json() - Parameters - ---------- - scene: raillabel.Scene - Scene, which should be saved. - path: str - Path to the file location, that should be used for saving. - save_path: str - Path to the JSON file. - prettify_json: bool, optional - If true, the JSON is saved with linebreaks and indents. This increases readibility but - also the file size. Default is False. - - """ + with Path(path).open("w") as scene_file: + scene_file.write(json_data) diff --git a/tests/test_raillabel/save/test_save.py b/tests/test_raillabel/save/test_save.py new file mode 100644 index 0000000..4009a88 --- /dev/null +++ b/tests/test_raillabel/save/test_save.py @@ -0,0 +1,23 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +import raillabel +from raillabel import Scene +from raillabel.json_format import JSONScene + + +def test_save(json_data, tmp_path): + scene_path = tmp_path / "scene.json" + ground_truth_scene = Scene.from_json(JSONScene(**json_data["openlabel_v1_short"])) + raillabel.save(ground_truth_scene, scene_path) + + actual = raillabel.load(scene_path) + assert actual == ground_truth_scene + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From dfed56169774ecb7712773ee8d0d44c686904972 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 15 Nov 2024 08:55:16 +0100 Subject: [PATCH 155/190] feat: implement SceneBuilder.empty() --- raillabel/scene_builder/scene_builder.py | 20 +++++++++++++++++++ .../scene_builder/test_scene_builder.py | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 raillabel/scene_builder/scene_builder.py create mode 100644 tests/test_raillabel/scene_builder/test_scene_builder.py diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py new file mode 100644 index 0000000..e954b10 --- /dev/null +++ b/raillabel/scene_builder/scene_builder.py @@ -0,0 +1,20 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.format import Metadata, Scene + + +@dataclass +class SceneBuilder: + """Use this class for easily creating scenes for tests.""" + + scene: Scene + + @classmethod + def empty(cls) -> SceneBuilder: + """Construct the SceneBuilder with an empty scene.""" + return SceneBuilder(Scene(metadata=Metadata(schema_version="1.0.0"))) diff --git a/tests/test_raillabel/scene_builder/test_scene_builder.py b/tests/test_raillabel/scene_builder/test_scene_builder.py new file mode 100644 index 0000000..c9e1452 --- /dev/null +++ b/tests/test_raillabel/scene_builder/test_scene_builder.py @@ -0,0 +1,19 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +import raillabel +from raillabel.format import Scene, Metadata +from raillabel.scene_builder.scene_builder import SceneBuilder + + +def test_empty(): + actual = SceneBuilder.empty() + assert actual.scene == Scene(Metadata(schema_version="1.0.0")) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 826f8640660659203ff8eafa4764a7344a49642c Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 15 Nov 2024 09:34:21 +0100 Subject: [PATCH 156/190] feat: implement SceneBuilder.add_object() --- raillabel/scene_builder/scene_builder.py | 54 ++++++++++++- .../scene_builder/test_scene_builder.py | 80 ++++++++++++++++++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index e954b10..beb083f 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -3,18 +3,68 @@ from __future__ import annotations +from copy import deepcopy from dataclasses import dataclass +from uuid import UUID -from raillabel.format import Metadata, Scene +from raillabel.format import Metadata, Object, Scene @dataclass class SceneBuilder: """Use this class for easily creating scenes for tests.""" - scene: Scene + result: Scene @classmethod def empty(cls) -> SceneBuilder: """Construct the SceneBuilder with an empty scene.""" return SceneBuilder(Scene(metadata=Metadata(schema_version="1.0.0"))) + + def add_object( + self, + object_id: str | UUID | None = None, + object_type: str | None = None, + object_name: str | None = None, + ) -> SceneBuilder: + """Add an object to a scene.""" + scene = deepcopy(self.result) + + object_type, object_name = _resolve_empty_object_name_or_type(object_type, object_name) + object_id = _resolve_empty_object_uid(scene, object_id) + + scene.objects[object_id] = Object(object_name, object_type) + return SceneBuilder(scene) + + +def _resolve_empty_object_name_or_type( + object_type: str | None, object_name: str | None +) -> tuple[str, str]: + if object_name is None and object_type is None: + object_type = "person" + object_name = object_type + "_0000" + return object_type, object_name + + if object_name is None and object_type is not None: + object_name = object_type + "_0000" + return object_type, object_name + + if object_name is not None and object_type is None: + object_type = object_name.split("_")[0] + return object_type, object_name + + return object_type, object_name # type: ignore + + +def _resolve_empty_object_uid(scene: Scene, object_id: str | UUID | None) -> UUID: + if object_id is None: + uid_index = 0 + while _generate_deterministic_uuid(uid_index, "5c59aad4") in scene.objects: + uid_index += 1 + object_id = _generate_deterministic_uuid(uid_index, "5c59aad4") + + return UUID(str(object_id)) + + +def _generate_deterministic_uuid(index: int, prefix: str) -> UUID: + return UUID(f"{prefix.zfill(0)}-0000-4000-0000-{str(index).zfill(12)}") diff --git a/tests/test_raillabel/scene_builder/test_scene_builder.py b/tests/test_raillabel/scene_builder/test_scene_builder.py index c9e1452..3923ea1 100644 --- a/tests/test_raillabel/scene_builder/test_scene_builder.py +++ b/tests/test_raillabel/scene_builder/test_scene_builder.py @@ -2,17 +2,93 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from uuid import UUID import pytest import raillabel -from raillabel.format import Scene, Metadata +from raillabel.format import Scene, Metadata, Object from raillabel.scene_builder.scene_builder import SceneBuilder def test_empty(): actual = SceneBuilder.empty() - assert actual.scene == Scene(Metadata(schema_version="1.0.0")) + assert actual.result == Scene(Metadata(schema_version="1.0.0")) + + +def test_add_object__all_options(): + actual = SceneBuilder.empty().add_object( + object_id="5c59aad4-9fcd-4903-a9fa-72b1b76c23a5", + object_type="train", + object_name="train_0001", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-9fcd-4903-a9fa-72b1b76c23a5"): Object(name="train_0001", type="train") + }, + ) + + +def test_add_object__no_object_name(): + actual = SceneBuilder.empty().add_object( + object_id="5c59aad4-9fcd-4903-a9fa-72b1b76c23a5", + object_type="train", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-9fcd-4903-a9fa-72b1b76c23a5"): Object(name="train_0000", type="train") + }, + ) + + +def test_add_object__no_object_type(): + actual = SceneBuilder.empty().add_object( + object_id="5c59aad4-9fcd-4903-a9fa-72b1b76c23a5", + object_name="train_0000", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-9fcd-4903-a9fa-72b1b76c23a5"): Object(name="train_0000", type="train") + }, + ) + + +def test_add_object__no_object_type_or_name(): + actual = SceneBuilder.empty().add_object( + object_id="5c59aad4-9fcd-4903-a9fa-72b1b76c23a5", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-9fcd-4903-a9fa-72b1b76c23a5"): Object(name="person_0000", type="person") + }, + ) + + +def test_add_object__no_object_id(): + actual = SceneBuilder.empty().add_object(object_type="train", object_name="train_0001") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="train_0001", type="train") + }, + ) + + +def test_add_object__object_id_iteration(): + actual = SceneBuilder.empty().add_object().add_object().add_object().add_object() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0000", type="person"), + UUID("5c59aad4-0000-4000-0000-000000000001"): Object(name="person_0000", type="person"), + UUID("5c59aad4-0000-4000-0000-000000000002"): Object(name="person_0000", type="person"), + UUID("5c59aad4-0000-4000-0000-000000000003"): Object(name="person_0000", type="person"), + }, + ) if __name__ == "__main__": From 45eef2976e8a6d8faf3f6dd508381b7ac5bcd42b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 15 Nov 2024 09:36:58 +0100 Subject: [PATCH 157/190] refactor: remove type ignore --- raillabel/scene_builder/scene_builder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index beb083f..9711746 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -53,7 +53,10 @@ def _resolve_empty_object_name_or_type( object_type = object_name.split("_")[0] return object_type, object_name - return object_type, object_name # type: ignore + if object_name is not None and object_type is not None: + return object_type, object_name + + raise RuntimeError def _resolve_empty_object_uid(scene: Scene, object_id: str | UUID | None) -> UUID: From ab021901cecd5606187276c0a5536f4f56786a05 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 15 Nov 2024 18:15:52 +0100 Subject: [PATCH 158/190] feat: implement SceneBuilder.add_sensor() --- raillabel/scene_builder/scene_builder.py | 55 +++++++++++- .../scene_builder/test_scene_builder.py | 86 ++++++++++++++++++- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 9711746..d398a0c 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -7,7 +7,18 @@ from dataclasses import dataclass from uuid import UUID -from raillabel.format import Metadata, Object, Scene +from raillabel.format import ( + Camera, + GpsImu, + IntrinsicsPinhole, + IntrinsicsRadar, + Lidar, + Metadata, + Object, + OtherSensor, + Radar, + Scene, +) @dataclass @@ -27,7 +38,7 @@ def add_object( object_type: str | None = None, object_name: str | None = None, ) -> SceneBuilder: - """Add an object to a scene.""" + """Add an object to the scene.""" scene = deepcopy(self.result) object_type, object_name = _resolve_empty_object_name_or_type(object_type, object_name) @@ -36,6 +47,43 @@ def add_object( scene.objects[object_id] = Object(object_name, object_type) return SceneBuilder(scene) + def add_sensor(self, sensor_id: str) -> SceneBuilder: + """Add a sensor to the scene. + + The sensor type is implicitly determined by the sensor_id. Id's, that start with 'rgb_' or + 'ir_' are added as a camera. If the id is 'lidar', it is a lidar. 'radar' creates a Radar. + 'gps_imu' creates a GpsImu. If the id does not match any of these, a OtherSensor is added. + """ + scene = deepcopy(self.result) + + truncated_sensor_id = sensor_id.split("_")[0].lower() + + if truncated_sensor_id in ["rgb", "ir"]: + scene.sensors[sensor_id] = Camera( + intrinsics=IntrinsicsPinhole( + camera_matrix=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + distortion=(0, 0, 0, 0, 0), + width_px=0, + height_px=0, + ) + ) + + elif truncated_sensor_id == "radar": + scene.sensors[sensor_id] = Radar( + intrinsics=IntrinsicsRadar(resolution_px_per_m=0, width_px=0, height_px=0) + ) + + elif truncated_sensor_id == "lidar": + scene.sensors[sensor_id] = Lidar() + + elif truncated_sensor_id == "gps": + scene.sensors[sensor_id] = GpsImu() + + else: + scene.sensors[sensor_id] = OtherSensor() + + return SceneBuilder(scene) + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None @@ -56,7 +104,7 @@ def _resolve_empty_object_name_or_type( if object_name is not None and object_type is not None: return object_type, object_name - raise RuntimeError + raise RuntimeError # this part is unreachable but this is the only way that mypy is happy def _resolve_empty_object_uid(scene: Scene, object_id: str | UUID | None) -> UUID: @@ -64,6 +112,7 @@ def _resolve_empty_object_uid(scene: Scene, object_id: str | UUID | None) -> UUI uid_index = 0 while _generate_deterministic_uuid(uid_index, "5c59aad4") in scene.objects: uid_index += 1 + object_id = _generate_deterministic_uuid(uid_index, "5c59aad4") return UUID(str(object_id)) diff --git a/tests/test_raillabel/scene_builder/test_scene_builder.py b/tests/test_raillabel/scene_builder/test_scene_builder.py index 3923ea1..8a8162d 100644 --- a/tests/test_raillabel/scene_builder/test_scene_builder.py +++ b/tests/test_raillabel/scene_builder/test_scene_builder.py @@ -7,8 +7,19 @@ import pytest import raillabel -from raillabel.format import Scene, Metadata, Object from raillabel.scene_builder.scene_builder import SceneBuilder +from raillabel.format import ( + Scene, + Metadata, + Object, + Camera, + Lidar, + Radar, + GpsImu, + OtherSensor, + IntrinsicsPinhole, + IntrinsicsRadar, +) def test_empty(): @@ -91,5 +102,76 @@ def test_add_object__object_id_iteration(): ) +def test_add_sensor__camera_rgb(): + actual = SceneBuilder.empty().add_sensor("rgb_middle") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + sensors={ + "rgb_middle": Camera( + intrinsics=IntrinsicsPinhole( + camera_matrix=tuple([0] * 12), + distortion=tuple([0] * 5), + width_px=0, + height_px=0, + ) + ) + }, + ) + + +def test_add_sensor__camera_ir(): + actual = SceneBuilder.empty().add_sensor("ir_left") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + sensors={ + "ir_left": Camera( + intrinsics=IntrinsicsPinhole( + camera_matrix=tuple([0] * 12), + distortion=tuple([0] * 5), + width_px=0, + height_px=0, + ) + ) + }, + ) + + +def test_add_sensor__radar(): + actual = SceneBuilder.empty().add_sensor("radar") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + sensors={ + "radar": Radar( + intrinsics=IntrinsicsRadar( + resolution_px_per_m=0, + width_px=0, + height_px=0, + ) + ) + }, + ) + + +def test_add_sensor__lidar(): + actual = SceneBuilder.empty().add_sensor("lidar") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), sensors={"lidar": Lidar()} + ) + + +def test_add_sensor__gps_imu(): + actual = SceneBuilder.empty().add_sensor("gps_imu") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), sensors={"gps_imu": GpsImu()} + ) + + +def test_add_sensor__other(): + actual = SceneBuilder.empty().add_sensor("SOMETHING_ELSE") + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), sensors={"SOMETHING_ELSE": OtherSensor()} + ) + + if __name__ == "__main__": - pytest.main([__file__, "-v"]) + pytest.main([__file__, "-vv"]) From 55554d499b660c470c13da92fdf56de367662ca6 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Fri, 15 Nov 2024 18:32:54 +0100 Subject: [PATCH 159/190] feat: implement SceneBuilder.add_frame() --- pyproject.toml | 1 + raillabel/scene_builder/scene_builder.py | 27 +++++++++++++++++++ .../scene_builder/test_scene_builder.py | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7f07378..b802571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ ignore = [ "D107", # __init__ docstrings are not necessary "D203", # incompatible with D211 "D213", # incompatible with D212 + "D413", # rule by convention which looks weird "D417", # kwargs can not be typed for the filter function "FBT001", # flags in functions are not bad practice diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index d398a0c..5727587 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -5,10 +5,12 @@ from copy import deepcopy from dataclasses import dataclass +from decimal import Decimal from uuid import UUID from raillabel.format import ( Camera, + Frame, GpsImu, IntrinsicsPinhole, IntrinsicsRadar, @@ -84,6 +86,31 @@ def add_sensor(self, sensor_id: str) -> SceneBuilder: return SceneBuilder(scene) + def add_frame(self, frame_id: int | None = None, timestamp: float | None = None) -> SceneBuilder: + """Add a frame to the scene. + + If no frame_id is provided, the frame_id is the lowest number not currently occupied by + another frame_id. + + Example: + ```python + scene = SceneBuilder.empty().add_frame(frame_id=1).add_frame(frame_id=3).add_frame().result + assert sorted(list(scene.frames.keys())) == [1, 2, 3] + ``` + """ + scene = deepcopy(self.result) + + if frame_id is None: + frame_id = 1 + while frame_id in scene.frames: + frame_id += 1 + + scene.frames[frame_id] = Frame( + timestamp=Decimal(timestamp) if timestamp is not None else None + ) + + return SceneBuilder(scene) + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None diff --git a/tests/test_raillabel/scene_builder/test_scene_builder.py b/tests/test_raillabel/scene_builder/test_scene_builder.py index 8a8162d..8efb1dc 100644 --- a/tests/test_raillabel/scene_builder/test_scene_builder.py +++ b/tests/test_raillabel/scene_builder/test_scene_builder.py @@ -3,6 +3,7 @@ from __future__ import annotations from uuid import UUID +from decimal import Decimal import pytest @@ -19,6 +20,7 @@ OtherSensor, IntrinsicsPinhole, IntrinsicsRadar, + Frame, ) @@ -173,5 +175,30 @@ def test_add_sensor__other(): ) +def test_add_frame(): + actual = SceneBuilder.empty().add_frame(1, 1631691173) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), frames={1: Frame(timestamp=Decimal(1631691173))} + ) + + +def test_add_frame__no_timestamp(): + actual = SceneBuilder.empty().add_frame(1) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), frames={1: Frame(timestamp=None)} + ) + + +def test_add_frame__no_frame_id(): + actual = SceneBuilder.empty().add_frame().add_frame() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + frames={ + 1: Frame(), + 2: Frame(), + }, + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 252fdb6cbedae6a4d892b1e2790b726e20dd470a Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 13:16:43 +0100 Subject: [PATCH 160/190] test: move raillabel tests one directory up --- tests/conftest.py | 39 ++++++++++++++++++ tests/{test_raillabel => format}/__init__.py | 0 tests/{test_raillabel => }/format/conftest.py | 0 .../format/test_attributes.py | 0 .../{test_raillabel => }/format/test_bbox.py | 0 .../format/test_camera.py | 16 +++++++- .../format/test_cuboid.py | 0 .../{test_raillabel => }/format/test_frame.py | 0 .../format/test_frame_interval.py | 0 .../format/test_gps_imu.py | 0 .../format/test_intrinsics_pinhole.py | 0 .../format/test_intrinsics_radar.py | 0 .../{test_raillabel => }/format/test_lidar.py | 0 .../format/test_metadata.py | 0 tests/{test_raillabel => }/format/test_num.py | 0 .../format/test_object.py | 0 .../format/test_other_sensor.py | 0 .../format/test_point2d.py | 0 .../format/test_point3d.py | 0 .../format/test_poly2d.py | 0 .../format/test_poly3d.py | 0 .../format/test_quaternion.py | 0 .../{test_raillabel => }/format/test_radar.py | 0 .../{test_raillabel => }/format/test_scene.py | 0 .../{test_raillabel => }/format/test_seg3d.py | 0 .../format/test_sensor_reference.py | 0 .../format/test_size2d.py | 0 .../format/test_size3d.py | 0 .../format/test_transform.py | 0 .../json_format/test_scene.py | 0 tests/{test_raillabel => }/load/test_load.py | 0 tests/{test_raillabel => }/save/test_save.py | 0 .../scene_builder/test_scene_builder.py | 41 ++++++++++++++----- tests/test_raillabel/format/__init__.py | 2 - 34 files changed, 83 insertions(+), 15 deletions(-) rename tests/{test_raillabel => format}/__init__.py (100%) rename tests/{test_raillabel => }/format/conftest.py (100%) rename tests/{test_raillabel => }/format/test_attributes.py (100%) rename tests/{test_raillabel => }/format/test_bbox.py (100%) rename tests/{test_raillabel => }/format/test_camera.py (78%) rename tests/{test_raillabel => }/format/test_cuboid.py (100%) rename tests/{test_raillabel => }/format/test_frame.py (100%) rename tests/{test_raillabel => }/format/test_frame_interval.py (100%) rename tests/{test_raillabel => }/format/test_gps_imu.py (100%) rename tests/{test_raillabel => }/format/test_intrinsics_pinhole.py (100%) rename tests/{test_raillabel => }/format/test_intrinsics_radar.py (100%) rename tests/{test_raillabel => }/format/test_lidar.py (100%) rename tests/{test_raillabel => }/format/test_metadata.py (100%) rename tests/{test_raillabel => }/format/test_num.py (100%) rename tests/{test_raillabel => }/format/test_object.py (100%) rename tests/{test_raillabel => }/format/test_other_sensor.py (100%) rename tests/{test_raillabel => }/format/test_point2d.py (100%) rename tests/{test_raillabel => }/format/test_point3d.py (100%) rename tests/{test_raillabel => }/format/test_poly2d.py (100%) rename tests/{test_raillabel => }/format/test_poly3d.py (100%) rename tests/{test_raillabel => }/format/test_quaternion.py (100%) rename tests/{test_raillabel => }/format/test_radar.py (100%) rename tests/{test_raillabel => }/format/test_scene.py (100%) rename tests/{test_raillabel => }/format/test_seg3d.py (100%) rename tests/{test_raillabel => }/format/test_sensor_reference.py (100%) rename tests/{test_raillabel => }/format/test_size2d.py (100%) rename tests/{test_raillabel => }/format/test_size3d.py (100%) rename tests/{test_raillabel => }/format/test_transform.py (100%) rename tests/{test_raillabel => }/json_format/test_scene.py (100%) rename tests/{test_raillabel => }/load/test_load.py (100%) rename tests/{test_raillabel => }/save/test_save.py (100%) rename tests/{test_raillabel => }/scene_builder/test_scene_builder.py (85%) delete mode 100644 tests/test_raillabel/format/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py index f9503ef..0b009bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,45 @@ sys.path.insert(1, str(Path(__file__).parent.parent)) import raillabel + +from format.test_attributes import attributes_multiple_types, attributes_multiple_types_json +from format.test_bbox import bbox, bbox_json, bbox_id +from format.test_camera import camera, camera_json, camera_empty +from format.test_cuboid import cuboid, cuboid_json, cuboid_id +from format.test_frame import frame, frame_json +from format.test_frame_interval import frame_interval, frame_interval_json +from format.test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json +from format.test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json +from format.test_lidar import lidar, lidar_json +from format.test_metadata import metadata, metadata_json +from format.test_num import num, num_json, num_id +from format.test_object import ( + objects, + object_person, + object_person_json, + object_person_id, + object_track, + object_track_json, + object_track_id, +) +from format.test_point2d import point2d, point2d_json, another_point2d, another_point2d_json +from format.test_point3d import point3d, point3d_json, another_point3d, another_point3d_json +from format.test_poly2d import poly2d, poly2d_json, poly2d_id +from format.test_poly3d import poly3d, poly3d_json, poly3d_id +from format.test_quaternion import quaternion, quaternion_json +from format.test_radar import radar, radar_json +from format.test_size2d import size2d, size2d_json +from format.test_size3d import size3d, size3d_json +from format.test_seg3d import seg3d, seg3d_json, seg3d_id +from format.test_sensor_reference import ( + another_sensor_reference, + another_sensor_reference_json, + sensor_reference, + sensor_reference_json, +) +from format.test_transform import transform, transform_json + + json_data_directories = [ Path(__file__).parent / "__test_assets__", ] diff --git a/tests/test_raillabel/__init__.py b/tests/format/__init__.py similarity index 100% rename from tests/test_raillabel/__init__.py rename to tests/format/__init__.py diff --git a/tests/test_raillabel/format/conftest.py b/tests/format/conftest.py similarity index 100% rename from tests/test_raillabel/format/conftest.py rename to tests/format/conftest.py diff --git a/tests/test_raillabel/format/test_attributes.py b/tests/format/test_attributes.py similarity index 100% rename from tests/test_raillabel/format/test_attributes.py rename to tests/format/test_attributes.py diff --git a/tests/test_raillabel/format/test_bbox.py b/tests/format/test_bbox.py similarity index 100% rename from tests/test_raillabel/format/test_bbox.py rename to tests/format/test_bbox.py diff --git a/tests/test_raillabel/format/test_camera.py b/tests/format/test_camera.py similarity index 78% rename from tests/test_raillabel/format/test_camera.py rename to tests/format/test_camera.py index 969f4a2..c4f6388 100644 --- a/tests/test_raillabel/format/test_camera.py +++ b/tests/format/test_camera.py @@ -5,7 +5,7 @@ import pytest -from raillabel.format import Camera +from raillabel.format import Camera, IntrinsicsPinhole from raillabel.json_format import ( JSONCoordinateSystem, JSONStreamCamera, @@ -34,7 +34,7 @@ def camera_json( @pytest.fixture -def camera(intrinsics_pinhole, transform) -> dict: +def camera(intrinsics_pinhole, transform) -> Camera: return Camera( intrinsics=intrinsics_pinhole, extrinsics=transform, @@ -43,6 +43,18 @@ def camera(intrinsics_pinhole, transform) -> dict: ) +@pytest.fixture +def camera_empty(intrinsics_pinhole, transform) -> Camera: + return Camera( + intrinsics=IntrinsicsPinhole( + camera_matrix=tuple([0] * 12), + distortion=tuple([0] * 5), + width_px=0, + height_px=0, + ) + ) + + # == Tests ============================ diff --git a/tests/test_raillabel/format/test_cuboid.py b/tests/format/test_cuboid.py similarity index 100% rename from tests/test_raillabel/format/test_cuboid.py rename to tests/format/test_cuboid.py diff --git a/tests/test_raillabel/format/test_frame.py b/tests/format/test_frame.py similarity index 100% rename from tests/test_raillabel/format/test_frame.py rename to tests/format/test_frame.py diff --git a/tests/test_raillabel/format/test_frame_interval.py b/tests/format/test_frame_interval.py similarity index 100% rename from tests/test_raillabel/format/test_frame_interval.py rename to tests/format/test_frame_interval.py diff --git a/tests/test_raillabel/format/test_gps_imu.py b/tests/format/test_gps_imu.py similarity index 100% rename from tests/test_raillabel/format/test_gps_imu.py rename to tests/format/test_gps_imu.py diff --git a/tests/test_raillabel/format/test_intrinsics_pinhole.py b/tests/format/test_intrinsics_pinhole.py similarity index 100% rename from tests/test_raillabel/format/test_intrinsics_pinhole.py rename to tests/format/test_intrinsics_pinhole.py diff --git a/tests/test_raillabel/format/test_intrinsics_radar.py b/tests/format/test_intrinsics_radar.py similarity index 100% rename from tests/test_raillabel/format/test_intrinsics_radar.py rename to tests/format/test_intrinsics_radar.py diff --git a/tests/test_raillabel/format/test_lidar.py b/tests/format/test_lidar.py similarity index 100% rename from tests/test_raillabel/format/test_lidar.py rename to tests/format/test_lidar.py diff --git a/tests/test_raillabel/format/test_metadata.py b/tests/format/test_metadata.py similarity index 100% rename from tests/test_raillabel/format/test_metadata.py rename to tests/format/test_metadata.py diff --git a/tests/test_raillabel/format/test_num.py b/tests/format/test_num.py similarity index 100% rename from tests/test_raillabel/format/test_num.py rename to tests/format/test_num.py diff --git a/tests/test_raillabel/format/test_object.py b/tests/format/test_object.py similarity index 100% rename from tests/test_raillabel/format/test_object.py rename to tests/format/test_object.py diff --git a/tests/test_raillabel/format/test_other_sensor.py b/tests/format/test_other_sensor.py similarity index 100% rename from tests/test_raillabel/format/test_other_sensor.py rename to tests/format/test_other_sensor.py diff --git a/tests/test_raillabel/format/test_point2d.py b/tests/format/test_point2d.py similarity index 100% rename from tests/test_raillabel/format/test_point2d.py rename to tests/format/test_point2d.py diff --git a/tests/test_raillabel/format/test_point3d.py b/tests/format/test_point3d.py similarity index 100% rename from tests/test_raillabel/format/test_point3d.py rename to tests/format/test_point3d.py diff --git a/tests/test_raillabel/format/test_poly2d.py b/tests/format/test_poly2d.py similarity index 100% rename from tests/test_raillabel/format/test_poly2d.py rename to tests/format/test_poly2d.py diff --git a/tests/test_raillabel/format/test_poly3d.py b/tests/format/test_poly3d.py similarity index 100% rename from tests/test_raillabel/format/test_poly3d.py rename to tests/format/test_poly3d.py diff --git a/tests/test_raillabel/format/test_quaternion.py b/tests/format/test_quaternion.py similarity index 100% rename from tests/test_raillabel/format/test_quaternion.py rename to tests/format/test_quaternion.py diff --git a/tests/test_raillabel/format/test_radar.py b/tests/format/test_radar.py similarity index 100% rename from tests/test_raillabel/format/test_radar.py rename to tests/format/test_radar.py diff --git a/tests/test_raillabel/format/test_scene.py b/tests/format/test_scene.py similarity index 100% rename from tests/test_raillabel/format/test_scene.py rename to tests/format/test_scene.py diff --git a/tests/test_raillabel/format/test_seg3d.py b/tests/format/test_seg3d.py similarity index 100% rename from tests/test_raillabel/format/test_seg3d.py rename to tests/format/test_seg3d.py diff --git a/tests/test_raillabel/format/test_sensor_reference.py b/tests/format/test_sensor_reference.py similarity index 100% rename from tests/test_raillabel/format/test_sensor_reference.py rename to tests/format/test_sensor_reference.py diff --git a/tests/test_raillabel/format/test_size2d.py b/tests/format/test_size2d.py similarity index 100% rename from tests/test_raillabel/format/test_size2d.py rename to tests/format/test_size2d.py diff --git a/tests/test_raillabel/format/test_size3d.py b/tests/format/test_size3d.py similarity index 100% rename from tests/test_raillabel/format/test_size3d.py rename to tests/format/test_size3d.py diff --git a/tests/test_raillabel/format/test_transform.py b/tests/format/test_transform.py similarity index 100% rename from tests/test_raillabel/format/test_transform.py rename to tests/format/test_transform.py diff --git a/tests/test_raillabel/json_format/test_scene.py b/tests/json_format/test_scene.py similarity index 100% rename from tests/test_raillabel/json_format/test_scene.py rename to tests/json_format/test_scene.py diff --git a/tests/test_raillabel/load/test_load.py b/tests/load/test_load.py similarity index 100% rename from tests/test_raillabel/load/test_load.py rename to tests/load/test_load.py diff --git a/tests/test_raillabel/save/test_save.py b/tests/save/test_save.py similarity index 100% rename from tests/test_raillabel/save/test_save.py rename to tests/save/test_save.py diff --git a/tests/test_raillabel/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py similarity index 85% rename from tests/test_raillabel/scene_builder/test_scene_builder.py rename to tests/scene_builder/test_scene_builder.py index 8efb1dc..a8c7e15 100644 --- a/tests/test_raillabel/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -21,6 +21,7 @@ IntrinsicsPinhole, IntrinsicsRadar, Frame, + Bbox, ) @@ -104,20 +105,11 @@ def test_add_object__object_id_iteration(): ) -def test_add_sensor__camera_rgb(): +def test_add_sensor__camera_rgb(camera_empty): actual = SceneBuilder.empty().add_sensor("rgb_middle") assert actual.result == Scene( metadata=Metadata(schema_version="1.0.0"), - sensors={ - "rgb_middle": Camera( - intrinsics=IntrinsicsPinhole( - camera_matrix=tuple([0] * 12), - distortion=tuple([0] * 5), - width_px=0, - height_px=0, - ) - ) - }, + sensors={"rgb_middle": camera_empty}, ) @@ -200,5 +192,32 @@ def test_add_frame__no_frame_id(): ) +# def test_add_bbox(): +# actual = SceneBuilder.empty().add_bbox(frame_id=2, object_name="person_0001", sensor_id="rgb_middle") +# assert actual.result == Scene( +# metadata=Metadata(schema_version="1.0.0"), +# objects={ +# UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") +# }, +# sensors={ +# "rgb_middle": Camera( +# intrinsics=IntrinsicsPinhole( +# camera_matrix=tuple([0] * 12), +# distortion=tuple([0] * 5), +# width_px=0, +# height_px=0, +# ) +# ) +# }, +# frames={ +# 2: Frame( +# annotations={ +# UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Bbox() +# } +# ), +# }, +# ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) diff --git a/tests/test_raillabel/format/__init__.py b/tests/test_raillabel/format/__init__.py deleted file mode 100644 index dd5d085..0000000 --- a/tests/test_raillabel/format/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 From ff232600a63e8cebaea88ebac5ff1ab96692cb25 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 13:21:26 +0100 Subject: [PATCH 161/190] test: create empty radar fixture --- tests/conftest.py | 2 +- tests/format/conftest.py | 38 ----------------------- tests/format/test_camera.py | 2 +- tests/format/test_radar.py | 15 +++++++-- tests/scene_builder/test_scene_builder.py | 25 +++------------ 5 files changed, 19 insertions(+), 63 deletions(-) delete mode 100644 tests/format/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py index 0b009bd..06b81e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,7 +41,7 @@ from format.test_poly2d import poly2d, poly2d_json, poly2d_id from format.test_poly3d import poly3d, poly3d_json, poly3d_id from format.test_quaternion import quaternion, quaternion_json -from format.test_radar import radar, radar_json +from format.test_radar import radar, radar_json, radar_empty from format.test_size2d import size2d, size2d_json from format.test_size3d import size3d, size3d_json from format.test_seg3d import seg3d, seg3d_json, seg3d_id diff --git a/tests/format/conftest.py b/tests/format/conftest.py deleted file mode 100644 index 4f75bde..0000000 --- a/tests/format/conftest.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -from .test_attributes import attributes_multiple_types, attributes_multiple_types_json -from .test_bbox import bbox, bbox_json, bbox_id -from .test_camera import camera, camera_json -from .test_cuboid import cuboid, cuboid_json, cuboid_id -from .test_frame import frame, frame_json -from .test_frame_interval import frame_interval, frame_interval_json -from .test_intrinsics_pinhole import intrinsics_pinhole, intrinsics_pinhole_json -from .test_intrinsics_radar import intrinsics_radar, intrinsics_radar_json -from .test_lidar import lidar, lidar_json -from .test_metadata import metadata, metadata_json -from .test_num import num, num_json, num_id -from .test_object import ( - objects, - object_person, - object_person_json, - object_person_id, - object_track, - object_track_json, - object_track_id, -) -from .test_point2d import point2d, point2d_json, another_point2d, another_point2d_json -from .test_point3d import point3d, point3d_json, another_point3d, another_point3d_json -from .test_poly2d import poly2d, poly2d_json, poly2d_id -from .test_poly3d import poly3d, poly3d_json, poly3d_id -from .test_quaternion import quaternion, quaternion_json -from .test_radar import radar, radar_json -from .test_size2d import size2d, size2d_json -from .test_size3d import size3d, size3d_json -from .test_seg3d import seg3d, seg3d_json, seg3d_id -from .test_sensor_reference import ( - another_sensor_reference, - another_sensor_reference_json, - sensor_reference, - sensor_reference_json, -) -from .test_transform import transform, transform_json diff --git a/tests/format/test_camera.py b/tests/format/test_camera.py index c4f6388..58598f0 100644 --- a/tests/format/test_camera.py +++ b/tests/format/test_camera.py @@ -44,7 +44,7 @@ def camera(intrinsics_pinhole, transform) -> Camera: @pytest.fixture -def camera_empty(intrinsics_pinhole, transform) -> Camera: +def camera_empty() -> Camera: return Camera( intrinsics=IntrinsicsPinhole( camera_matrix=tuple([0] * 12), diff --git a/tests/format/test_radar.py b/tests/format/test_radar.py index 5bd8958..2e15f59 100644 --- a/tests/format/test_radar.py +++ b/tests/format/test_radar.py @@ -5,7 +5,7 @@ import pytest -from raillabel.format import Radar +from raillabel.format import Radar, IntrinsicsRadar from raillabel.json_format import ( JSONCoordinateSystem, JSONStreamRadar, @@ -34,7 +34,7 @@ def radar_json( @pytest.fixture -def radar(intrinsics_radar, transform) -> dict: +def radar(intrinsics_radar, transform) -> Radar: return Radar( intrinsics=intrinsics_radar, extrinsics=transform, @@ -43,6 +43,17 @@ def radar(intrinsics_radar, transform) -> dict: ) +@pytest.fixture +def radar_empty() -> Radar: + return Radar( + intrinsics=IntrinsicsRadar( + resolution_px_per_m=0, + width_px=0, + height_px=0, + ) + ) + + # == Tests ============================ diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index a8c7e15..6a7d773 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -113,36 +113,19 @@ def test_add_sensor__camera_rgb(camera_empty): ) -def test_add_sensor__camera_ir(): +def test_add_sensor__camera_ir(camera_empty): actual = SceneBuilder.empty().add_sensor("ir_left") assert actual.result == Scene( metadata=Metadata(schema_version="1.0.0"), - sensors={ - "ir_left": Camera( - intrinsics=IntrinsicsPinhole( - camera_matrix=tuple([0] * 12), - distortion=tuple([0] * 5), - width_px=0, - height_px=0, - ) - ) - }, + sensors={"ir_left": camera_empty}, ) -def test_add_sensor__radar(): +def test_add_sensor__radar(radar_empty): actual = SceneBuilder.empty().add_sensor("radar") assert actual.result == Scene( metadata=Metadata(schema_version="1.0.0"), - sensors={ - "radar": Radar( - intrinsics=IntrinsicsRadar( - resolution_px_per_m=0, - width_px=0, - height_px=0, - ) - ) - }, + sensors={"radar": radar_empty}, ) From b7d3f930a4625919f6cbbf1f8133f7679ccc8356 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 13:53:49 +0100 Subject: [PATCH 162/190] feat: implement SceneBuilder.add_bbox() --- raillabel/scene_builder/scene_builder.py | 61 ++++++++++++++++++ tests/scene_builder/test_scene_builder.py | 78 +++++++++++++++-------- 2 files changed, 114 insertions(+), 25 deletions(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 5727587..2b73471 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -9,6 +9,7 @@ from uuid import UUID from raillabel.format import ( + Bbox, Camera, Frame, GpsImu, @@ -18,9 +19,12 @@ Metadata, Object, OtherSensor, + Point2d, Radar, Scene, + Size2d, ) +from raillabel.format._util import _flatten_list @dataclass @@ -111,6 +115,37 @@ def add_frame(self, frame_id: int | None = None, timestamp: float | None = None) return SceneBuilder(scene) + def add_bbox( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "rgb_middle", + ) -> SceneBuilder: + """Add a bbox to the scene.""" + new_builder = deepcopy(self) + + uid = _resolve_empty_annotation_uid(new_builder.result, uid) + + if not _is_object_name_object(object_name, new_builder.result): + new_builder = new_builder.add_object(object_name=object_name) + + if sensor_id not in new_builder.result.sensors: + new_builder = new_builder.add_sensor(sensor_id) + + if frame_id not in new_builder.result.frames: + new_builder = new_builder.add_frame(frame_id=frame_id) + + new_builder.result.frames[frame_id].annotations[uid] = Bbox( + object_id=_get_object_uid_from_name(object_name, new_builder.result), # type: ignore + sensor_id=sensor_id, + pos=Point2d(0, 0), + size=Size2d(0, 0), + attributes={}, + ) + + return new_builder + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None @@ -147,3 +182,29 @@ def _resolve_empty_object_uid(scene: Scene, object_id: str | UUID | None) -> UUI def _generate_deterministic_uuid(index: int, prefix: str) -> UUID: return UUID(f"{prefix.zfill(0)}-0000-4000-0000-{str(index).zfill(12)}") + + +def _resolve_empty_annotation_uid(scene: Scene, uid: str | UUID | None) -> UUID: + if uid is None: + annotation_uids_in_scene = set( + _flatten_list([tuple(frame.annotations.keys()) for frame in scene.frames.values()]) + ) + uid_index = 0 + while _generate_deterministic_uuid(uid_index, "6c95543d") in annotation_uids_in_scene: + uid_index += 1 + + uid = _generate_deterministic_uuid(uid_index, "6c95543d") + + return UUID(str(uid)) + + +def _is_object_name_object(object_name: str, scene: Scene) -> bool: + return any(object_.name == object_name for object_ in scene.objects.values()) + + +def _get_object_uid_from_name(object_name: str, scene: Scene) -> UUID: + for object_uid, object_ in scene.objects.items(): + if object_.name == object_name: + return object_uid + + raise RuntimeError diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index 6a7d773..d87dc8d 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -22,6 +22,8 @@ IntrinsicsRadar, Frame, Bbox, + Point2d, + Size2d, ) @@ -175,31 +177,57 @@ def test_add_frame__no_frame_id(): ) -# def test_add_bbox(): -# actual = SceneBuilder.empty().add_bbox(frame_id=2, object_name="person_0001", sensor_id="rgb_middle") -# assert actual.result == Scene( -# metadata=Metadata(schema_version="1.0.0"), -# objects={ -# UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") -# }, -# sensors={ -# "rgb_middle": Camera( -# intrinsics=IntrinsicsPinhole( -# camera_matrix=tuple([0] * 12), -# distortion=tuple([0] * 5), -# width_px=0, -# height_px=0, -# ) -# ) -# }, -# frames={ -# 2: Frame( -# annotations={ -# UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Bbox() -# } -# ), -# }, -# ) +def test_add_bbox(camera_empty): + actual = SceneBuilder.empty().add_bbox( + uid=UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"), + frame_id=2, + object_name="person_0001", + sensor_id="ir_middle", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"ir_middle": camera_empty}, + frames={ + 2: Frame( + annotations={ + UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Bbox( + sensor_id="ir_middle", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + pos=Point2d(0, 0), + size=Size2d(0, 0), + attributes={}, + ) + } + ), + }, + ) + + +def test_add_bbox__just_defaults(camera_empty): + actual = SceneBuilder.empty().add_bbox() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"rgb_middle": camera_empty}, + frames={ + 1: Frame( + annotations={ + UUID("6c95543d-0000-4000-0000-000000000000"): Bbox( + sensor_id="rgb_middle", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + pos=Point2d(0, 0), + size=Size2d(0, 0), + attributes={}, + ) + } + ), + }, + ) if __name__ == "__main__": From 3cec05e05c47ef1660267009a2d820a7934f23f1 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 14:08:31 +0100 Subject: [PATCH 163/190] feat: implement SceneBuilder.add_cuboid() --- raillabel/scene_builder/scene_builder.py | 61 +++++++++++++++++++++-- tests/scene_builder/test_scene_builder.py | 59 ++++++++++++++++++++++ 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 2b73471..519e948 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -11,6 +11,7 @@ from raillabel.format import ( Bbox, Camera, + Cuboid, Frame, GpsImu, IntrinsicsPinhole, @@ -20,9 +21,15 @@ Object, OtherSensor, Point2d, + Point3d, + Poly2d, + Poly3d, + Quaternion, Radar, Scene, + Seg3d, Size2d, + Size3d, ) from raillabel.format._util import _flatten_list @@ -115,14 +122,15 @@ def add_frame(self, frame_id: int | None = None, timestamp: float | None = None) return SceneBuilder(scene) - def add_bbox( + def add_annotation( self, + annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, uid: str | UUID | None = None, frame_id: int = 1, object_name: str = "person_0001", sensor_id: str = "rgb_middle", ) -> SceneBuilder: - """Add a bbox to the scene.""" + """Add an annotation to the scene.""" new_builder = deepcopy(self) uid = _resolve_empty_annotation_uid(new_builder.result, uid) @@ -136,15 +144,58 @@ def add_bbox( if frame_id not in new_builder.result.frames: new_builder = new_builder.add_frame(frame_id=frame_id) - new_builder.result.frames[frame_id].annotations[uid] = Bbox( - object_id=_get_object_uid_from_name(object_name, new_builder.result), # type: ignore + annotation.object_id = _get_object_uid_from_name(object_name, new_builder.result) + annotation.sensor_id = sensor_id + + new_builder.result.frames[frame_id].annotations[uid] = annotation + return new_builder + + def add_bbox( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "rgb_middle", + ) -> SceneBuilder: + """Add a bbox to the scene.""" + bbox = Bbox( + object_id=UUID("ffffffff-ffff-4fff-ffff-ffffffffffff"), sensor_id=sensor_id, pos=Point2d(0, 0), size=Size2d(0, 0), attributes={}, ) + return self.add_annotation( + annotation=bbox, + uid=uid, + frame_id=frame_id, + object_name=object_name, + sensor_id=sensor_id, + ) - return new_builder + def add_cuboid( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "lidar", + ) -> SceneBuilder: + """Add a cuboid to the scene.""" + cuboid = Cuboid( + object_id=UUID("ffffffff-ffff-4fff-ffff-ffffffffffff"), + sensor_id=sensor_id, + pos=Point3d(0, 0, 0), + size=Size3d(0, 0, 0), + quat=Quaternion(0, 0, 0, 0), + attributes={}, + ) + return self.add_annotation( + annotation=cuboid, + uid=uid, + frame_id=frame_id, + object_name=object_name, + sensor_id=sensor_id, + ) def _resolve_empty_object_name_or_type( diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index d87dc8d..b67478d 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -24,6 +24,10 @@ Bbox, Point2d, Size2d, + Cuboid, + Point3d, + Size3d, + Quaternion, ) @@ -230,5 +234,60 @@ def test_add_bbox__just_defaults(camera_empty): ) +def test_add_cuboid(): + actual = SceneBuilder.empty().add_cuboid( + uid=UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"), + frame_id=2, + object_name="person_0001", + sensor_id="lidar_left", + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar_left": Lidar()}, + frames={ + 2: Frame( + annotations={ + UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Cuboid( + sensor_id="lidar_left", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + pos=Point3d(0, 0, 0), + size=Size3d(0, 0, 0), + quat=Quaternion(0, 0, 0, 0), + attributes={}, + ) + } + ), + }, + ) + + +def test_add_cuboid__just_defaults(): + actual = SceneBuilder.empty().add_cuboid() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar": Lidar()}, + frames={ + 1: Frame( + annotations={ + UUID("6c95543d-0000-4000-0000-000000000000"): Cuboid( + sensor_id="lidar", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + pos=Point3d(0, 0, 0), + size=Size3d(0, 0, 0), + quat=Quaternion(0, 0, 0, 0), + attributes={}, + ) + } + ), + }, + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 6207011c424c89577cecd927c142a98b248cf7cb Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 14:17:48 +0100 Subject: [PATCH 164/190] feat: implement SceneBuilder.add_poly2d() --- raillabel/scene_builder/scene_builder.py | 30 ++++++++++- tests/scene_builder/test_scene_builder.py | 63 ++++++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 519e948..39a724b 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -156,6 +156,7 @@ def add_bbox( frame_id: int = 1, object_name: str = "person_0001", sensor_id: str = "rgb_middle", + attributes: dict | None = None, ) -> SceneBuilder: """Add a bbox to the scene.""" bbox = Bbox( @@ -163,7 +164,7 @@ def add_bbox( sensor_id=sensor_id, pos=Point2d(0, 0), size=Size2d(0, 0), - attributes={}, + attributes=attributes if attributes is not None else {}, ) return self.add_annotation( annotation=bbox, @@ -179,6 +180,7 @@ def add_cuboid( frame_id: int = 1, object_name: str = "person_0001", sensor_id: str = "lidar", + attributes: dict | None = None, ) -> SceneBuilder: """Add a cuboid to the scene.""" cuboid = Cuboid( @@ -187,7 +189,7 @@ def add_cuboid( pos=Point3d(0, 0, 0), size=Size3d(0, 0, 0), quat=Quaternion(0, 0, 0, 0), - attributes={}, + attributes=attributes if attributes is not None else {}, ) return self.add_annotation( annotation=cuboid, @@ -197,6 +199,30 @@ def add_cuboid( sensor_id=sensor_id, ) + def add_poly2d( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "rgb_middle", + attributes: dict | None = None, + ) -> SceneBuilder: + """Add a poly2d to the scene.""" + poly2d = Poly2d( + object_id=UUID("ffffffff-ffff-4fff-ffff-ffffffffffff"), + sensor_id=sensor_id, + points=[], + closed=False, + attributes=attributes if attributes is not None else {}, + ) + return self.add_annotation( + annotation=poly2d, + uid=uid, + frame_id=frame_id, + object_name=object_name, + sensor_id=sensor_id, + ) + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index b67478d..338f2d8 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -28,6 +28,9 @@ Point3d, Size3d, Quaternion, + Poly2d, + Poly3d, + Seg3d, ) @@ -187,6 +190,7 @@ def test_add_bbox(camera_empty): frame_id=2, object_name="person_0001", sensor_id="ir_middle", + attributes={"attr": True}, ) assert actual.result == Scene( metadata=Metadata(schema_version="1.0.0"), @@ -202,7 +206,7 @@ def test_add_bbox(camera_empty): object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), pos=Point2d(0, 0), size=Size2d(0, 0), - attributes={}, + attributes={"attr": True}, ) } ), @@ -240,6 +244,7 @@ def test_add_cuboid(): frame_id=2, object_name="person_0001", sensor_id="lidar_left", + attributes={"my_attr": 5}, ) assert actual.result == Scene( metadata=Metadata(schema_version="1.0.0"), @@ -256,7 +261,7 @@ def test_add_cuboid(): pos=Point3d(0, 0, 0), size=Size3d(0, 0, 0), quat=Quaternion(0, 0, 0, 0), - attributes={}, + attributes={"my_attr": 5}, ) } ), @@ -289,5 +294,59 @@ def test_add_cuboid__just_defaults(): ) +def test_add_poly2d(camera_empty): + actual = SceneBuilder.empty().add_poly2d( + uid=UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"), + frame_id=2, + object_name="person_0001", + sensor_id="ir_left", + attributes={"my_attr": 5}, + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"ir_left": camera_empty}, + frames={ + 2: Frame( + annotations={ + UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Poly2d( + sensor_id="ir_left", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + points=[], + closed=False, + attributes={"my_attr": 5}, + ) + } + ), + }, + ) + + +def test_add_poly2d__just_defaults(camera_empty): + actual = SceneBuilder.empty().add_poly2d() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"rgb_middle": camera_empty}, + frames={ + 1: Frame( + annotations={ + UUID("6c95543d-0000-4000-0000-000000000000"): Poly2d( + sensor_id="rgb_middle", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + points=[], + closed=False, + attributes={}, + ) + } + ), + }, + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 2630778fa856d5cdf00067367417729c47c9667e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 14:19:42 +0100 Subject: [PATCH 165/190] feat: implement SceneBuilder.add_poly3d() --- raillabel/scene_builder/scene_builder.py | 24 ++++++++++ tests/scene_builder/test_scene_builder.py | 54 +++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 39a724b..786ec75 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -223,6 +223,30 @@ def add_poly2d( sensor_id=sensor_id, ) + def add_poly3d( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "lidar", + attributes: dict | None = None, + ) -> SceneBuilder: + """Add a poly3d to the scene.""" + poly3d = Poly3d( + object_id=UUID("ffffffff-ffff-4fff-ffff-ffffffffffff"), + sensor_id=sensor_id, + points=[], + closed=False, + attributes=attributes if attributes is not None else {}, + ) + return self.add_annotation( + annotation=poly3d, + uid=uid, + frame_id=frame_id, + object_name=object_name, + sensor_id=sensor_id, + ) + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index 338f2d8..1df925d 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -348,5 +348,59 @@ def test_add_poly2d__just_defaults(camera_empty): ) +def test_add_poly3d(): + actual = SceneBuilder.empty().add_poly3d( + uid=UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"), + frame_id=2, + object_name="person_0001", + sensor_id="lidar_right", + attributes={"my_attr": 5}, + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar_right": Lidar()}, + frames={ + 2: Frame( + annotations={ + UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Poly3d( + sensor_id="lidar_right", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + points=[], + closed=False, + attributes={"my_attr": 5}, + ) + } + ), + }, + ) + + +def test_add_poly3d__just_defaults(): + actual = SceneBuilder.empty().add_poly3d() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar": Lidar()}, + frames={ + 1: Frame( + annotations={ + UUID("6c95543d-0000-4000-0000-000000000000"): Poly3d( + sensor_id="lidar", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + points=[], + closed=False, + attributes={}, + ) + } + ), + }, + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 97a4065a346688dcc88b5e0dbcda35b24e4ec435 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 14:21:37 +0100 Subject: [PATCH 166/190] feat: implement SceneBuilder.add_seg3d() --- raillabel/scene_builder/scene_builder.py | 23 ++++++++++ tests/scene_builder/test_scene_builder.py | 52 +++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/raillabel/scene_builder/scene_builder.py b/raillabel/scene_builder/scene_builder.py index 786ec75..feaa56d 100644 --- a/raillabel/scene_builder/scene_builder.py +++ b/raillabel/scene_builder/scene_builder.py @@ -247,6 +247,29 @@ def add_poly3d( sensor_id=sensor_id, ) + def add_seg3d( + self, + uid: str | UUID | None = None, + frame_id: int = 1, + object_name: str = "person_0001", + sensor_id: str = "lidar", + attributes: dict | None = None, + ) -> SceneBuilder: + """Add a poly3d to the scene.""" + seg3d = Seg3d( + object_id=UUID("ffffffff-ffff-4fff-ffff-ffffffffffff"), + sensor_id=sensor_id, + point_ids=[], + attributes=attributes if attributes is not None else {}, + ) + return self.add_annotation( + annotation=seg3d, + uid=uid, + frame_id=frame_id, + object_name=object_name, + sensor_id=sensor_id, + ) + def _resolve_empty_object_name_or_type( object_type: str | None, object_name: str | None diff --git a/tests/scene_builder/test_scene_builder.py b/tests/scene_builder/test_scene_builder.py index 1df925d..d448aca 100644 --- a/tests/scene_builder/test_scene_builder.py +++ b/tests/scene_builder/test_scene_builder.py @@ -402,5 +402,57 @@ def test_add_poly3d__just_defaults(): ) +def test_add_seg3d(): + actual = SceneBuilder.empty().add_seg3d( + uid=UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"), + frame_id=2, + object_name="person_0001", + sensor_id="lidar_right", + attributes={"my_attr": 5}, + ) + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar_right": Lidar()}, + frames={ + 2: Frame( + annotations={ + UUID("6c95543d-4d4f-43df-a52d-36bf868e09d8"): Seg3d( + sensor_id="lidar_right", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + point_ids=[], + attributes={"my_attr": 5}, + ) + } + ), + }, + ) + + +def test_add_seg3d__just_defaults(): + actual = SceneBuilder.empty().add_seg3d() + assert actual.result == Scene( + metadata=Metadata(schema_version="1.0.0"), + objects={ + UUID("5c59aad4-0000-4000-0000-000000000000"): Object(name="person_0001", type="person") + }, + sensors={"lidar": Lidar()}, + frames={ + 1: Frame( + annotations={ + UUID("6c95543d-0000-4000-0000-000000000000"): Seg3d( + sensor_id="lidar", + object_id=UUID("5c59aad4-0000-4000-0000-000000000000"), + point_ids=[], + attributes={}, + ) + } + ), + }, + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From a059be4eaefe7ddcdb70eb0b225e96eda8b61fc1 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 15:06:51 +0100 Subject: [PATCH 167/190] feat: implement FrameIdFilter for include_frame_ids --- pyproject.toml | 2 ++ raillabel/__init__.py | 3 ++- raillabel/filter/__init__.py | 8 ++++++++ raillabel/filter/_filter_abc.py | 19 ++++++++++++++++++ raillabel/filter/filter.py | 30 +++++++++++++++++++++++++++++ raillabel/filter/frame_id_filter.py | 27 ++++++++++++++++++++++++++ raillabel/scene_builder/__init__.py | 7 +++++++ tests/filter/test_filter_frames.py | 21 ++++++++++++++++++++ 8 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 raillabel/filter/__init__.py create mode 100644 raillabel/filter/_filter_abc.py create mode 100644 raillabel/filter/filter.py create mode 100644 raillabel/filter/frame_id_filter.py create mode 100644 raillabel/scene_builder/__init__.py create mode 100644 tests/filter/test_filter_frames.py diff --git a/pyproject.toml b/pyproject.toml index b802571..a133dbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,8 @@ select = ["ALL"] ignore = [ "A002", # objects from OpenLABEL conflict with a Python builtin, but staying consistent with the domain is more important + "B024", # needed for _FilterAbc + "COM812", # conflicts with ruff formatter "D100", # imo no docstrings are necessary in public modules diff --git a/raillabel/__init__.py b/raillabel/__init__.py index 5b141b9..344419b 100644 --- a/raillabel/__init__.py +++ b/raillabel/__init__.py @@ -4,12 +4,13 @@ from importlib import metadata -from . import format +from . import filter, format from .format import Scene from .load.load import load from .save.save import save __all__ = [ + "filter", "format", "Scene", "load", diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py new file mode 100644 index 0000000..81901fe --- /dev/null +++ b/raillabel/filter/__init__.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +"""Package for the raillabel filter functionality.""" + +from .filter import filter_ +from .frame_id_filter import FrameIdFilter + +__all__ = ["filter_", "FrameIdFilter"] diff --git a/raillabel/filter/_filter_abc.py b/raillabel/filter/_filter_abc.py new file mode 100644 index 0000000..15253d6 --- /dev/null +++ b/raillabel/filter/_filter_abc.py @@ -0,0 +1,19 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from abc import ABC, abstractmethod + +from raillabel.format import Frame + + +class _FilterAbc(ABC): + """Base class of all filter classes regardless of level.""" + + +class _FrameLevelFilter(_FilterAbc): + """Base class of all filter classes applied to the frames.""" + + @abstractmethod + def passes_filter(self, frame_id: int, frame: Frame) -> bool: + """Assess if a frame passes this filter.""" + raise NotImplementedError diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py new file mode 100644 index 0000000..ea65c2a --- /dev/null +++ b/raillabel/filter/filter.py @@ -0,0 +1,30 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from copy import deepcopy + +from raillabel.format import Scene + +from ._filter_abc import _FrameLevelFilter + + +def filter_(scene: Scene, filters: list[_FrameLevelFilter]) -> Scene: + """Return a scene with filters applied to annotations, frame, sensors and objects.""" + filtered_scene = Scene( + metadata=deepcopy(scene.metadata), + sensors=deepcopy(scene.sensors), + objects=deepcopy(scene.objects), + ) + + for frame_id, frame in scene.frames.items(): + frame_passes_filters = True + for filter_ in filters: + if not filter_.passes_filter(frame_id, frame): + frame_passes_filters = False + + if frame_passes_filters: + filtered_scene.frames[frame_id] = deepcopy(frame) + + return filtered_scene diff --git a/raillabel/filter/frame_id_filter.py b/raillabel/filter/frame_id_filter.py new file mode 100644 index 0000000..0abb6a5 --- /dev/null +++ b/raillabel/filter/frame_id_filter.py @@ -0,0 +1,27 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.format import Frame + +from ._filter_abc import _FrameLevelFilter + + +@dataclass +class FrameIdFilter(_FrameLevelFilter): + """Filter for the frames in a scene based on the frame id.""" + + include_frame_ids: set[int] | None = None + + def passes_filter(self, frame_id: int, _: Frame) -> bool: + """Assess if a frame passes this filter.""" + if self.include_frame_ids is None: + return True + + if frame_id in self.include_frame_ids: + return True + + return False diff --git a/raillabel/scene_builder/__init__.py b/raillabel/scene_builder/__init__.py new file mode 100644 index 0000000..1c5c815 --- /dev/null +++ b/raillabel/scene_builder/__init__.py @@ -0,0 +1,7 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 +"""Package for the scene building functionality.""" + +from .scene_builder import SceneBuilder + +__all__ = ["SceneBuilder"] diff --git a/tests/filter/test_filter_frames.py b/tests/filter/test_filter_frames.py new file mode 100644 index 0000000..945e6d4 --- /dev/null +++ b/tests/filter/test_filter_frames.py @@ -0,0 +1,21 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import pytest + +import raillabel +from raillabel.scene_builder import SceneBuilder + + +def test_include(): + scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result + filters = [raillabel.filter.FrameIdFilter(include_frame_ids=[1, 3])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result + + +if __name__ == "__main__": + pytest.main([__file__, "-vv"]) From e75f336a584628fb74b048b9e7953892f575e6cc Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 15:14:26 +0100 Subject: [PATCH 168/190] refactor: limit scope of FrameIdFilter to include frames --- raillabel/filter/__init__.py | 4 ++-- ...ame_id_filter.py => include_frame_id_filter.py} | 14 ++++---------- .../{test_filter_frames.py => test_filter.py} | 4 ++-- 3 files changed, 8 insertions(+), 14 deletions(-) rename raillabel/filter/{frame_id_filter.py => include_frame_id_filter.py} (53%) rename tests/filter/{test_filter_frames.py => test_filter.py} (83%) diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 81901fe..5652f31 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -3,6 +3,6 @@ """Package for the raillabel filter functionality.""" from .filter import filter_ -from .frame_id_filter import FrameIdFilter +from .include_frame_id_filter import IncludeFrameIdFilter -__all__ = ["filter_", "FrameIdFilter"] +__all__ = ["filter_", "IncludeFrameIdFilter"] diff --git a/raillabel/filter/frame_id_filter.py b/raillabel/filter/include_frame_id_filter.py similarity index 53% rename from raillabel/filter/frame_id_filter.py rename to raillabel/filter/include_frame_id_filter.py index 0abb6a5..335eb7b 100644 --- a/raillabel/filter/frame_id_filter.py +++ b/raillabel/filter/include_frame_id_filter.py @@ -11,17 +11,11 @@ @dataclass -class FrameIdFilter(_FrameLevelFilter): - """Filter for the frames in a scene based on the frame id.""" +class IncludeFrameIdFilter(_FrameLevelFilter): + """Filter out all the frames in the scene that do NOT match a list of allowed ids.""" - include_frame_ids: set[int] | None = None + frame_ids: set[int] | list[int] def passes_filter(self, frame_id: int, _: Frame) -> bool: """Assess if a frame passes this filter.""" - if self.include_frame_ids is None: - return True - - if frame_id in self.include_frame_ids: - return True - - return False + return frame_id in self.frame_ids diff --git a/tests/filter/test_filter_frames.py b/tests/filter/test_filter.py similarity index 83% rename from tests/filter/test_filter_frames.py rename to tests/filter/test_filter.py index 945e6d4..75c79c7 100644 --- a/tests/filter/test_filter_frames.py +++ b/tests/filter/test_filter.py @@ -9,9 +9,9 @@ from raillabel.scene_builder import SceneBuilder -def test_include(): +def test_include_frames(): scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result - filters = [raillabel.filter.FrameIdFilter(include_frame_ids=[1, 3])] + filters = [raillabel.filter.IncludeFrameIdFilter([1, 3])] actual = raillabel.filter.filter_(scene, filters) assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result From fa8c96c516cbcd10c26a862e741e4ea2a1de17c1 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 15:17:14 +0100 Subject: [PATCH 169/190] feat: implement ExcludeFrameIdFilter --- raillabel/filter/__init__.py | 3 ++- raillabel/filter/exclude_frame_id_filter.py | 21 +++++++++++++++++++++ tests/filter/test_filter.py | 8 ++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 raillabel/filter/exclude_frame_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 5652f31..a338d72 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 """Package for the raillabel filter functionality.""" +from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_frame_id_filter import IncludeFrameIdFilter -__all__ = ["filter_", "IncludeFrameIdFilter"] +__all__ = ["filter_", "IncludeFrameIdFilter", "ExcludeFrameIdFilter"] diff --git a/raillabel/filter/exclude_frame_id_filter.py b/raillabel/filter/exclude_frame_id_filter.py new file mode 100644 index 0000000..d89a367 --- /dev/null +++ b/raillabel/filter/exclude_frame_id_filter.py @@ -0,0 +1,21 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass + +from raillabel.format import Frame + +from ._filter_abc import _FrameLevelFilter + + +@dataclass +class ExcludeFrameIdFilter(_FrameLevelFilter): + """Filter out all the frames in the scene that do match a list of disallowed ids.""" + + frame_ids: set[int] | list[int] + + def passes_filter(self, frame_id: int, _: Frame) -> bool: + """Assess if a frame passes this filter.""" + return frame_id not in self.frame_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 75c79c7..51e6045 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -17,5 +17,13 @@ def test_include_frames(): assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result +def test_exclude_frames(): + scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result + filters = [raillabel.filter.ExcludeFrameIdFilter([2])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 737e31bb7bbbf290bab29a99ee140cb90e36f48d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 16:08:29 +0100 Subject: [PATCH 170/190] feat: implement StartTimeFilter --- raillabel/filter/__init__.py | 3 ++- raillabel/filter/start_time_filter.py | 25 +++++++++++++++++++++++++ tests/filter/test_filter.py | 8 ++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 raillabel/filter/start_time_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index a338d72..3384a51 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -5,5 +5,6 @@ from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_frame_id_filter import IncludeFrameIdFilter +from .start_time_filter import StartTimeFilter -__all__ = ["filter_", "IncludeFrameIdFilter", "ExcludeFrameIdFilter"] +__all__ = ["filter_", "IncludeFrameIdFilter", "ExcludeFrameIdFilter", "StartTimeFilter"] diff --git a/raillabel/filter/start_time_filter.py b/raillabel/filter/start_time_filter.py new file mode 100644 index 0000000..072e829 --- /dev/null +++ b/raillabel/filter/start_time_filter.py @@ -0,0 +1,25 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from decimal import Decimal + +from raillabel.format import Frame + +from ._filter_abc import _FrameLevelFilter + + +@dataclass +class StartTimeFilter(_FrameLevelFilter): + """Filter out all the frames in the scene with timestamps lower than the start_time.""" + + start_time: float | Decimal + + def passes_filter(self, _: int, frame: Frame) -> bool: + """Assess if a frame passes this filter.""" + if frame.timestamp is not None: + return frame.timestamp > self.start_time + + return True diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 51e6045..59deb27 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -25,5 +25,13 @@ def test_exclude_frames(): assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result +def test_start_time(): + scene = SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).add_frame(3, 300).result + filters = [raillabel.filter.StartTimeFilter(150)] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_frame(2, 200).add_frame(3, 300).result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 82327b5121aaa40dbf36efc94537cbea1e590310 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 16:11:31 +0100 Subject: [PATCH 171/190] feat: implement EndTimeFilter --- raillabel/filter/__init__.py | 9 ++++++++- raillabel/filter/end_time_filter.py | 25 +++++++++++++++++++++++++ raillabel/filter/start_time_filter.py | 2 +- tests/filter/test_filter.py | 8 ++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 raillabel/filter/end_time_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 3384a51..74f02a3 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -2,9 +2,16 @@ # SPDX-License-Identifier: Apache-2.0 """Package for the raillabel filter functionality.""" +from .end_time_filter import EndTimeFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_frame_id_filter import IncludeFrameIdFilter from .start_time_filter import StartTimeFilter -__all__ = ["filter_", "IncludeFrameIdFilter", "ExcludeFrameIdFilter", "StartTimeFilter"] +__all__ = [ + "filter_", + "IncludeFrameIdFilter", + "ExcludeFrameIdFilter", + "StartTimeFilter", + "EndTimeFilter", +] diff --git a/raillabel/filter/end_time_filter.py b/raillabel/filter/end_time_filter.py new file mode 100644 index 0000000..2b59fc7 --- /dev/null +++ b/raillabel/filter/end_time_filter.py @@ -0,0 +1,25 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from decimal import Decimal + +from raillabel.format import Frame + +from ._filter_abc import _FrameLevelFilter + + +@dataclass +class EndTimeFilter(_FrameLevelFilter): + """Filter out all the frames in the scene with timestamps higher than the end_time.""" + + end_time: float | Decimal + + def passes_filter(self, _: int, frame: Frame) -> bool: + """Assess if a frame passes this filter.""" + if frame.timestamp is not None: + return frame.timestamp <= self.end_time + + return True diff --git a/raillabel/filter/start_time_filter.py b/raillabel/filter/start_time_filter.py index 072e829..8ce8c7e 100644 --- a/raillabel/filter/start_time_filter.py +++ b/raillabel/filter/start_time_filter.py @@ -20,6 +20,6 @@ class StartTimeFilter(_FrameLevelFilter): def passes_filter(self, _: int, frame: Frame) -> bool: """Assess if a frame passes this filter.""" if frame.timestamp is not None: - return frame.timestamp > self.start_time + return frame.timestamp >= self.start_time return True diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 59deb27..496cb8e 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -33,5 +33,13 @@ def test_start_time(): assert actual == SceneBuilder.empty().add_frame(2, 200).add_frame(3, 300).result +def test_start_time(): + scene = SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).add_frame(3, 300).result + filters = [raillabel.filter.EndTimeFilter(250)] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 0453c81d9c9e5f0a53aeb7faead03488c7e2bab5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 16:52:32 +0100 Subject: [PATCH 172/190] feat: implement IncludeAnnotationIdFilter --- raillabel/filter/__init__.py | 2 + raillabel/filter/_filter_abc.py | 15 ++++- raillabel/filter/filter.py | 57 ++++++++++++++++--- .../filter/include_annotation_id_filter.py | 22 +++++++ tests/filter/test_filter.py | 35 +++++++++++- 5 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 raillabel/filter/include_annotation_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 74f02a3..260c5a9 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -5,6 +5,7 @@ from .end_time_filter import EndTimeFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ +from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_frame_id_filter import IncludeFrameIdFilter from .start_time_filter import StartTimeFilter @@ -14,4 +15,5 @@ "ExcludeFrameIdFilter", "StartTimeFilter", "EndTimeFilter", + "IncludeAnnotationIdFilter", ] diff --git a/raillabel/filter/_filter_abc.py b/raillabel/filter/_filter_abc.py index 15253d6..70582aa 100644 --- a/raillabel/filter/_filter_abc.py +++ b/raillabel/filter/_filter_abc.py @@ -1,15 +1,28 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations from abc import ABC, abstractmethod +from uuid import UUID -from raillabel.format import Frame +from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Seg3d class _FilterAbc(ABC): """Base class of all filter classes regardless of level.""" +class _AnnotationLevelFilter(_FilterAbc): + """Base class of all filter classes applied to the annotations.""" + + @abstractmethod + def passes_filter( + self, annotation_id: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d + ) -> bool: + """Assess if an annotation passes this filter.""" + raise NotImplementedError + + class _FrameLevelFilter(_FilterAbc): """Base class of all filter classes applied to the frames.""" diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index ea65c2a..c8bce4a 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -4,13 +4,14 @@ from __future__ import annotations from copy import deepcopy +from uuid import UUID -from raillabel.format import Scene +from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Scene, Seg3d -from ._filter_abc import _FrameLevelFilter +from ._filter_abc import _AnnotationLevelFilter, _FilterAbc, _FrameLevelFilter -def filter_(scene: Scene, filters: list[_FrameLevelFilter]) -> Scene: +def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: """Return a scene with filters applied to annotations, frame, sensors and objects.""" filtered_scene = Scene( metadata=deepcopy(scene.metadata), @@ -18,13 +19,51 @@ def filter_(scene: Scene, filters: list[_FrameLevelFilter]) -> Scene: objects=deepcopy(scene.objects), ) + frame_filters, annotation_filters = _separate_filters(filters) + for frame_id, frame in scene.frames.items(): - frame_passes_filters = True - for filter_ in filters: - if not filter_.passes_filter(frame_id, frame): - frame_passes_filters = False + if not _frame_passes_all_filters(frame_id, frame, frame_filters): + continue + + filtered_frame = Frame( + timestamp=deepcopy(frame.timestamp), + sensors=deepcopy(frame.sensors), + frame_data=deepcopy(frame.frame_data), + ) + + for annotation_id, annotation in frame.annotations.items(): + if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): + filtered_frame.annotations[annotation_id] = deepcopy(annotation) - if frame_passes_filters: - filtered_scene.frames[frame_id] = deepcopy(frame) + filtered_scene.frames[frame_id] = filtered_frame return filtered_scene + + +def _separate_filters( + all_filters: list[_FilterAbc], +) -> tuple[list[_FrameLevelFilter], list[_AnnotationLevelFilter]]: + frame_filters = [] + annotation_filters = [] + for filter_ in all_filters: + if isinstance(filter_, _FrameLevelFilter): + frame_filters.append(filter_) + + if isinstance(filter_, _AnnotationLevelFilter): + annotation_filters.append(filter_) + + return frame_filters, annotation_filters + + +def _frame_passes_all_filters( + frame_id: int, frame: Frame, frame_filters: list[_FrameLevelFilter] +) -> bool: + return all(filter_.passes_filter(frame_id, frame) for filter_ in frame_filters) + + +def _annotation_passes_all_filters( + annotation_id: UUID, + annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, + annotation_filters: list[_AnnotationLevelFilter], +) -> bool: + return all(filter_.passes_filter(annotation_id, annotation) for filter_ in annotation_filters) diff --git a/raillabel/filter/include_annotation_id_filter.py b/raillabel/filter/include_annotation_id_filter.py new file mode 100644 index 0000000..dc8f311 --- /dev/null +++ b/raillabel/filter/include_annotation_id_filter.py @@ -0,0 +1,22 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeAnnotationIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have the correct ids.""" + + annotation_ids: set[UUID] | list[UUID] + + def passes_filter(self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + return annotation_id in self.annotation_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 496cb8e..4a4ccc3 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -3,13 +3,15 @@ from __future__ import annotations +from uuid import UUID + import pytest import raillabel from raillabel.scene_builder import SceneBuilder -def test_include_frames(): +def test_include_frame_ids(): scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result filters = [raillabel.filter.IncludeFrameIdFilter([1, 3])] @@ -17,7 +19,7 @@ def test_include_frames(): assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result -def test_exclude_frames(): +def test_exclude_frame_ids(): scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result filters = [raillabel.filter.ExcludeFrameIdFilter([2])] @@ -33,7 +35,7 @@ def test_start_time(): assert actual == SceneBuilder.empty().add_frame(2, 200).add_frame(3, 300).result -def test_start_time(): +def test_end_time(): scene = SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).add_frame(3, 300).result filters = [raillabel.filter.EndTimeFilter(250)] @@ -41,5 +43,32 @@ def test_start_time(): assert actual == SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).result +def test_include_annotation_ids(): + scene = ( + SceneBuilder.empty() + .add_bbox(uid="6c95543d-0000-4000-0000-000000000000") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000001") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000002") + .result + ) + filters = [ + raillabel.filter.IncludeAnnotationIdFilter( + [ + UUID("6c95543d-0000-4000-0000-000000000000"), + UUID("6c95543d-0000-4000-0000-000000000002"), + ] + ) + ] + + actual = raillabel.filter.filter_(scene, filters) + assert ( + actual + == SceneBuilder.empty() + .add_bbox(uid="6c95543d-0000-4000-0000-000000000000") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000002") + .result + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 2cc8bd329e4fbcf7b9638228ee87f13002127bd9 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 16:57:42 +0100 Subject: [PATCH 173/190] feat: implement ExcludeAnnotationIdFilter --- raillabel/filter/__init__.py | 2 ++ .../filter/exclude_annotation_id_filter.py | 22 +++++++++++++++++++ tests/filter/test_filter.py | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 raillabel/filter/exclude_annotation_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 260c5a9..e5a1aee 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -3,6 +3,7 @@ """Package for the raillabel filter functionality.""" from .end_time_filter import EndTimeFilter +from .exclude_annotation_id_filter import ExcludeAnnotationIdFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter @@ -16,4 +17,5 @@ "StartTimeFilter", "EndTimeFilter", "IncludeAnnotationIdFilter", + "ExcludeAnnotationIdFilter", ] diff --git a/raillabel/filter/exclude_annotation_id_filter.py b/raillabel/filter/exclude_annotation_id_filter.py new file mode 100644 index 0000000..7973e50 --- /dev/null +++ b/raillabel/filter/exclude_annotation_id_filter.py @@ -0,0 +1,22 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeAnnotationIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do have disallowed ids.""" + + annotation_ids: set[UUID] | list[UUID] + + def passes_filter(self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + return annotation_id not in self.annotation_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 4a4ccc3..df8f386 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -70,5 +70,27 @@ def test_include_annotation_ids(): ) +def test_exclude_annotation_ids(): + scene = ( + SceneBuilder.empty() + .add_bbox(uid="6c95543d-0000-4000-0000-000000000000") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000001") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000002") + .result + ) + filters = [ + raillabel.filter.ExcludeAnnotationIdFilter([UUID("6c95543d-0000-4000-0000-000000000001")]) + ] + + actual = raillabel.filter.filter_(scene, filters) + assert ( + actual + == SceneBuilder.empty() + .add_bbox(uid="6c95543d-0000-4000-0000-000000000000") + .add_bbox(uid="6c95543d-0000-4000-0000-000000000002") + .result + ) + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 02db99a56bf36497024d028fa56dc9ddbe1a9cdb Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 17:15:49 +0100 Subject: [PATCH 174/190] feat: implement IncludeAnnotationTypeFilter --- raillabel/filter/__init__.py | 2 + raillabel/filter/filter.py | 28 ++++++++--- .../filter/include_annotation_type_filter.py | 46 +++++++++++++++++++ tests/filter/test_filter.py | 8 ++++ 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 raillabel/filter/include_annotation_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index e5a1aee..bc7e92c 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -7,6 +7,7 @@ from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter +from .include_annotation_type_filter import IncludeAnnotationTypeFilter from .include_frame_id_filter import IncludeFrameIdFilter from .start_time_filter import StartTimeFilter @@ -18,4 +19,5 @@ "EndTimeFilter", "IncludeAnnotationIdFilter", "ExcludeAnnotationIdFilter", + "IncludeAnnotationTypeFilter", ] diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index c8bce4a..7ad3edd 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -13,11 +13,10 @@ def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: """Return a scene with filters applied to annotations, frame, sensors and objects.""" - filtered_scene = Scene( - metadata=deepcopy(scene.metadata), - sensors=deepcopy(scene.sensors), - objects=deepcopy(scene.objects), - ) + filtered_scene = Scene(metadata=deepcopy(scene.metadata)) + + used_sensor_ids = set() + used_object_ids = set() frame_filters, annotation_filters = _separate_filters(filters) @@ -32,11 +31,26 @@ def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: ) for annotation_id, annotation in frame.annotations.items(): - if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): - filtered_frame.annotations[annotation_id] = deepcopy(annotation) + if not _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): + continue + + filtered_frame.annotations[annotation_id] = deepcopy(annotation) + used_sensor_ids.add(annotation.sensor_id) + used_object_ids.add(annotation.object_id) filtered_scene.frames[frame_id] = filtered_frame + filtered_scene.sensors = { + sensor_id: deepcopy(sensor) + for sensor_id, sensor in scene.sensors.items() + if sensor_id in used_sensor_ids + } + filtered_scene.objects = { + object_id: deepcopy(object_) + for object_id, object_ in scene.objects.items() + if object_id in used_object_ids + } + return filtered_scene diff --git a/raillabel/filter/include_annotation_type_filter.py b/raillabel/filter/include_annotation_type_filter.py new file mode 100644 index 0000000..5e4908e --- /dev/null +++ b/raillabel/filter/include_annotation_type_filter.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeAnnotationTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have the type (like bbox or cuboid).""" + + annotation_types: ( + set[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] + | list[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] + ) + + def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + annotation_type_str = None + + if isinstance(annotation, Bbox): + annotation_type_str = "bbox" + + elif isinstance(annotation, Cuboid): + annotation_type_str = "cuboid" + + elif isinstance(annotation, Poly2d): + annotation_type_str = "poly2d" + + elif isinstance(annotation, Poly3d): + annotation_type_str = "poly3d" + + elif isinstance(annotation, Seg3d): + annotation_type_str = "seg3d" + + else: + raise TypeError + + return annotation_type_str in self.annotation_types diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index df8f386..8da2db2 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -92,5 +92,13 @@ def test_exclude_annotation_ids(): ) +def test_include_annotation_type(): + scene = SceneBuilder.empty().add_bbox().add_cuboid().result + filters = [raillabel.filter.IncludeAnnotationTypeFilter(["bbox"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox().result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From f41ce29774d0d873459e036e06678965f9547483 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 17:33:13 +0100 Subject: [PATCH 175/190] refactor: filter_ --- raillabel/filter/filter.py | 108 ++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index 7ad3edd..174ef23 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -6,50 +6,33 @@ from copy import deepcopy from uuid import UUID -from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Scene, Seg3d +from raillabel.format import ( + Bbox, + Camera, + Cuboid, + Frame, + GpsImu, + Lidar, + Object, + OtherSensor, + Poly2d, + Poly3d, + Radar, + Scene, + Seg3d, +) from ._filter_abc import _AnnotationLevelFilter, _FilterAbc, _FrameLevelFilter def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: """Return a scene with filters applied to annotations, frame, sensors and objects.""" - filtered_scene = Scene(metadata=deepcopy(scene.metadata)) - - used_sensor_ids = set() - used_object_ids = set() - frame_filters, annotation_filters = _separate_filters(filters) - for frame_id, frame in scene.frames.items(): - if not _frame_passes_all_filters(frame_id, frame, frame_filters): - continue - - filtered_frame = Frame( - timestamp=deepcopy(frame.timestamp), - sensors=deepcopy(frame.sensors), - frame_data=deepcopy(frame.frame_data), - ) - - for annotation_id, annotation in frame.annotations.items(): - if not _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): - continue - - filtered_frame.annotations[annotation_id] = deepcopy(annotation) - used_sensor_ids.add(annotation.sensor_id) - used_object_ids.add(annotation.object_id) - - filtered_scene.frames[frame_id] = filtered_frame - - filtered_scene.sensors = { - sensor_id: deepcopy(sensor) - for sensor_id, sensor in scene.sensors.items() - if sensor_id in used_sensor_ids - } - filtered_scene.objects = { - object_id: deepcopy(object_) - for object_id, object_ in scene.objects.items() - if object_id in used_object_ids - } + filtered_scene = Scene(metadata=deepcopy(scene.metadata)) + filtered_scene.frames = _filter_frames(scene.frames, frame_filters, annotation_filters) + filtered_scene.sensors = _get_used_sensors(scene, filtered_scene) + filtered_scene.objects = _get_used_objects(scene, filtered_scene) return filtered_scene @@ -69,6 +52,37 @@ def _separate_filters( return frame_filters, annotation_filters +def _filter_frames( + frames: dict[int, Frame], + frame_filters: list[_FrameLevelFilter], + annotation_filters: list[_AnnotationLevelFilter], +) -> dict[int, Frame]: + filtered_frames = {} + + for frame_id, frame in frames.items(): + if _frame_passes_all_filters(frame_id, frame, frame_filters): + filtered_frames[frame_id] = Frame( + timestamp=deepcopy(frame.timestamp), + sensors=deepcopy(frame.sensors), + frame_data=deepcopy(frame.frame_data), + annotations=_filter_annotations(frame, annotation_filters), + ) + + return filtered_frames + + +def _filter_annotations( + frame: Frame, annotation_filters: list[_AnnotationLevelFilter] +) -> dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d]: + annotations = {} + + for annotation_id, annotation in frame.annotations.items(): + if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): + annotations[annotation_id] = deepcopy(annotation) + + return annotations + + def _frame_passes_all_filters( frame_id: int, frame: Frame, frame_filters: list[_FrameLevelFilter] ) -> bool: @@ -81,3 +95,25 @@ def _annotation_passes_all_filters( annotation_filters: list[_AnnotationLevelFilter], ) -> bool: return all(filter_.passes_filter(annotation_id, annotation) for filter_ in annotation_filters) + + +def _get_used_sensors( + scene: Scene, filtered_scene: Scene +) -> dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor]: + used_sensors = {} + for frame in filtered_scene.frames.values(): + for annotation in frame.annotations.values(): + if annotation.sensor_id not in used_sensors: + used_sensors[annotation.sensor_id] = deepcopy(scene.sensors[annotation.sensor_id]) + + return used_sensors + + +def _get_used_objects(scene: Scene, filtered_scene: Scene) -> dict[UUID, Object]: + used_objects = {} + for frame in filtered_scene.frames.values(): + for annotation in frame.annotations.values(): + if annotation.object_id not in used_objects: + used_objects[annotation.object_id] = deepcopy(scene.objects[annotation.object_id]) + + return used_objects From fd3cc1f99de11276b980e87f55a1ead10765518d Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 17:36:34 +0100 Subject: [PATCH 176/190] feat: implement ExcludeAnnotationTypeFilter --- raillabel/filter/__init__.py | 2 + .../filter/exclude_annotation_type_filter.py | 46 +++++++++++++++++++ tests/filter/test_filter.py | 8 ++++ 3 files changed, 56 insertions(+) create mode 100644 raillabel/filter/exclude_annotation_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index bc7e92c..4d90bb1 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -4,6 +4,7 @@ from .end_time_filter import EndTimeFilter from .exclude_annotation_id_filter import ExcludeAnnotationIdFilter +from .exclude_annotation_type_filter import ExcludeAnnotationTypeFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter @@ -20,4 +21,5 @@ "IncludeAnnotationIdFilter", "ExcludeAnnotationIdFilter", "IncludeAnnotationTypeFilter", + "ExcludeAnnotationTypeFilter", ] diff --git a/raillabel/filter/exclude_annotation_type_filter.py b/raillabel/filter/exclude_annotation_type_filter.py new file mode 100644 index 0000000..89e2357 --- /dev/null +++ b/raillabel/filter/exclude_annotation_type_filter.py @@ -0,0 +1,46 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeAnnotationTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do have the type (like bbox or cuboid).""" + + annotation_types: ( + set[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] + | list[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] + ) + + def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + annotation_type_str = None + + if isinstance(annotation, Bbox): + annotation_type_str = "bbox" + + elif isinstance(annotation, Cuboid): + annotation_type_str = "cuboid" + + elif isinstance(annotation, Poly2d): + annotation_type_str = "poly2d" + + elif isinstance(annotation, Poly3d): + annotation_type_str = "poly3d" + + elif isinstance(annotation, Seg3d): + annotation_type_str = "seg3d" + + else: + raise TypeError + + return annotation_type_str not in self.annotation_types diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 8da2db2..8671a42 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -100,5 +100,13 @@ def test_include_annotation_type(): assert actual == SceneBuilder.empty().add_bbox().result +def test_exclude_annotation_type(): + scene = SceneBuilder.empty().add_bbox().add_cuboid().result + filters = [raillabel.filter.ExcludeAnnotationTypeFilter(["cuboid"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox().result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From c565dfca83169881d78f237a42d2d9d160e487e9 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 17:47:29 +0100 Subject: [PATCH 177/190] feat: implement IncludeObjectIdFilter --- raillabel/filter/__init__.py | 2 ++ raillabel/filter/include_object_id_filter.py | 22 ++++++++++++++++++++ tests/filter/test_filter.py | 15 +++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 raillabel/filter/include_object_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 4d90bb1..80c61f1 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -10,6 +10,7 @@ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter from .include_frame_id_filter import IncludeFrameIdFilter +from .include_object_id_filter import IncludeObjectIdFilter from .start_time_filter import StartTimeFilter __all__ = [ @@ -22,4 +23,5 @@ "ExcludeAnnotationIdFilter", "IncludeAnnotationTypeFilter", "ExcludeAnnotationTypeFilter", + "IncludeObjectIdFilter", ] diff --git a/raillabel/filter/include_object_id_filter.py b/raillabel/filter/include_object_id_filter.py new file mode 100644 index 0000000..96aa7e5 --- /dev/null +++ b/raillabel/filter/include_object_id_filter.py @@ -0,0 +1,22 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeObjectIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have matching object ids.""" + + object_ids: list[UUID] + + def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + return annotation.object_id in self.object_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 8671a42..1b6d5f0 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -108,5 +108,20 @@ def test_exclude_annotation_type(): assert actual == SceneBuilder.empty().add_bbox().result +def test_include_object_ids(): + scene = ( + SceneBuilder.empty() + .add_bbox(object_name="person_0001") + .add_cuboid(object_name="train_0001") + .result + ) + filters = [ + raillabel.filter.IncludeObjectIdFilter([UUID("5c59aad4-0000-4000-0000-000000000000")]) + ] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From c61ba1a355df6d6cfffe4b916e62f68e732eb71b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 17:49:42 +0100 Subject: [PATCH 178/190] feat: implement ExcludeObjectIdFilter --- raillabel/filter/__init__.py | 2 ++ raillabel/filter/exclude_object_id_filter.py | 22 ++++++++++++++++++++ tests/filter/test_filter.py | 15 +++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 raillabel/filter/exclude_object_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 80c61f1..370f3f5 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -6,6 +6,7 @@ from .exclude_annotation_id_filter import ExcludeAnnotationIdFilter from .exclude_annotation_type_filter import ExcludeAnnotationTypeFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter +from .exclude_object_id_filter import ExcludeObjectIdFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter @@ -24,4 +25,5 @@ "IncludeAnnotationTypeFilter", "ExcludeAnnotationTypeFilter", "IncludeObjectIdFilter", + "ExcludeObjectIdFilter", ] diff --git a/raillabel/filter/exclude_object_id_filter.py b/raillabel/filter/exclude_object_id_filter.py new file mode 100644 index 0000000..9ed7963 --- /dev/null +++ b/raillabel/filter/exclude_object_id_filter.py @@ -0,0 +1,22 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeObjectIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do have matching object ids.""" + + object_ids: list[UUID] + + def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + """Assess if an annotation passes this filter.""" + return annotation.object_id not in self.object_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 1b6d5f0..f196dcf 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -123,5 +123,20 @@ def test_include_object_ids(): assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result +def test_exclude_object_ids(): + scene = ( + SceneBuilder.empty() + .add_bbox(object_name="person_0001") + .add_cuboid(object_name="train_0001") + .result + ) + filters = [ + raillabel.filter.ExcludeObjectIdFilter([UUID("5c59aad4-0000-4000-0000-000000000001")]) + ] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From a8b56674030780c359c3536ec71c6c641e94d8c6 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 18:03:17 +0100 Subject: [PATCH 179/190] feat: implement IncludeObjectTypeFilter --- raillabel/filter/__init__.py | 2 ++ raillabel/filter/_filter_abc.py | 4 ++-- .../filter/exclude_annotation_id_filter.py | 6 +++-- .../filter/exclude_annotation_type_filter.py | 6 +++-- raillabel/filter/exclude_object_id_filter.py | 6 +++-- raillabel/filter/filter.py | 17 +++++++------ .../filter/include_annotation_id_filter.py | 6 +++-- .../filter/include_annotation_type_filter.py | 6 +++-- raillabel/filter/include_object_id_filter.py | 6 +++-- .../filter/include_object_type_filter.py | 24 +++++++++++++++++++ tests/filter/test_filter.py | 13 ++++++++++ 11 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 raillabel/filter/include_object_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 370f3f5..8fb800a 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -12,6 +12,7 @@ from .include_annotation_type_filter import IncludeAnnotationTypeFilter from .include_frame_id_filter import IncludeFrameIdFilter from .include_object_id_filter import IncludeObjectIdFilter +from .include_object_type_filter import IncludeObjectTypeFilter from .start_time_filter import StartTimeFilter __all__ = [ @@ -26,4 +27,5 @@ "ExcludeAnnotationTypeFilter", "IncludeObjectIdFilter", "ExcludeObjectIdFilter", + "IncludeObjectTypeFilter", ] diff --git a/raillabel/filter/_filter_abc.py b/raillabel/filter/_filter_abc.py index 70582aa..cb7b1b7 100644 --- a/raillabel/filter/_filter_abc.py +++ b/raillabel/filter/_filter_abc.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from uuid import UUID -from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Scene, Seg3d class _FilterAbc(ABC): @@ -17,7 +17,7 @@ class _AnnotationLevelFilter(_FilterAbc): @abstractmethod def passes_filter( - self, annotation_id: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d + self, annotation_id: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, scene: Scene ) -> bool: """Assess if an annotation passes this filter.""" raise NotImplementedError diff --git a/raillabel/filter/exclude_annotation_id_filter.py b/raillabel/filter/exclude_annotation_id_filter.py index 7973e50..a41830f 100644 --- a/raillabel/filter/exclude_annotation_id_filter.py +++ b/raillabel/filter/exclude_annotation_id_filter.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -17,6 +17,8 @@ class ExcludeAnnotationIdFilter(_AnnotationLevelFilter): annotation_ids: set[UUID] | list[UUID] - def passes_filter(self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" return annotation_id not in self.annotation_ids diff --git a/raillabel/filter/exclude_annotation_type_filter.py b/raillabel/filter/exclude_annotation_type_filter.py index 89e2357..80180dd 100644 --- a/raillabel/filter/exclude_annotation_type_filter.py +++ b/raillabel/filter/exclude_annotation_type_filter.py @@ -7,7 +7,7 @@ from typing import Literal from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -21,7 +21,9 @@ class ExcludeAnnotationTypeFilter(_AnnotationLevelFilter): | list[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] ) - def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" annotation_type_str = None diff --git a/raillabel/filter/exclude_object_id_filter.py b/raillabel/filter/exclude_object_id_filter.py index 9ed7963..f840d8e 100644 --- a/raillabel/filter/exclude_object_id_filter.py +++ b/raillabel/filter/exclude_object_id_filter.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -17,6 +17,8 @@ class ExcludeObjectIdFilter(_AnnotationLevelFilter): object_ids: list[UUID] - def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" return annotation.object_id not in self.object_ids diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py index 174ef23..7fa2c9a 100644 --- a/raillabel/filter/filter.py +++ b/raillabel/filter/filter.py @@ -30,7 +30,7 @@ def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: frame_filters, annotation_filters = _separate_filters(filters) filtered_scene = Scene(metadata=deepcopy(scene.metadata)) - filtered_scene.frames = _filter_frames(scene.frames, frame_filters, annotation_filters) + filtered_scene.frames = _filter_frames(scene, frame_filters, annotation_filters) filtered_scene.sensors = _get_used_sensors(scene, filtered_scene) filtered_scene.objects = _get_used_objects(scene, filtered_scene) @@ -53,31 +53,31 @@ def _separate_filters( def _filter_frames( - frames: dict[int, Frame], + scene: Scene, frame_filters: list[_FrameLevelFilter], annotation_filters: list[_AnnotationLevelFilter], ) -> dict[int, Frame]: filtered_frames = {} - for frame_id, frame in frames.items(): + for frame_id, frame in scene.frames.items(): if _frame_passes_all_filters(frame_id, frame, frame_filters): filtered_frames[frame_id] = Frame( timestamp=deepcopy(frame.timestamp), sensors=deepcopy(frame.sensors), frame_data=deepcopy(frame.frame_data), - annotations=_filter_annotations(frame, annotation_filters), + annotations=_filter_annotations(frame, annotation_filters, scene), ) return filtered_frames def _filter_annotations( - frame: Frame, annotation_filters: list[_AnnotationLevelFilter] + frame: Frame, annotation_filters: list[_AnnotationLevelFilter], scene: Scene ) -> dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d]: annotations = {} for annotation_id, annotation in frame.annotations.items(): - if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters): + if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters, scene): annotations[annotation_id] = deepcopy(annotation) return annotations @@ -93,8 +93,11 @@ def _annotation_passes_all_filters( annotation_id: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, annotation_filters: list[_AnnotationLevelFilter], + scene: Scene, ) -> bool: - return all(filter_.passes_filter(annotation_id, annotation) for filter_ in annotation_filters) + return all( + filter_.passes_filter(annotation_id, annotation, scene) for filter_ in annotation_filters + ) def _get_used_sensors( diff --git a/raillabel/filter/include_annotation_id_filter.py b/raillabel/filter/include_annotation_id_filter.py index dc8f311..7cac89b 100644 --- a/raillabel/filter/include_annotation_id_filter.py +++ b/raillabel/filter/include_annotation_id_filter.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -17,6 +17,8 @@ class IncludeAnnotationIdFilter(_AnnotationLevelFilter): annotation_ids: set[UUID] | list[UUID] - def passes_filter(self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, annotation_id: UUID, _: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" return annotation_id in self.annotation_ids diff --git a/raillabel/filter/include_annotation_type_filter.py b/raillabel/filter/include_annotation_type_filter.py index 5e4908e..300be80 100644 --- a/raillabel/filter/include_annotation_type_filter.py +++ b/raillabel/filter/include_annotation_type_filter.py @@ -7,7 +7,7 @@ from typing import Literal from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -21,7 +21,9 @@ class IncludeAnnotationTypeFilter(_AnnotationLevelFilter): | list[Literal["bbox", "cuboid", "poly2d", "poly3d", "seg3d"]] ) - def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" annotation_type_str = None diff --git a/raillabel/filter/include_object_id_filter.py b/raillabel/filter/include_object_id_filter.py index 96aa7e5..e4bccb9 100644 --- a/raillabel/filter/include_object_id_filter.py +++ b/raillabel/filter/include_object_id_filter.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from uuid import UUID -from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Seg3d +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d from ._filter_abc import _AnnotationLevelFilter @@ -17,6 +17,8 @@ class IncludeObjectIdFilter(_AnnotationLevelFilter): object_ids: list[UUID] - def passes_filter(self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d) -> bool: + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: """Assess if an annotation passes this filter.""" return annotation.object_id in self.object_ids diff --git a/raillabel/filter/include_object_type_filter.py b/raillabel/filter/include_object_type_filter.py new file mode 100644 index 0000000..aa1a352 --- /dev/null +++ b/raillabel/filter/include_object_type_filter.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeObjectTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT match the type (like 'person').""" + + object_types: list[str] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, scene: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return scene.objects[annotation.object_id].type in self.object_types diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index f196dcf..5a20f43 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -138,5 +138,18 @@ def test_exclude_object_ids(): assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result +def test_include_object_types(): + scene = ( + SceneBuilder.empty() + .add_bbox(object_name="person_0001") + .add_cuboid(object_name="train_0001") + .result + ) + filters = [raillabel.filter.IncludeObjectTypeFilter(["person"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From ad8bb5874936cf0ec9180d37e3045da1d35cffb5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 16 Nov 2024 18:07:26 +0100 Subject: [PATCH 180/190] feat: implement ExcludeObjectTypeFilter --- raillabel/filter/__init__.py | 2 ++ .../filter/exclude_object_type_filter.py | 24 +++++++++++++++++++ tests/filter/test_filter.py | 13 ++++++++++ 3 files changed, 39 insertions(+) create mode 100644 raillabel/filter/exclude_object_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 8fb800a..1d2d5a4 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -7,6 +7,7 @@ from .exclude_annotation_type_filter import ExcludeAnnotationTypeFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .exclude_object_id_filter import ExcludeObjectIdFilter +from .exclude_object_type_filter import ExcludeObjectTypeFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter @@ -28,4 +29,5 @@ "IncludeObjectIdFilter", "ExcludeObjectIdFilter", "IncludeObjectTypeFilter", + "ExcludeObjectTypeFilter", ] diff --git a/raillabel/filter/exclude_object_type_filter.py b/raillabel/filter/exclude_object_type_filter.py new file mode 100644 index 0000000..bdd26d9 --- /dev/null +++ b/raillabel/filter/exclude_object_type_filter.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeObjectTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do match the type (like 'person').""" + + object_types: list[str] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, scene: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return scene.objects[annotation.object_id].type not in self.object_types diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 5a20f43..3e73045 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -151,5 +151,18 @@ def test_include_object_types(): assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result +def test_exclude_object_types(): + scene = ( + SceneBuilder.empty() + .add_bbox(object_name="person_0001") + .add_cuboid(object_name="train_0001") + .result + ) + filters = [raillabel.filter.ExcludeObjectTypeFilter(["train"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 17fb336e3a9686d73b13921f5f7ac8662f1cdd1d Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:01:22 +0100 Subject: [PATCH 181/190] feat: implement extra field parsing for metadata --- raillabel/format/metadata.py | 11 +---------- raillabel/save/save.py | 4 ++-- tests/format/test_metadata.py | 26 ++++++++++++++++---------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/raillabel/format/metadata.py b/raillabel/format/metadata.py index b8ae018..ad571ea 100644 --- a/raillabel/format/metadata.py +++ b/raillabel/format/metadata.py @@ -58,13 +58,4 @@ def from_json(cls, json: JSONMetadata) -> Metadata: def to_json(self) -> JSONMetadata: """Export this object into the RailLabel JSON format.""" - return JSONMetadata( - schema_version=self.schema_version, - name=self.name, - subschema_version=self.subschema_version, - exporter_version=self.exporter_version, - file_version=self.file_version, - tagged_file=self.tagged_file, - annotator=self.annotator, - comment=self.comment, - ) + return JSONMetadata(**dict(vars(self))) diff --git a/raillabel/save/save.py b/raillabel/save/save.py index dbe62fc..c5e1e05 100644 --- a/raillabel/save/save.py +++ b/raillabel/save/save.py @@ -11,9 +11,9 @@ def save(scene: Scene, path: Path | str, prettify_json: bool = False) -> None: """Save a raillabel.Scene to a JSON file.""" if prettify_json: - json_data = scene.to_json().model_dump_json(indent=4) + json_data = scene.to_json().model_dump_json(exclude_none=True, indent=4) else: - json_data = scene.to_json().model_dump_json() + json_data = scene.to_json().model_dump_json(exclude_none=True) with Path(path).open("w") as scene_file: scene_file.write(json_data) diff --git a/tests/format/test_metadata.py b/tests/format/test_metadata.py index 6ba5379..35494ac 100644 --- a/tests/format/test_metadata.py +++ b/tests/format/test_metadata.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +import json import pytest @@ -47,7 +48,12 @@ def test_from_json(metadata, metadata_json): assert actual == metadata -def test_from_json__extra_fields(): +def test_to_json(metadata, metadata_json): + actual = metadata.to_json() + assert actual == metadata_json + + +def test_extra_fields(): json_metadata = JSONMetadata( **{ "schema_version": "1.0.0", @@ -55,15 +61,15 @@ def test_from_json__extra_fields(): "ADDITIONAL_OBJECT": {"first_field": 2, "second_field": [1, 2, 3]}, } ) - - actual = Metadata.from_json(json_metadata) - assert actual.ADDITIONAL_STR == "SOME_VALUE" - assert actual.ADDITIONAL_OBJECT == {"first_field": 2, "second_field": [1, 2, 3]} - - -def test_to_json(metadata, metadata_json): - actual = metadata.to_json() - assert actual == metadata_json + metadata = Metadata.from_json(json_metadata) + json_metadata_again = metadata.to_json() + + actual = json.loads(json_metadata_again.model_dump_json(exclude_none=True)) + assert actual == { + "schema_version": "1.0.0", + "ADDITIONAL_STR": "SOME_VALUE", + "ADDITIONAL_OBJECT": {"first_field": 2, "second_field": [1, 2, 3]}, + } if __name__ == "__main__": From a0e998969efe6809d24ba1137ddef88ae5e67873 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:13:24 +0100 Subject: [PATCH 182/190] feat: implement IncludeSensorIdFilter --- raillabel/filter/__init__.py | 2 ++ raillabel/filter/include_sensor_id_filter.py | 24 ++++++++++++++++++++ tests/filter/test_filter.py | 10 ++++++++ 3 files changed, 36 insertions(+) create mode 100644 raillabel/filter/include_sensor_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 1d2d5a4..57ae79f 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -14,6 +14,7 @@ from .include_frame_id_filter import IncludeFrameIdFilter from .include_object_id_filter import IncludeObjectIdFilter from .include_object_type_filter import IncludeObjectTypeFilter +from .include_sensor_id_filter import IncludeSensorIdFilter from .start_time_filter import StartTimeFilter __all__ = [ @@ -30,4 +31,5 @@ "ExcludeObjectIdFilter", "IncludeObjectTypeFilter", "ExcludeObjectTypeFilter", + "IncludeSensorIdFilter", ] diff --git a/raillabel/filter/include_sensor_id_filter.py b/raillabel/filter/include_sensor_id_filter.py new file mode 100644 index 0000000..49ce83b --- /dev/null +++ b/raillabel/filter/include_sensor_id_filter.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeSensorIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have matching sensor ids.""" + + sensor_ids: set[str] | list[str] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return annotation.sensor_id in self.sensor_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 3e73045..164d381 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -164,5 +164,15 @@ def test_exclude_object_types(): assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result +def test_include_sensor_ids(): + scene = ( + SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").add_cuboid(sensor_id="lidar").result + ) + filters = [raillabel.filter.IncludeSensorIdFilter(["rgb_middle"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 44113377625b272e393343892d061f191acfc62b Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:15:33 +0100 Subject: [PATCH 183/190] feat: implement ExcludeSensorIdFilter --- raillabel/filter/__init__.py | 2 ++ raillabel/filter/exclude_sensor_id_filter.py | 24 ++++++++++++++++++++ tests/filter/test_filter.py | 10 ++++++++ 3 files changed, 36 insertions(+) create mode 100644 raillabel/filter/exclude_sensor_id_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 57ae79f..078cb26 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -8,6 +8,7 @@ from .exclude_frame_id_filter import ExcludeFrameIdFilter from .exclude_object_id_filter import ExcludeObjectIdFilter from .exclude_object_type_filter import ExcludeObjectTypeFilter +from .exclude_sensor_id_filter import ExcludeSensorIdFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter @@ -32,4 +33,5 @@ "IncludeObjectTypeFilter", "ExcludeObjectTypeFilter", "IncludeSensorIdFilter", + "ExcludeSensorIdFilter", ] diff --git a/raillabel/filter/exclude_sensor_id_filter.py b/raillabel/filter/exclude_sensor_id_filter.py new file mode 100644 index 0000000..f2375cf --- /dev/null +++ b/raillabel/filter/exclude_sensor_id_filter.py @@ -0,0 +1,24 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeSensorIdFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have matching sensor ids.""" + + sensor_ids: set[str] | list[str] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return annotation.sensor_id not in self.sensor_ids diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 164d381..0f0ac8c 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -174,5 +174,15 @@ def test_include_sensor_ids(): assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result +def test_exclude_sensor_ids(): + scene = ( + SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").add_cuboid(sensor_id="lidar").result + ) + filters = [raillabel.filter.ExcludeSensorIdFilter(["lidar"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 6e292f2132495f1fbfa43960079a90fe48f6d54e Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:24:50 +0100 Subject: [PATCH 184/190] feat: implement IncludeSensorTypeFilter --- raillabel/filter/__init__.py | 2 ++ .../filter/include_sensor_type_filter.py | 25 +++++++++++++++++++ raillabel/format/camera.py | 2 ++ raillabel/format/gps_imu.py | 2 ++ raillabel/format/lidar.py | 2 ++ raillabel/format/other_sensor.py | 2 ++ raillabel/format/radar.py | 2 ++ tests/filter/test_filter.py | 13 ++++++++++ 8 files changed, 50 insertions(+) create mode 100644 raillabel/filter/include_sensor_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 078cb26..ac6a218 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -16,6 +16,7 @@ from .include_object_id_filter import IncludeObjectIdFilter from .include_object_type_filter import IncludeObjectTypeFilter from .include_sensor_id_filter import IncludeSensorIdFilter +from .include_sensor_type_filter import IncludeSensorTypeFilter from .start_time_filter import StartTimeFilter __all__ = [ @@ -34,4 +35,5 @@ "ExcludeObjectTypeFilter", "IncludeSensorIdFilter", "ExcludeSensorIdFilter", + "IncludeSensorTypeFilter", ] diff --git a/raillabel/filter/include_sensor_type_filter.py b/raillabel/filter/include_sensor_type_filter.py new file mode 100644 index 0000000..527f752 --- /dev/null +++ b/raillabel/filter/include_sensor_type_filter.py @@ -0,0 +1,25 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeSensorTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT match the sensor type (like 'camera').""" + + sensor_types: list[Literal["camera", "lidar", "radar", "gps_imu", "other"]] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, scene: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return scene.sensors[annotation.sensor_id].TYPE in self.sensor_types diff --git a/raillabel/format/camera.py b/raillabel/format/camera.py index b692afe..263ee91 100644 --- a/raillabel/format/camera.py +++ b/raillabel/format/camera.py @@ -32,6 +32,8 @@ class Camera: description: str | None = None "Additional information about the sensor." + TYPE: str = "camera" + @classmethod def from_json( cls, json_stream: JSONStreamCamera, json_coordinate_system: JSONCoordinateSystem diff --git a/raillabel/format/gps_imu.py b/raillabel/format/gps_imu.py index ad3610d..86ca56a 100644 --- a/raillabel/format/gps_imu.py +++ b/raillabel/format/gps_imu.py @@ -11,6 +11,8 @@ class GpsImu(_SensorWithoutIntrinsics): """A gps sensor with inertial measurement unit.""" + TYPE: str = "gps_imu" + @classmethod def from_json( cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem diff --git a/raillabel/format/lidar.py b/raillabel/format/lidar.py index e69e26a..a3d4ae5 100644 --- a/raillabel/format/lidar.py +++ b/raillabel/format/lidar.py @@ -11,6 +11,8 @@ class Lidar(_SensorWithoutIntrinsics): """A lidar sensor.""" + TYPE: str = "lidar" + @classmethod def from_json( cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem diff --git a/raillabel/format/other_sensor.py b/raillabel/format/other_sensor.py index 5a1b353..7d62518 100644 --- a/raillabel/format/other_sensor.py +++ b/raillabel/format/other_sensor.py @@ -11,6 +11,8 @@ class OtherSensor(_SensorWithoutIntrinsics): """A sensor that is not represented by the available options.""" + TYPE: str = "other" + @classmethod def from_json( cls, json_stream: JSONStreamOther, json_coordinate_system: JSONCoordinateSystem diff --git a/raillabel/format/radar.py b/raillabel/format/radar.py index 702aa32..226c143 100644 --- a/raillabel/format/radar.py +++ b/raillabel/format/radar.py @@ -32,6 +32,8 @@ class Radar: description: str | None = None "Additional information about the sensor." + TYPE: str = "radar" + @classmethod def from_json( cls, json_stream: JSONStreamRadar, json_coordinate_system: JSONCoordinateSystem diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 0f0ac8c..2ae5a86 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -184,5 +184,18 @@ def test_exclude_sensor_ids(): assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result +def test_include_sensor_types(): + scene = ( + SceneBuilder.empty() + .add_bbox(sensor_id="rgb_middle") + .add_cuboid(sensor_id="lidar_middle") + .result + ) + filters = [raillabel.filter.IncludeSensorTypeFilter(["camera"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 222ffc0a386cf1879f84539ceb6c0daf586bd63b Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:26:58 +0100 Subject: [PATCH 185/190] feat: implement ExcludeSensorTypeFilter --- raillabel/filter/__init__.py | 2 ++ .../filter/exclude_sensor_type_filter.py | 25 +++++++++++++++++++ tests/filter/test_filter.py | 13 ++++++++++ 3 files changed, 40 insertions(+) create mode 100644 raillabel/filter/exclude_sensor_type_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index ac6a218..d93e9be 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -9,6 +9,7 @@ from .exclude_object_id_filter import ExcludeObjectIdFilter from .exclude_object_type_filter import ExcludeObjectTypeFilter from .exclude_sensor_id_filter import ExcludeSensorIdFilter +from .exclude_sensor_type_filter import ExcludeSensorTypeFilter from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter @@ -36,4 +37,5 @@ "IncludeSensorIdFilter", "ExcludeSensorIdFilter", "IncludeSensorTypeFilter", + "ExcludeSensorTypeFilter", ] diff --git a/raillabel/filter/exclude_sensor_type_filter.py b/raillabel/filter/exclude_sensor_type_filter.py new file mode 100644 index 0000000..a4d9d07 --- /dev/null +++ b/raillabel/filter/exclude_sensor_type_filter.py @@ -0,0 +1,25 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeSensorTypeFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do match the sensor type (like 'camera').""" + + sensor_types: list[Literal["camera", "lidar", "radar", "gps_imu", "other"]] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, scene: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + return scene.sensors[annotation.sensor_id].TYPE not in self.sensor_types diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 2ae5a86..0585d0c 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -197,5 +197,18 @@ def test_include_sensor_types(): assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result +def test_exclude_sensor_types(): + scene = ( + SceneBuilder.empty() + .add_bbox(sensor_id="rgb_middle") + .add_cuboid(sensor_id="lidar_middle") + .result + ) + filters = [raillabel.filter.ExcludeSensorTypeFilter(["lidar"])] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 61a5b2174695417e883d61a2455cc8732a2bc661 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:40:21 +0100 Subject: [PATCH 186/190] feat: implement IncludeAttributesFilter --- raillabel/filter/__init__.py | 2 + raillabel/filter/include_attributes_filter.py | 39 +++++++++++++++++++ tests/filter/test_filter.py | 26 +++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 raillabel/filter/include_attributes_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index d93e9be..22cb083 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -13,6 +13,7 @@ from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter +from .include_attributes_filter import IncludeAttributesFilter from .include_frame_id_filter import IncludeFrameIdFilter from .include_object_id_filter import IncludeObjectIdFilter from .include_object_type_filter import IncludeObjectTypeFilter @@ -38,4 +39,5 @@ "ExcludeSensorIdFilter", "IncludeSensorTypeFilter", "ExcludeSensorTypeFilter", + "IncludeAttributesFilter", ] diff --git a/raillabel/filter/include_attributes_filter.py b/raillabel/filter/include_attributes_filter.py new file mode 100644 index 0000000..da72025 --- /dev/null +++ b/raillabel/filter/include_attributes_filter.py @@ -0,0 +1,39 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class IncludeAttributesFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do NOT have matching attributes. + + If an attribute has None as the value, all annotations are excluded, that do not have this + attribute. If the value is anything other than None, all annotations are exluded that do not + have the attribute or where the value does not match. + """ + + attributes: dict[str, bool | float | str | list | None] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + for attribute_name, attribute_value in annotation.attributes.items(): + if attribute_name not in self.attributes: + return False + + if ( + self.attributes[attribute_name] is not None + and attribute_value != self.attributes[attribute_name] + ): + return False + + return True diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index 0585d0c..e7ef7fa 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -210,5 +210,31 @@ def test_exclude_sensor_types(): assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result +def test_include_attributes__only_keys(): + scene = ( + SceneBuilder.empty() + .add_bbox(attributes={"length": 42}) + .add_bbox(attributes={"width": 34}) + .result + ) + filters = [raillabel.filter.IncludeAttributesFilter({"length": None})] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(attributes={"length": 42}).result + + +def test_include_attributes__with_values(): + scene = ( + SceneBuilder.empty() + .add_bbox(attributes={"is_dummy": True}) + .add_bbox(attributes={"is_dummy": False}) + .result + ) + filters = [raillabel.filter.IncludeAttributesFilter({"is_dummy": True})] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(attributes={"is_dummy": True}).result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From ed6d55d6bdfbe705e9bb39b3706eba668b124ac5 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 09:51:18 +0100 Subject: [PATCH 187/190] feat: implement ExcludeAttributesFilter --- raillabel/filter/__init__.py | 2 + raillabel/filter/exclude_attributes_filter.py | 40 +++++++++++++++++++ raillabel/filter/include_attributes_filter.py | 2 +- tests/filter/test_filter.py | 26 ++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 raillabel/filter/exclude_attributes_filter.py diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 22cb083..70fc30d 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -5,6 +5,7 @@ from .end_time_filter import EndTimeFilter from .exclude_annotation_id_filter import ExcludeAnnotationIdFilter from .exclude_annotation_type_filter import ExcludeAnnotationTypeFilter +from .exclude_attributes_filter import ExcludeAttributesFilter from .exclude_frame_id_filter import ExcludeFrameIdFilter from .exclude_object_id_filter import ExcludeObjectIdFilter from .exclude_object_type_filter import ExcludeObjectTypeFilter @@ -40,4 +41,5 @@ "IncludeSensorTypeFilter", "ExcludeSensorTypeFilter", "IncludeAttributesFilter", + "ExcludeAttributesFilter", ] diff --git a/raillabel/filter/exclude_attributes_filter.py b/raillabel/filter/exclude_attributes_filter.py new file mode 100644 index 0000000..b2b33e8 --- /dev/null +++ b/raillabel/filter/exclude_attributes_filter.py @@ -0,0 +1,40 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID + +from raillabel.format import Bbox, Cuboid, Poly2d, Poly3d, Scene, Seg3d + +from ._filter_abc import _AnnotationLevelFilter + + +@dataclass +class ExcludeAttributesFilter(_AnnotationLevelFilter): + """Filter out all annotations in the scene, that do have matching attributes. + + If an attribute has None as the value, all annotations are included, that do not have this + attribute. If the value is anything other than None, all annotations are included that do not + have the attribute or where the value does not match. + """ + + attributes: dict[str, bool | float | str | list | None] + + def passes_filter( + self, _: UUID, annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, __: Scene + ) -> bool: + """Assess if an annotation passes this filter.""" + for attribute_name, attribute_value in self.attributes.items(): + if attribute_value is None: + if attribute_name in annotation.attributes: + return False + + elif ( + attribute_name in annotation.attributes + and attribute_value == annotation.attributes[attribute_name] + ): + return False + + return True diff --git a/raillabel/filter/include_attributes_filter.py b/raillabel/filter/include_attributes_filter.py index da72025..7071c17 100644 --- a/raillabel/filter/include_attributes_filter.py +++ b/raillabel/filter/include_attributes_filter.py @@ -16,7 +16,7 @@ class IncludeAttributesFilter(_AnnotationLevelFilter): """Filter out all annotations in the scene, that do NOT have matching attributes. If an attribute has None as the value, all annotations are excluded, that do not have this - attribute. If the value is anything other than None, all annotations are exluded that do not + attribute. If the value is anything other than None, all annotations are excluded that do not have the attribute or where the value does not match. """ diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index e7ef7fa..e065272 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -236,5 +236,31 @@ def test_include_attributes__with_values(): assert actual == SceneBuilder.empty().add_bbox(attributes={"is_dummy": True}).result +def test_exclude_attributes__only_keys(): + scene = ( + SceneBuilder.empty() + .add_bbox(attributes={"length": 42}) + .add_bbox(attributes={"width": 34}) + .result + ) + filters = [raillabel.filter.ExcludeAttributesFilter({"width": None})] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(attributes={"length": 42}).result + + +def test_exclude_attributes__with_values(): + scene = ( + SceneBuilder.empty() + .add_bbox(attributes={"is_dummy": True}) + .add_bbox(attributes={"is_dummy": False}) + .result + ) + filters = [raillabel.filter.ExcludeAttributesFilter({"is_dummy": False})] + + actual = raillabel.filter.filter_(scene, filters) + assert actual == SceneBuilder.empty().add_bbox(attributes={"is_dummy": True}).result + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) From 64a36793aeff2c117b4d79769b05cb6d402e9758 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 10:05:06 +0100 Subject: [PATCH 188/190] docs: update github docs --- docs/source/howto.rst | 20 ----- .../howtos/1 Validating Annotation Files.rst | 75 ------------------- .../howtos/2 Loading Annotation Files.rst | 25 ------- .../howtos/3 Saving Annotation Files.rst | 32 -------- docs/source/howtos/4 Filtering Scenes.rst | 50 ------------- docs/source/index.rst | 42 ++++++++--- 6 files changed, 32 insertions(+), 212 deletions(-) delete mode 100644 docs/source/howto.rst delete mode 100644 docs/source/howtos/1 Validating Annotation Files.rst delete mode 100644 docs/source/howtos/2 Loading Annotation Files.rst delete mode 100644 docs/source/howtos/3 Saving Annotation Files.rst delete mode 100644 docs/source/howtos/4 Filtering Scenes.rst diff --git a/docs/source/howto.rst b/docs/source/howto.rst deleted file mode 100644 index 5810e32..0000000 --- a/docs/source/howto.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. - Copyright DB InfraGO AG and contributors - SPDX-License-Identifier: Apache-2.0 - -.. _howtos: - -******* -How Tos -******* - -In this section you can view dedicated tutorial-notebooks of key -features. - - -.. toctree:: - :maxdepth: 4 - :caption: How tos: - :glob: - - howtos/* diff --git a/docs/source/howtos/1 Validating Annotation Files.rst b/docs/source/howtos/1 Validating Annotation Files.rst deleted file mode 100644 index c87ef96..0000000 --- a/docs/source/howtos/1 Validating Annotation Files.rst +++ /dev/null @@ -1,75 +0,0 @@ -.. - Copyright DB InfraGO AG and contributors - SPDX-License-Identifier: Apache-2.0 - -============================= -1 Validating Annotation Files -============================= - -Some annotation files might not fit the valid schema, depending on their source and editing history. To check whether a JSON file is a valid RailLabel annotation file, the raillabel.validate() method can be used. This checks the JSON file against a given schema and returns whether the data is valid and any potential incompatibilities with the schema. - -.. code-block:: python - - import raillabel - - valid_data = { - "openlabel": { - "metadata": { - "schema_version": "1.0.0" - } - } - } - - raillabel.validate(valid_data) - -.. code-block:: python - - Returns: (True, []) - -.. code-block:: python - - import raillabel - - invalid_data = { - "openlabel": { - "metadata": { - "schema_version": "1.0.0" - }, - "invalid_field": "foo" - } - } - - raillabel.validate(invalid_data) - -.. code-block:: python - - Returns: (False, ["$.openlabel: Additional properties are not allowed ('invalid_field' was unexpected)"]) - -By default, the OpenLABEL JSON schema is used for validation. However, the method can validate data with any schema. - -.. code-block:: python - - import raillabel - - data = { - "openlabel": { - "metadata": { - "schema_version": "2.0.0" - } - } - } - - raillabel.validate(data, "path/to/schema.json") - -The method returns a tuple of two elements. The first element is a boolean marking if the data validates against the schema. The second is a list of strings with each string being an error in the data. This example shows how to present the results of the method to a user. - -.. code-block:: python - - is_data_valid, warnings = raillabel.validate(data) - - if is_data_valid: - do_something() - - else: - for w in warnings: - print(w) diff --git a/docs/source/howtos/2 Loading Annotation Files.rst b/docs/source/howtos/2 Loading Annotation Files.rst deleted file mode 100644 index 0d65c3d..0000000 --- a/docs/source/howtos/2 Loading Annotation Files.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. - Copyright DB InfraGO AG and contributors - SPDX-License-Identifier: Apache-2.0 - -========================== -2 Loading Annotation Files -========================== - -Loading annotation files into a raillabel.Scene is done with ``raillabel.load()``. - -.. code-block:: python - - import raillabel - scene = raillabel.load('path/to/file.json') - -The method can itself identify if the file is from a supported format and then choose the correct loader-class. - -Validation -========== -If the data should be validated before beeing loaded, the ``validate`` parameter can be set to True. If the data is not valid, a ``raillabel.SchemaError`` is raised with all errors included. - -.. code-block:: python - - import raillabel - scene = raillabel.load('path/to/file.json', validate=True) diff --git a/docs/source/howtos/3 Saving Annotation Files.rst b/docs/source/howtos/3 Saving Annotation Files.rst deleted file mode 100644 index dce3d7f..0000000 --- a/docs/source/howtos/3 Saving Annotation Files.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. - Copyright DB InfraGO AG and contributors - SPDX-License-Identifier: Apache-2.0 - -========================= -3 Saving Annotation Files -========================= - -If changes have been made to an annotation file or a file should be copied, it can be saved via the ``raillabel.save()`` method. - -.. code-block:: python - - import raillabel - - scene = raillabel.load('path/to/file.json') - scene.frames['0'].stream_stamps['lidar'].timestamp += 37 - raillabel.save(scene, 'path/to/file.json') - -By default, the JSON file is saved as compact as possible with no linebreaks or indents. This can be changed by setting ``prettify_json=True`` as an argument. The indent size used is 4 spaces. - -Validation -========== - -If the data should be validated after beeing saved, the ``validate`` parameter can be set to True. If the data is not valid, a ``raillabel.SchemaError`` is raised with all errors included and the data is not saved. - -.. code-block:: python - - import raillabel - - scene = raillabel.load('path/to/file.json') - scene.frames['0'].stream_stamps['lidar'].timestamp += 37 - raillabel.save(scene, 'path/to/file.json', validate=True) diff --git a/docs/source/howtos/4 Filtering Scenes.rst b/docs/source/howtos/4 Filtering Scenes.rst deleted file mode 100644 index 4b99330..0000000 --- a/docs/source/howtos/4 Filtering Scenes.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. - Copyright DB InfraGO AG and contributors - SPDX-License-Identifier: Apache-2.0 - -================== -4 Filtering Scenes -================== - -The annotations of a scene can be filtered by many criteria. This functionality is provided by ``raillabel.filter()``. This method takes a scene and filter arguments and returns a copy of the scene with filters applied. - -.. code-block:: python - - import raillabel - import decimal - - scene = raillabel.load('path/to/file.json') - - scene_with_only_trains = raillabel.filter( - scene, - include_object_types=['train'] - ) - scene_without_bboxs = raillabel.filter( - scene, - exclude_annotation_types=['bbox'] - ) - cut_scene_with_only_red_trains = raillabel.filter( - scene, - start_timestamp=decimal.Decimal('1587349200.004200000'), - exclude_frames=[4, 2], - include_object_types=['train'], - include_attributes={ - 'color': 'red' - } - ) - scene_with_annotations_with_an_attribute = raillabel.filter( - scene, - include_attributes={ # All annotations with the color - 'color': None # attribute will be included, - } # regardless of color value. - ) - -Most filter categories have an include and exclude parameter (i.e ``include_object_types`` and ``exclude_object_types``). When include is set, all annotations, that meet the criterium are *included* into the filtered scene. Excluded parameters are *excluded* from the scene. These two parameters are mutually exclusive and can not both be set. - -.. code-block:: python - - invalid_scene = raillabel.filter( - scene, - include_object_types=['person'], # Will raise a ValueError due - exclude_object_types=['train'] # to mutual exclusivity. - ) diff --git a/docs/source/index.rst b/docs/source/index.rst index 46fb152..a1d914a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,11 @@ Python RailLabel Development-Kit **Date**: |today| **Version**: |Version| +.. + Copyright DB InfraGO AG and contributors + SPDX-License-Identifier: Apache-2.0 + + Description ----------- This library is designed to assist in the handling of the sensor annotations of Deutsche Bahn in the OpenLABEL data format. Common usage for the library: @@ -23,20 +28,37 @@ This library is designed to assist in the handling of the sensor annotations of * filtering for specific frames, annotations or objects * editing annotation files -If you want a quickstart at how to use this package, head right into the -:ref:`how-tos section `. +Motivation +---------- -.. toctree:: - :caption: Introduction - :maxdepth: 4 +Working with our own data has brought up the need to interact with the annotations programmatically. The annotation data is stored in .json files in the `ASAM OpenLABEL annotation `_ format, an emerging industry standard targeted towards the automotive sector. But as a standard it is designed very inclusively, which makes it overloaded for our limited use cases. We therefore decided to create a submodel called "RailLabel" with a corresponding devkit for easier interaction with the data. The example below shows a comparison between a purely JSON based approach compared to the devkit. - intro +With JSON only: -.. toctree:: - :caption: Tutorials - :titlesonly: +.. code-block:: python + + import json + + with open('path/to/file.json', 'r') as data_file: + scene = json.load(data_file) + + scene['openlabel']['frames']['1']['frame_properties']['streams']['lidar']['stream_properties']['stream_sync']['timestamp'] += 37 + + with open('path/to/other_file.json', 'w') as data_file: + json.dump(scene, data_file) + +With RailLabel: + +.. code-block:: python + + import raillabel + + scene = raillabel.load('path/to/file.json') + scene.frames[1].sensors['lidar'].timestamp += 37 + raillabel.save(scene, 'path/to/other_file.json') - howto +Content +------- .. toctree:: :caption: Modules From 7a71a533b637c8b48125c18c9f68ff26c5eb107f Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 10:24:00 +0100 Subject: [PATCH 189/190] refactor: move filter functionality to Scene.filter() --- README.md | 11 +++ raillabel/filter/__init__.py | 2 - raillabel/filter/_filter_abc.py | 4 +- raillabel/filter/filter.py | 122 -------------------------------- raillabel/format/scene.py | 103 +++++++++++++++++++++++++++ tests/filter/test_filter.py | 40 +++++------ 6 files changed, 137 insertions(+), 145 deletions(-) delete mode 100644 raillabel/filter/filter.py diff --git a/README.md b/README.md index 465eb59..c39d0d2 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ pip install -e '.[docs,test]' pre-commit install ``` +# Usage + +The first step in using `raillabel` is downloading a desired dataset (like [OSDaR23](https://data.fid-move.de/dataset/osdar23)). You can then load any scene by running +```python +import raillabel + +scene = raillabel.load("path/to/annotation_file.json") +``` + +This returns a [`raillabel.Scene`](https://dsd-dbs.github.io/raillabel/code/raillabel.html#raillabel.Scene), which is the root class for the annotations. + # Contributing We'd love to see your bug reports and improvement suggestions! Please take a diff --git a/raillabel/filter/__init__.py b/raillabel/filter/__init__.py index 70fc30d..64231dd 100644 --- a/raillabel/filter/__init__.py +++ b/raillabel/filter/__init__.py @@ -11,7 +11,6 @@ from .exclude_object_type_filter import ExcludeObjectTypeFilter from .exclude_sensor_id_filter import ExcludeSensorIdFilter from .exclude_sensor_type_filter import ExcludeSensorTypeFilter -from .filter import filter_ from .include_annotation_id_filter import IncludeAnnotationIdFilter from .include_annotation_type_filter import IncludeAnnotationTypeFilter from .include_attributes_filter import IncludeAttributesFilter @@ -23,7 +22,6 @@ from .start_time_filter import StartTimeFilter __all__ = [ - "filter_", "IncludeFrameIdFilter", "ExcludeFrameIdFilter", "StartTimeFilter", diff --git a/raillabel/filter/_filter_abc.py b/raillabel/filter/_filter_abc.py index cb7b1b7..3deb1b0 100644 --- a/raillabel/filter/_filter_abc.py +++ b/raillabel/filter/_filter_abc.py @@ -3,9 +3,11 @@ from __future__ import annotations from abc import ABC, abstractmethod +from typing import TYPE_CHECKING from uuid import UUID -from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Scene, Seg3d +if TYPE_CHECKING: + from raillabel.format import Bbox, Cuboid, Frame, Poly2d, Poly3d, Scene, Seg3d class _FilterAbc(ABC): diff --git a/raillabel/filter/filter.py b/raillabel/filter/filter.py deleted file mode 100644 index 7fa2c9a..0000000 --- a/raillabel/filter/filter.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from copy import deepcopy -from uuid import UUID - -from raillabel.format import ( - Bbox, - Camera, - Cuboid, - Frame, - GpsImu, - Lidar, - Object, - OtherSensor, - Poly2d, - Poly3d, - Radar, - Scene, - Seg3d, -) - -from ._filter_abc import _AnnotationLevelFilter, _FilterAbc, _FrameLevelFilter - - -def filter_(scene: Scene, filters: list[_FilterAbc]) -> Scene: - """Return a scene with filters applied to annotations, frame, sensors and objects.""" - frame_filters, annotation_filters = _separate_filters(filters) - - filtered_scene = Scene(metadata=deepcopy(scene.metadata)) - filtered_scene.frames = _filter_frames(scene, frame_filters, annotation_filters) - filtered_scene.sensors = _get_used_sensors(scene, filtered_scene) - filtered_scene.objects = _get_used_objects(scene, filtered_scene) - - return filtered_scene - - -def _separate_filters( - all_filters: list[_FilterAbc], -) -> tuple[list[_FrameLevelFilter], list[_AnnotationLevelFilter]]: - frame_filters = [] - annotation_filters = [] - for filter_ in all_filters: - if isinstance(filter_, _FrameLevelFilter): - frame_filters.append(filter_) - - if isinstance(filter_, _AnnotationLevelFilter): - annotation_filters.append(filter_) - - return frame_filters, annotation_filters - - -def _filter_frames( - scene: Scene, - frame_filters: list[_FrameLevelFilter], - annotation_filters: list[_AnnotationLevelFilter], -) -> dict[int, Frame]: - filtered_frames = {} - - for frame_id, frame in scene.frames.items(): - if _frame_passes_all_filters(frame_id, frame, frame_filters): - filtered_frames[frame_id] = Frame( - timestamp=deepcopy(frame.timestamp), - sensors=deepcopy(frame.sensors), - frame_data=deepcopy(frame.frame_data), - annotations=_filter_annotations(frame, annotation_filters, scene), - ) - - return filtered_frames - - -def _filter_annotations( - frame: Frame, annotation_filters: list[_AnnotationLevelFilter], scene: Scene -) -> dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d]: - annotations = {} - - for annotation_id, annotation in frame.annotations.items(): - if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters, scene): - annotations[annotation_id] = deepcopy(annotation) - - return annotations - - -def _frame_passes_all_filters( - frame_id: int, frame: Frame, frame_filters: list[_FrameLevelFilter] -) -> bool: - return all(filter_.passes_filter(frame_id, frame) for filter_ in frame_filters) - - -def _annotation_passes_all_filters( - annotation_id: UUID, - annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, - annotation_filters: list[_AnnotationLevelFilter], - scene: Scene, -) -> bool: - return all( - filter_.passes_filter(annotation_id, annotation, scene) for filter_ in annotation_filters - ) - - -def _get_used_sensors( - scene: Scene, filtered_scene: Scene -) -> dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor]: - used_sensors = {} - for frame in filtered_scene.frames.values(): - for annotation in frame.annotations.values(): - if annotation.sensor_id not in used_sensors: - used_sensors[annotation.sensor_id] = deepcopy(scene.sensors[annotation.sensor_id]) - - return used_sensors - - -def _get_used_objects(scene: Scene, filtered_scene: Scene) -> dict[UUID, Object]: - used_objects = {} - for frame in filtered_scene.frames.values(): - for annotation in frame.annotations.values(): - if annotation.object_id not in used_objects: - used_objects[annotation.object_id] = deepcopy(scene.objects[annotation.object_id]) - - return used_objects diff --git a/raillabel/format/scene.py b/raillabel/format/scene.py index 9220794..88fae19 100644 --- a/raillabel/format/scene.py +++ b/raillabel/format/scene.py @@ -3,9 +3,11 @@ from __future__ import annotations +from copy import deepcopy from dataclasses import dataclass, field from uuid import UUID +from raillabel.filter._filter_abc import _AnnotationLevelFilter, _FilterAbc, _FrameLevelFilter from raillabel.json_format import ( JSONCoordinateSystem, JSONFrame, @@ -17,7 +19,9 @@ JSONStreamRadar, ) +from .bbox import Bbox from .camera import Camera +from .cuboid import Cuboid from .frame import Frame from .frame_interval import FrameInterval from .gps_imu import GpsImu @@ -25,7 +29,10 @@ from .metadata import Metadata from .object import Object from .other_sensor import OtherSensor +from .poly2d import Poly2d +from .poly3d import Poly3d from .radar import Radar +from .seg3d import Seg3d @dataclass @@ -75,6 +82,17 @@ def to_json(self) -> JSONScene: ) ) + def filter(self, filters: list[_FilterAbc]) -> Scene: + """Return a scene with annotations, sensors, objects and frames excluded.""" + frame_filters, annotation_filters = _separate_filters(filters) + + filtered_scene = Scene(metadata=deepcopy(self.metadata)) + filtered_scene.frames = _filter_frames(self, frame_filters, annotation_filters) + filtered_scene.sensors = _get_used_sensors(self, filtered_scene) + filtered_scene.objects = _get_used_objects(self, filtered_scene) + + return filtered_scene + def _sensors_from_json( json_streams: dict[str, JSONStreamCamera | JSONStreamOther | JSONStreamRadar] | None, @@ -133,3 +151,88 @@ def _coordinate_systems_to_json( children=list(json_coordinate_systems.keys()), ) return json_coordinate_systems + + +def _separate_filters( + all_filters: list[_FilterAbc], +) -> tuple[list[_FrameLevelFilter], list[_AnnotationLevelFilter]]: + frame_filters = [] + annotation_filters = [] + for filter_ in all_filters: + if isinstance(filter_, _FrameLevelFilter): + frame_filters.append(filter_) + + if isinstance(filter_, _AnnotationLevelFilter): + annotation_filters.append(filter_) + + return frame_filters, annotation_filters + + +def _filter_frames( + scene: Scene, + frame_filters: list[_FrameLevelFilter], + annotation_filters: list[_AnnotationLevelFilter], +) -> dict[int, Frame]: + filtered_frames = {} + + for frame_id, frame in scene.frames.items(): + if _frame_passes_all_filters(frame_id, frame, frame_filters): + filtered_frames[frame_id] = Frame( + timestamp=deepcopy(frame.timestamp), + sensors=deepcopy(frame.sensors), + frame_data=deepcopy(frame.frame_data), + annotations=_filter_annotations(frame, annotation_filters, scene), + ) + + return filtered_frames + + +def _filter_annotations( + frame: Frame, annotation_filters: list[_AnnotationLevelFilter], scene: Scene +) -> dict[UUID, Bbox | Cuboid | Poly2d | Poly3d | Seg3d]: + annotations = {} + + for annotation_id, annotation in frame.annotations.items(): + if _annotation_passes_all_filters(annotation_id, annotation, annotation_filters, scene): + annotations[annotation_id] = deepcopy(annotation) + + return annotations + + +def _frame_passes_all_filters( + frame_id: int, frame: Frame, frame_filters: list[_FrameLevelFilter] +) -> bool: + return all(filter_.passes_filter(frame_id, frame) for filter_ in frame_filters) + + +def _annotation_passes_all_filters( + annotation_id: UUID, + annotation: Bbox | Cuboid | Poly2d | Poly3d | Seg3d, + annotation_filters: list[_AnnotationLevelFilter], + scene: Scene, +) -> bool: + return all( + filter_.passes_filter(annotation_id, annotation, scene) for filter_ in annotation_filters + ) + + +def _get_used_sensors( + scene: Scene, filtered_scene: Scene +) -> dict[str, Camera | Lidar | Radar | GpsImu | OtherSensor]: + used_sensors = {} + for frame in filtered_scene.frames.values(): + for annotation in frame.annotations.values(): + if annotation.sensor_id not in used_sensors: + used_sensors[annotation.sensor_id] = deepcopy(scene.sensors[annotation.sensor_id]) + + return used_sensors + + +def _get_used_objects(scene: Scene, filtered_scene: Scene) -> dict[UUID, Object]: + used_objects = {} + for frame in filtered_scene.frames.values(): + for annotation in frame.annotations.values(): + if annotation.object_id not in used_objects: + used_objects[annotation.object_id] = deepcopy(scene.objects[annotation.object_id]) + + return used_objects diff --git a/tests/filter/test_filter.py b/tests/filter/test_filter.py index e065272..b9fd9c4 100644 --- a/tests/filter/test_filter.py +++ b/tests/filter/test_filter.py @@ -15,7 +15,7 @@ def test_include_frame_ids(): scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result filters = [raillabel.filter.IncludeFrameIdFilter([1, 3])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result @@ -23,7 +23,7 @@ def test_exclude_frame_ids(): scene = SceneBuilder.empty().add_frame(1).add_frame(2).add_frame(3).result filters = [raillabel.filter.ExcludeFrameIdFilter([2])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_frame(1).add_frame(3).result @@ -31,7 +31,7 @@ def test_start_time(): scene = SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).add_frame(3, 300).result filters = [raillabel.filter.StartTimeFilter(150)] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_frame(2, 200).add_frame(3, 300).result @@ -39,7 +39,7 @@ def test_end_time(): scene = SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).add_frame(3, 300).result filters = [raillabel.filter.EndTimeFilter(250)] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_frame(1, 100).add_frame(2, 200).result @@ -60,7 +60,7 @@ def test_include_annotation_ids(): ) ] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert ( actual == SceneBuilder.empty() @@ -82,7 +82,7 @@ def test_exclude_annotation_ids(): raillabel.filter.ExcludeAnnotationIdFilter([UUID("6c95543d-0000-4000-0000-000000000001")]) ] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert ( actual == SceneBuilder.empty() @@ -96,7 +96,7 @@ def test_include_annotation_type(): scene = SceneBuilder.empty().add_bbox().add_cuboid().result filters = [raillabel.filter.IncludeAnnotationTypeFilter(["bbox"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox().result @@ -104,7 +104,7 @@ def test_exclude_annotation_type(): scene = SceneBuilder.empty().add_bbox().add_cuboid().result filters = [raillabel.filter.ExcludeAnnotationTypeFilter(["cuboid"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox().result @@ -119,7 +119,7 @@ def test_include_object_ids(): raillabel.filter.IncludeObjectIdFilter([UUID("5c59aad4-0000-4000-0000-000000000000")]) ] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result @@ -134,7 +134,7 @@ def test_exclude_object_ids(): raillabel.filter.ExcludeObjectIdFilter([UUID("5c59aad4-0000-4000-0000-000000000001")]) ] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result @@ -147,7 +147,7 @@ def test_include_object_types(): ) filters = [raillabel.filter.IncludeObjectTypeFilter(["person"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result @@ -160,7 +160,7 @@ def test_exclude_object_types(): ) filters = [raillabel.filter.ExcludeObjectTypeFilter(["train"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(object_name="person_0001").result @@ -170,7 +170,7 @@ def test_include_sensor_ids(): ) filters = [raillabel.filter.IncludeSensorIdFilter(["rgb_middle"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result @@ -180,7 +180,7 @@ def test_exclude_sensor_ids(): ) filters = [raillabel.filter.ExcludeSensorIdFilter(["lidar"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result @@ -193,7 +193,7 @@ def test_include_sensor_types(): ) filters = [raillabel.filter.IncludeSensorTypeFilter(["camera"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result @@ -206,7 +206,7 @@ def test_exclude_sensor_types(): ) filters = [raillabel.filter.ExcludeSensorTypeFilter(["lidar"])] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(sensor_id="rgb_middle").result @@ -219,7 +219,7 @@ def test_include_attributes__only_keys(): ) filters = [raillabel.filter.IncludeAttributesFilter({"length": None})] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(attributes={"length": 42}).result @@ -232,7 +232,7 @@ def test_include_attributes__with_values(): ) filters = [raillabel.filter.IncludeAttributesFilter({"is_dummy": True})] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(attributes={"is_dummy": True}).result @@ -245,7 +245,7 @@ def test_exclude_attributes__only_keys(): ) filters = [raillabel.filter.ExcludeAttributesFilter({"width": None})] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(attributes={"length": 42}).result @@ -258,7 +258,7 @@ def test_exclude_attributes__with_values(): ) filters = [raillabel.filter.ExcludeAttributesFilter({"is_dummy": False})] - actual = raillabel.filter.filter_(scene, filters) + actual = scene.filter(filters) assert actual == SceneBuilder.empty().add_bbox(attributes={"is_dummy": True}).result From 5631f045ce2b1d52aafde97eb9d5eec90321ab45 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 12:34:57 +0100 Subject: [PATCH 190/190] docs: update README and CHANGELOG --- CHANGELOG.md | 8 ++++++-- README.md | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7986c0f..e7d5858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,7 +106,11 @@ Functionality, that has been **moved** to the `raillabel_providerkit`: Other breaking changes: - the `fromdict()` and `asdict()` methods in `raillabel.format` classes have been replaced with `from_json()` and `to_json` respectively -- `raillabel.format.Transform` fields have been changed by `pos -> position` and `quad -> quaternion` to make it more explicit - `raillabel.format.FrameInterval` fields have been changed by `frame_start -> start` and `frame_end -> end` to make it more concise -- `raillabel.format.Frame.uid` field has been removed to avoid redundant information +- all uid fields of classes have been removed (like `raillabel.format.Frame.uid`) have been removed to avoid redundant information - `raillabel.format.Sensor` has been removed in favor of the different sensor type classes `raillabel.format.Camera`, `raillabel.format.Lidar`, `raillabel.format.Radar` and `raillabel.format.GpsImu` +- `raillabel.filter()` has been removed in favor of `raillabel.Scene.filter()` with different input arguments + +New features: +- `raillabel.json_format` has been introduced as an interface between the JSON format and the `raillabel` classes +- `raillabel.scene_builder.SceneBuilder` is now available to easily build scenes for testing purposes diff --git a/README.md b/README.md index c39d0d2..3bb2b38 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,33 @@ scene = raillabel.load("path/to/annotation_file.json") This returns a [`raillabel.Scene`](https://dsd-dbs.github.io/raillabel/code/raillabel.html#raillabel.Scene), which is the root class for the annotations. +If a file is too extensive for your use-case you can filter out certain parts of a scene like this +```python +from raillabel.filter import ( + IncludeObjectTypeFilter, + ExcludeAnnotationTypeFilter, + StartTimeFilter, ExcludeFrameIdFilter, + IncludeAttributesFilter +) + +scene_with_only_trains = scene.filter([IncludeObjectTypeFilter(["rail_vehicle"])]) + +scene_without_bboxs = scene.filter([ExcludeAnnotationTypeFilter(["bbox"])]) + +cut_scene_with_only_red_trains = scene.filter([ + StartTimeFilter("1587349200.004200000"), + ExcludeFrameIdFilter([2, 4]), + IncludeObjectTypeFilter(["rail_vehicle"]), + IncludeAttributesFilter({"color": "red"}), +]) +``` +An overview of all available filters can be found [here](https://dsd-dbs.github.io/raillabel/code/raillabel.filter.html#module-raillabel.filter). + +If you then want to save your changes, then use +```python +raillabel.save(cut_scene_with_only_red_trains, "/path/to/target.json") +``` + # Contributing We'd love to see your bug reports and improvement suggestions! Please take a