From fee3aff682030b158ac55753a2a73701f2b2a9d1 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 4 Nov 2024 16:25:47 +0100 Subject: [PATCH 01/67] 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 02/67] 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 03/67] 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 04/67] 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 05/67] 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 06/67] 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 07/67] 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 08/67] 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 09/67] 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 10/67] 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 11/67] 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 12/67] 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 13/67] 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 14/67] 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 15/67] 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 16/67] 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 17/67] 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 18/67] 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 19/67] 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 20/67] 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 21/67] 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 22/67] 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 23/67] 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 24/67] 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 25/67] 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 26/67] 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 27/67] 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 28/67] 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 29/67] 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 30/67] 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 31/67] 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 32/67] 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 33/67] 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 34/67] 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 35/67] 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 36/67] 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 37/67] 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 38/67] 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 39/67] 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 40/67] 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 41/67] 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 42/67] 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 43/67] 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 44/67] 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 45/67] 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 46/67] 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 47/67] 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 48/67] 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 49/67] 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 50/67] 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 51/67] 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 52/67] 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 53/67] 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 54/67] 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 55/67] 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 56/67] 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 57/67] 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 58/67] 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 59/67] 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 60/67] 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 61/67] 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 62/67] 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 63/67] 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 64/67] 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 65/67] 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 66/67] 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 67/67] 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"])