From 02d622f6242c9bf667d95c424969c30c9bda51ef Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 14:05:56 +0100 Subject: [PATCH 01/27] feat: include python versions 3.12 und 3.13 --- .github/workflows/build-test-publish.yml | 6 ++++-- pyproject.toml | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test-publish.yml b/.github/workflows/build-test-publish.yml index 94481c0..80a0da4 100644 --- a/.github/workflows/build-test-publish.yml +++ b/.github/workflows/build-test-publish.yml @@ -22,9 +22,11 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" + - "3.13" include: - os: windows-latest - python_version: "3.8" + python_version: "3.12" steps: - uses: actions/checkout@v2 - name: Set up Python ${{matrix.python_version}} @@ -57,7 +59,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.13" - name: Install dependencies run: |- python -m pip install -U pip diff --git a/pyproject.toml b/pyproject.toml index 7947c6b..58edd16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dynamic = ["version"] name = "raillabel-providerkit" description = "A devkit for working with recorded and annotated train ride data from Deutsche Bahn." readme = "README.md" -requires-python = ">=3.8, <3.12" +requires-python = ">=3.8, <3.14" license = { text = "Apache-2.0" } authors = [ { name = "DB InfraGO AG" }, @@ -31,6 +31,8 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "jsonschema>=4.4.0", From 8d112fb2102e0a0486d73b38ede2254d31fdc761 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 18 Nov 2024 14:57:29 +0100 Subject: [PATCH 02/27] fix: adapt first test to new raillabel version --- pyproject.toml | 2 +- .../validate_rail_side/validate_rail_side.py | 120 ++- tests/conftest.py | 2 +- .../_util/test_warning.py | 2 +- .../validation/conftest.py | 9 +- .../test_onthology_schema_v1.py | 167 ---- .../test_validate_onthology.py | 632 ------------- .../test_validate_rail_side.py | 849 +++++++++--------- 8 files changed, 490 insertions(+), 1293 deletions(-) delete mode 100644 tests/test_raillabel_providerkit/validation/validate_onthology/test_onthology_schema_v1.py delete mode 100644 tests/test_raillabel_providerkit/validation/validate_onthology/test_validate_onthology.py diff --git a/pyproject.toml b/pyproject.toml index 58edd16..3931199 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ dependencies = [ "jsonschema>=4.4.0", "fastjsonschema>=2.16.2", - "raillabel>=3.1.0, <4.0.0", + "raillabel>=4.0.0", "pyyaml>=6.0.0", "numpy>=1.24.4", ] diff --git a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py index a062322..2deb0d9 100644 --- a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py +++ b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py @@ -5,6 +5,7 @@ import numpy as np import raillabel +from raillabel.filter import IncludeObjectTypeFilter, IncludeSensorIdFilter, IncludeAnnotationTypeFilter, IncludeSensorTypeFilter, IncludeAttributesFilter from raillabel_providerkit._util._filters import filter_sensor_uids_by_type @@ -27,16 +28,16 @@ def validate_rail_side(scene: raillabel.Scene) -> list[str]: errors: list[str] = [] # Get a list of camera uids - cameras = filter_sensor_uids_by_type( - list(scene.sensors.values()), raillabel.format.SensorType.CAMERA - ) + cameras = list(scene.filter([IncludeSensorTypeFilter("camera")]).sensors.keys()) # Check per camera for camera in cameras: # Filter scene for track annotations in the selected camera sensor - filtered_scene = raillabel.filter( - scene, include_object_types=["track"], include_sensors=[camera] - ) + filtered_scene = scene.filter([ + IncludeObjectTypeFilter(["track"]), + IncludeSensorIdFilter([camera]), + IncludeAnnotationTypeFilter(["poly2d"]), + ]) # Check per frame for frame_uid, frame in filtered_scene.frames.items(): @@ -45,33 +46,34 @@ def validate_rail_side(scene: raillabel.Scene) -> list[str]: # Add errors if there is more than one left or right rail for object_uid, (left_count, right_count) in counts_per_track.items(): - if left_count > 1: - errors.append( - f"In sensor {camera} frame {frame_uid}, the track with" - f" object_uid {object_uid} has more than one ({left_count}) left rail." - ) - if right_count > 1: - errors.append( - f"In sensor {camera} frame {frame_uid}, the track with" - f" object_uid {object_uid} has more than one ({right_count}) right rail." - ) + if left_count > 1 or right_count > 1: + if left_count > 1: + errors.append( + f"In sensor {camera} frame {frame_uid}, the track with" + f" object_uid {object_uid} has more than one ({left_count}) left rail." + ) + if right_count > 1: + errors.append( + f"In sensor {camera} frame {frame_uid}, the track with" + f" object_uid {object_uid} has more than one ({right_count}) right rail." + ) + continue # If exactly one left and right rail exists, check if the track has its rails swapped # or intersects with itself - if left_count == 1 == right_count: - # Get the two annotations in question - left_rail: raillabel.format.Poly2d | None = _get_track_from_frame( - frame, object_uid, "leftRail" - ) - right_rail: raillabel.format.Poly2d | None = _get_track_from_frame( - frame, object_uid, "rightRail" - ) - if left_rail is None or right_rail is None: - continue - - swap_error: str | None = _check_rails_for_swap(left_rail, right_rail, frame_uid) - if swap_error is not None: - errors.append(swap_error) + # Get the two annotations in question + left_rail: raillabel.format.Poly2d | None = _get_track_from_frame( + frame, object_uid, "leftRail" + ) + right_rail: raillabel.format.Poly2d | None = _get_track_from_frame( + frame, object_uid, "rightRail" + ) + if left_rail is None or right_rail is None: + continue + + swap_error: str | None = _check_rails_for_swap(left_rail, right_rail, frame_uid) + if swap_error is not None: + errors.append(swap_error) return errors @@ -82,7 +84,7 @@ def _check_rails_for_swap( frame_uid: str | int = "unknown", ) -> str | None: # Ensure the rails belong to the same track - if left_rail.object.uid != right_rail.object.uid: + if left_rail.object_id != right_rail.object_id: return None max_common_y = _find_max_common_y(left_rail, right_rail) @@ -94,8 +96,8 @@ def _check_rails_for_swap( if left_x is None or right_x is None: return None - object_uid = left_rail.object.uid - sensor_uid = left_rail.sensor.uid if left_rail.sensor is not None else "unknown" + object_uid = left_rail.object_id + sensor_uid = left_rail.sensor_id if left_rail.sensor_id is not None else "unknown" if left_x >= right_x: return ( @@ -117,34 +119,31 @@ def _check_rails_for_swap( def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[str, tuple[int, int]]: - # For each track, the left and right rail counts are stored as a tuple (left, right) - counts: dict[str, tuple[int, int]] = {} + # For each track, the left and right rail counts are stored as a list (left, right) + counts: dict[str, list[int, int]] = {} # For each track, count the left and right rails - for object_uid, unfiltered_annotations in frame.object_data.items(): - # Ensure we work only on Poly2d annotations - poly2ds: list[raillabel.format.Poly2d] = _filter_for_poly2ds( - list(unfiltered_annotations.values()) - ) - - # Count left and right rails - left_count: int = 0 - right_count: int = 0 - for poly2d in poly2ds: - rail_side = poly2d.attributes["railSide"] - if rail_side == "leftRail": - left_count += 1 - elif rail_side == "rightRail": - right_count += 1 - else: - # NOTE: This is ignored because it is covered by validate_onthology - continue - - # Store counts of current track - counts[object_uid] = (left_count, right_count) + unfiltered_annotations = list(frame.annotations.values()) + # Ensure we work only on Poly2d annotations + poly2ds: list[raillabel.format.Poly2d] = _filter_for_poly2ds(unfiltered_annotations) + + # Count left and right rails + for poly2d in poly2ds: + object_id = poly2d.object_id + if object_id not in counts: + counts[object_id] = [0, 0] + + rail_side = poly2d.attributes["railSide"] + if rail_side == "leftRail": + counts[object_id][0] += 1 + elif rail_side == "rightRail": + counts[object_id][1] += 1 + else: + # NOTE: This is ignored because it is covered by validate_onthology + continue # Return results - return counts + return {key: tuple(value) for key, value in counts.items()} def _filter_for_poly2ds( @@ -269,11 +268,8 @@ def _find_x_by_y(y: float, poly2d: raillabel.format.Poly2d) -> float | None: def _get_track_from_frame( frame: raillabel.format.Frame, object_uid: str, rail_side: str ) -> raillabel.format.Poly2d | None: - if object_uid not in frame.object_data: - return None - - for annotation in frame.object_data[object_uid].values(): - if not isinstance(annotation, raillabel.format.Poly2d): + for annotation in frame.annotations.values(): + if annotation.object_id != object_uid: continue if "railSide" not in annotation.attributes: diff --git a/tests/conftest.py b/tests/conftest.py index 641e430..79914f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -98,7 +98,7 @@ def default_frame(empty_annotation) -> raillabel.format.Frame: @pytest.fixture def empty_frame() -> raillabel.format.Frame: - return raillabel.format.Frame(uid=0, timestamp=None, sensors={}, frame_data={}, annotations={}) + return raillabel.format.Frame(timestamp=None, sensors={}, frame_data={}, annotations={}) @pytest.fixture diff --git a/tests/test_raillabel_providerkit/_util/test_warning.py b/tests/test_raillabel_providerkit/_util/test_warning.py index 291e1b8..73885e2 100644 --- a/tests/test_raillabel_providerkit/_util/test_warning.py +++ b/tests/test_raillabel_providerkit/_util/test_warning.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -from raillabel._util._warning import _warning, _WarningsLogger +from raillabel_providerkit._util._warning import _warning, _WarningsLogger def test_issue_warning(): diff --git a/tests/test_raillabel_providerkit/validation/conftest.py b/tests/test_raillabel_providerkit/validation/conftest.py index 84355ce..36e0d2d 100644 --- a/tests/test_raillabel_providerkit/validation/conftest.py +++ b/tests/test_raillabel_providerkit/validation/conftest.py @@ -1,9 +1,2 @@ # Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from validate_onthology.test_validate_onthology import ( - demo_onthology, - invalid_onthology_scene, - metadata, - valid_onthology_scene, -) +# SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/tests/test_raillabel_providerkit/validation/validate_onthology/test_onthology_schema_v1.py b/tests/test_raillabel_providerkit/validation/validate_onthology/test_onthology_schema_v1.py deleted file mode 100644 index f9f83fa..0000000 --- a/tests/test_raillabel_providerkit/validation/validate_onthology/test_onthology_schema_v1.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import os -import typing as t -from pathlib import Path - -import jsonschema -import pytest -import yaml - -# == Fixtures ========================= - - -@pytest.fixture -def schema_path() -> Path: - return ( - Path(__file__).parent.parent.parent.parent.parent - / "raillabel_providerkit" - / "validation" - / "validate_onthology" - / "onthology_schema_v1.yaml" - ) - - -@pytest.fixture -def schema(schema_path) -> dict: - with schema_path.open() as f: - schema_data = yaml.safe_load(f) - return schema_data - - -@pytest.fixture -def validator(schema) -> jsonschema.Draft7Validator: - return jsonschema.Draft7Validator(schema) - - -def schema_errors(data: dict, validator: jsonschema.Draft7Validator) -> t.List[str]: - errors = [] - - for error in validator.iter_errors(data): - errors.append("$" + error.json_path[1:] + ": " + str(error.message)) - - return errors - - -# == Tests ========================= - - -def test_classes(validator): - data = { - "person": {}, - "train": {}, - } - - assert schema_errors(data, validator) == [] - - -def test_class_unsupported_field(validator): - data = {"person": {"UNSUPPORTED_FIELD": {}}} - - assert schema_errors(data, validator) == [ - "$.person: Additional properties are not allowed ('UNSUPPORTED_FIELD' was unexpected)", - ] - - -def test_attributes_field(validator): - data = {"person": {"attributes": {}}} - - assert schema_errors(data, validator) == [] - - -def test_attribute_string(validator): - data = {"person": {"attributes": {"name": "string"}}} - - assert schema_errors(data, validator) == [] - - -def test_attribute_integer(validator): - data = {"person": {"attributes": {"number_of_fingers": "integer"}}} - - assert schema_errors(data, validator) == [] - - -def test_attribute_boolean(validator): - data = {"person": {"attributes": {"number_of_fingers": "boolean"}}} - - assert schema_errors(data, validator) == [] - - -def test_attribute_single_select(validator): - data = { - "person": { - "attributes": { - "carrying": { - "type": "single-select", - "options": ["groceries", "a baby", "the new Slicer-Dicer 3000 (WOW!)"], - } - } - } - } - - assert schema_errors(data, validator) == [] - - -def test_attribute_multi_select(validator): - data = { - "person": { - "attributes": { - "carrying": { - "type": "multi-select", - "options": ["groceries", "a baby", "the new Slicer-Dicer 3000 (WOW!)"], - } - } - } - } - - assert schema_errors(data, validator) == [] - - -def test_attribute_vector(validator): - data = {"person": {"attributes": {"carrying": "vector"}}} - - assert schema_errors(data, validator) == [] - - -def test_sensor_types(validator): - data = { - "person": { - "sensor_types": { - "camera": {}, - "lidar": {}, - "radar": {}, - } - } - } - - assert schema_errors(data, validator) == [] - - -def test_sensor_types_unsupported_type(validator): - data = { - "person": { - "sensor_types": { - "UNSUPPORTED_SENSOR_TYPE": {}, - "lidar": {}, - } - } - } - - assert len(schema_errors(data, validator)) == 1 - - -def test_sensor_type_attributes(validator): - data = { - "person": { - "sensor_types": { - "lidar": {"attributes": {"name": "string"}}, - } - } - } - - assert schema_errors(data, validator) == [] - - -if __name__ == "__main__": - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel_providerkit/validation/validate_onthology/test_validate_onthology.py b/tests/test_raillabel_providerkit/validation/validate_onthology/test_validate_onthology.py deleted file mode 100644 index 7a173ae..0000000 --- a/tests/test_raillabel_providerkit/validation/validate_onthology/test_validate_onthology.py +++ /dev/null @@ -1,632 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import typing as t -from uuid import uuid4 - -import pytest -import raillabel - -from raillabel_providerkit import exceptions -from raillabel_providerkit.validation import validate_onthology - -# == Helpers ========================== - - -def make_dict_with_uids(objects: list) -> dict: - return {obj.uid: obj for obj in objects} - - -def build_scene( - sensors: t.List[raillabel.format.Sensor], - objects: t.List[raillabel.format.Object], - annotations: t.List[t.Type[raillabel.format._ObjectAnnotation]], -) -> raillabel.Scene: - if type(sensors) == list: - sensors = make_dict_with_uids(sensors) - - return raillabel.Scene( - metadata=raillabel.format.Metadata(schema_version="1.0.0"), - sensors=sensors, - objects=make_dict_with_uids(objects), - frames={0: raillabel.format.Frame(uid=0, annotations=make_dict_with_uids(annotations))}, - ) - - -@pytest.fixture -def sensors() -> t.List[raillabel.format.Sensor]: - return { - "rgb_middle": raillabel.format.Sensor( - uid="rgb_middle", - type=raillabel.format.SensorType.CAMERA, - ), - "lidar": raillabel.format.Sensor( - uid="lidar", - type=raillabel.format.SensorType.LIDAR, - ), - "radar": raillabel.format.Sensor( - uid="radar", - type=raillabel.format.SensorType.RADAR, - ), - } - - -@pytest.fixture -def object_person() -> raillabel.format.Object: - return raillabel.format.Object( - uid="973ecc31-36f3-4b41-a1d8-9b584f265822", - name="person_0000", - type="person", - ) - - -def build_object(type: str) -> raillabel.format.Object: - return raillabel.format.Object( - uid=uuid4, - name=type, - type=type, - ) - - -def build_annotation( - object: raillabel.format.Object, - uid: str = "a3f3abe5-082d-42ce-966c-bae9c6dae9d9", - sensor: raillabel.format.Sensor = raillabel.format.Sensor( - uid="rgb_middle", - type=raillabel.format.SensorType.CAMERA, - ), - attributes: dict = {}, -) -> raillabel.format.Bbox: - return raillabel.format.Bbox( - uid=uid, - object=object, - sensor=sensor, - attributes=attributes, - pos=[], - size=[], - ) - - -# == Fixtures ========================= - - -@pytest.fixture -def metadata(): - return raillabel.format.Metadata(schema_version="1.0.0") - - -@pytest.fixture -def demo_onthology() -> dict: - return { - "person": {}, - "train": {}, - } - - -@pytest.fixture -def valid_onthology_scene(metadata) -> raillabel.Scene: - return raillabel.format.Scene( - metadata=metadata, - objects=make_dict_with_uids( - [ - build_object("person"), - build_object("person"), - build_object("train"), - ] - ), - ) - - -@pytest.fixture -def invalid_onthology_scene(metadata) -> raillabel.Scene: - return raillabel.format.Scene( - metadata=metadata, - objects=make_dict_with_uids( - [ - build_object("INVALID_CLASS"), - ] - ), - ) - - -# == Tests ============================ - - -def test_onthology_schema_invalid(): - onthology = {"person": {"INVALID_FIELD": {}}} - - with pytest.raises(exceptions.OnthologySchemaError): - validate_onthology(None, onthology) - - -def test_valid_classes(metadata): - onthology = { - "person": {}, - "train": {}, - } - - scene = raillabel.format.Scene( - metadata=metadata, - objects=make_dict_with_uids( - [ - build_object("person"), - build_object("person"), - build_object("train"), - ] - ), - ) - - assert validate_onthology(scene, onthology) == [] - - -def test_invalid_class(metadata): - onthology = { - "person": {}, - "train": {}, - } - - scene = raillabel.format.Scene( - metadata=metadata, - objects=make_dict_with_uids( - [ - build_object("person"), - build_object("UNDEFINED_CLASS"), - ] - ), - ) - - assert validate_onthology(scene, onthology) == ["Object type 'UNDEFINED_CLASS' is not defined."] - - -def test_undefined_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"UNKNOWN_ATTRIBUTE": 10} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Undefined attribute 'UNKNOWN_ATTRIBUTE' in annotation {annotation.uid}." - ] - - -def test_missing_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {"number_of_fingers": "integer"}}, - } - - annotation = build_annotation(object=object_person, sensor=sensors["lidar"], attributes={}) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Missing attribute 'number_of_fingers' in annotation {annotation.uid}." - ] - - -def test_valid_integer_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {"number_of_fingers": "integer"}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"number_of_fingers": 10} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_integer_attribute_type(sensors, object_person): - onthology = { - "person": {"attributes": {"number_of_fingers": "integer"}}, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"number_of_fingers": "THIS SHOULD BE AN INTEGER"}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'number_of_fingers' of annotation {annotation.uid} is of type 'str' (should be 'int')." - ] - - -def test_valid_string_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {"first_name": "string"}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"first_name": "Gudrun"} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_string_attribute_type(sensors, object_person): - onthology = { - "person": {"attributes": {"first_name": "string"}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"first_name": 42} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'first_name' of annotation {annotation.uid} is of type 'int' (should be 'str')." - ] - - -def test_valid_boolean_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {"has_cool_blue_shirt": "boolean"}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"has_cool_blue_shirt": False} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_boolean_attribute_type(sensors, object_person): - onthology = { - "person": {"attributes": {"has_cool_blue_shirt": "boolean"}}, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"has_cool_blue_shirt": "NO THE SHIRT IS ORANGE ... AND THIS SHOULD BE A BOOL"}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'has_cool_blue_shirt' of annotation {annotation.uid} is of type 'str' (should be 'bool')." - ] - - -def test_valid_vector_attribute(sensors, object_person): - onthology = { - "person": {"attributes": {"favorite_pizzas": "vector"}}, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"favorite_pizzas": ["Diavolo", "Neapolitan", "Quattro Formaggi"]}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_vector_attribute_type(sensors, object_person): - onthology = { - "person": {"attributes": {"favorite_pizzas": "vector"}}, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={ - "favorite_pizzas": "does not like pizza (ikr)... THIS SHOULD BE A VECTOR AS WELL" - }, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'favorite_pizzas' of annotation {annotation.uid} is of type 'str' (should be 'list')." - ] - - -def test_valid_single_select_attribute(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "single-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"carries": "groceries"} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_single_select_attribute_type(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "single-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"carries": False} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'carries' of annotation {annotation.uid} is of type 'bool' (should be 'str')." - ] - - -def test_single_select_attribute_undefined_option(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "single-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"carries": "something very unexpected"}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'carries' of annotation {annotation.uid} has an undefined value " - + "'something very unexpected' (defined options: 'a baby', 'groceries', 'the SlicerDicer 3000™ (wow!)')." - ] - - -def test_valid_multi_select_attribute(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "multi-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"carries": ["groceries", "a baby"]}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_false_multi_select_attribute_type(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "multi-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"carries": "a baby"} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'carries' of annotation {annotation.uid} is of type 'str' (should be 'list')." - ] - - -def test_multi_select_attribute_undefined_option(sensors, object_person): - onthology = { - "person": { - "attributes": { - "carries": { - "type": "multi-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - } - } - }, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={"carries": ["a baby", "something very unexpected"]}, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'carries' of annotation {annotation.uid} has an undefined value " - + "'something very unexpected' (defined options: 'a baby', 'groceries', 'the SlicerDicer 3000™ (wow!)')." - ] - - -def test_multiple_attributes_valid(sensors, object_person): - onthology = { - "person": { - "attributes": { - "number_of_fingers": "integer", - "first_name": "string", - "carries": { - "type": "single-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - }, - } - } - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={ - "carries": "groceries", - "number_of_fingers": 9, - "first_name": "Brunhilde", - }, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_multiple_attributes_invalid(sensors, object_person): - onthology = { - "person": { - "attributes": { - "number_of_fingers": "integer", - "first_name": "string", - "carries": { - "type": "single-select", - "options": [ - "groceries", - "a baby", - "the SlicerDicer 3000™ (wow!)", - ], - }, - } - } - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={ - "carries": "something very unexpected", - "number_of_fingers": 9, - "first_name": True, - }, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - c = validate_onthology(scene, onthology) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'carries' of annotation {annotation.uid} has an undefined value " - + "'something very unexpected' (defined options: 'a baby', 'groceries', 'the SlicerDicer 3000™ (wow!)').", - f"Attribute 'first_name' of annotation {annotation.uid} is of type 'bool' (should be 'str').", - ] - - -def test_valid_sensor_type_attribute(sensors, object_person): - onthology = { - "person": {"sensor_types": {"lidar": {"attributes": {"number_of_fingers": "integer"}}}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"number_of_fingers": 10} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_invalid_sensor_type_attribute(sensors, object_person): - onthology = { - "person": {"sensor_types": {"lidar": {"attributes": {"number_of_fingers": "integer"}}}}, - } - - annotation = build_annotation( - object=object_person, sensor=sensors["lidar"], attributes={"number_of_fingers": "None"} - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Attribute 'number_of_fingers' of annotation {annotation.uid} is of type 'str' (should be 'int')." - ] - - -def test_valid_sensor_type_attributes_and_attributes(sensors, object_person): - onthology = { - "person": { - "attributes": {"first_name": "string"}, - "sensor_types": {"lidar": {"attributes": {"number_of_fingers": "integer"}}}, - }, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={ - "number_of_fingers": 10, - "first_name": "Brunhilde", - }, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [] - - -def test_invalid_sensor_type_attributes_and_attributes(sensors, object_person): - onthology = { - "person": { - "attributes": {"first_name": "string"}, - "sensor_types": {"lidar": {"attributes": {"number_of_fingers": "integer"}}}, - }, - } - - annotation = build_annotation( - object=object_person, - sensor=sensors["lidar"], - attributes={ - "first_name": "Brunhilde", - }, - ) - - scene = build_scene(sensors, [object_person], [annotation]) - assert validate_onthology(scene, onthology) == [ - f"Missing attribute 'number_of_fingers' in annotation {annotation.uid}." - ] - - -if __name__ == "__main__": - pytest.main([__file__, "--disable-pytest-warnings", "--cache-clear", "-v"]) diff --git a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py index 2b69bf3..2322dfa 100644 --- a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py +++ b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py @@ -3,6 +3,8 @@ import pytest import raillabel +from raillabel.scene_builder import SceneBuilder +from raillabel.format import Poly2d, Point2d from raillabel_providerkit.validation.validate_rail_side.validate_rail_side import ( validate_rail_side, @@ -10,34 +12,34 @@ ) -@pytest.fixture -def example_camera_1() -> raillabel.format.Sensor: - return raillabel.format.Sensor( - uid="rgb_center", - type=raillabel.format.SensorType.CAMERA, - ) +# @pytest.fixture +# def example_camera_1() -> raillabel.format.Camera: +# return raillabel.format.Sensor( +# uid="rgb_center", +# type=raillabel.format.SensorType.CAMERA, +# ) -@pytest.fixture -def example_camera_2() -> raillabel.format.Sensor: - return raillabel.format.Sensor( - uid="ir_center", - type=raillabel.format.SensorType.CAMERA, - ) +# @pytest.fixture +# def example_camera_2() -> raillabel.format.Sensor: +# return raillabel.format.Sensor( +# uid="ir_center", +# type=raillabel.format.SensorType.CAMERA, +# ) -@pytest.fixture -def example_track_1() -> raillabel.format.Object: - return raillabel.format.Object( - uid="a1082ef9-555b-4b69-a888-7da531d8a2eb", name="track0001", type="track" - ) +# @pytest.fixture +# def example_track_1() -> raillabel.format.Object: +# return raillabel.format.Object( +# uid="a1082ef9-555b-4b69-a888-7da531d8a2eb", name="track0001", type="track" +# ) -@pytest.fixture -def example_track_2() -> raillabel.format.Object: - return raillabel.format.Object( - uid="6e92e7af-3bc8-4225-b538-16d19e3f8aa7", name="track0002", type="track" - ) +# @pytest.fixture +# def example_track_2() -> raillabel.format.Object: +# return raillabel.format.Object( +# uid="6e92e7af-3bc8-4225-b538-16d19e3f8aa7", name="track0002", type="track" +# ) def test_count_rails_per_track_in_frame__empty(empty_frame): @@ -46,423 +48,428 @@ def test_count_rails_per_track_in_frame__empty(empty_frame): assert len(results) == 0 -def test_count_rails_per_track_in_frame__many_rails_for_one_track( - empty_frame, example_camera_1, example_track_1 -): - frame = empty_frame - sensor = example_camera_1 - object = example_track_1 - - LEFT_COUNT = 32 - RIGHT_COUNT = 42 - - for i in range(LEFT_COUNT): - uid = f"test_left_{i}" - frame.annotations[uid] = raillabel.format.Poly2d( - uid=uid, - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - - for i in range(RIGHT_COUNT): - uid = f"test_right_{i}" - frame.annotations[uid] = raillabel.format.Poly2d( - uid=uid, - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - - results = _count_rails_per_track_in_frame(frame) - assert len(results) == 1 - assert object.uid in results.keys() - assert results[object.uid] == (LEFT_COUNT, RIGHT_COUNT) - - -def test_count_rails_per_track_in_frame__many_rails_for_two_tracks( - empty_frame, example_camera_1, example_track_1, example_track_2 -): - frame = empty_frame - sensor = example_camera_1 - object1 = example_track_1 - object2 = example_track_2 - - LEFT_COUNT = 32 - RIGHT_COUNT = 42 - - for object in [object1, object2]: - for i in range(LEFT_COUNT): - uid = f"test_left_{i}_object_{object.uid}" - frame.annotations[uid] = raillabel.format.Poly2d( - uid=uid, - object=object, - sensor=sensor, +# def test_count_rails_per_track_in_frame__many_rails_for_one_track( +# empty_frame, example_camera_1, example_track_1 +# ): + + +# frame = empty_frame +# sensor = example_camera_1 +# object = example_track_1 + +# LEFT_COUNT = 32 +# RIGHT_COUNT = 42 + +# for i in range(LEFT_COUNT): +# uid = f"test_left_{i}" +# frame.annotations[uid] = raillabel.format.Poly2d( +# uid=uid, +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) + +# for i in range(RIGHT_COUNT): +# uid = f"test_right_{i}" +# frame.annotations[uid] = raillabel.format.Poly2d( +# uid=uid, +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) + +# results = _count_rails_per_track_in_frame(frame) +# assert len(results) == 1 +# assert object.uid in results.keys() +# assert results[object.uid] == (LEFT_COUNT, RIGHT_COUNT) + + +# def test_count_rails_per_track_in_frame__many_rails_for_two_tracks( +# empty_frame, example_camera_1, example_track_1, example_track_2 +# ): +# frame = empty_frame +# sensor = example_camera_1 +# object1 = example_track_1 +# object2 = example_track_2 + +# LEFT_COUNT = 32 +# RIGHT_COUNT = 42 + +# for object in [object1, object2]: +# for i in range(LEFT_COUNT): +# uid = f"test_left_{i}_object_{object.uid}" +# frame.annotations[uid] = raillabel.format.Poly2d( +# uid=uid, +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) + +# for i in range(RIGHT_COUNT): +# uid = f"test_right_{i}_object_{object.uid}" +# frame.annotations[uid] = raillabel.format.Poly2d( +# uid=uid, +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) + +# results = _count_rails_per_track_in_frame(frame) +# assert len(results) == 2 +# assert object1.uid in results.keys() +# assert object2.uid in results.keys() +# assert results[object1.uid] == (LEFT_COUNT, RIGHT_COUNT) +# assert results[object2.uid] == (LEFT_COUNT, RIGHT_COUNT) + + +def test_validate_rail_side__no_errors(): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( points=[ raillabel.format.Point2d(0, 0), raillabel.format.Point2d(0, 1), ], closed=False, attributes={"railSide": "leftRail"}, - ) - - for i in range(RIGHT_COUNT): - uid = f"test_right_{i}_object_{object.uid}" - frame.annotations[uid] = raillabel.format.Poly2d( - uid=uid, - object=object, - sensor=sensor, + object_id="IGNORE_THIS", + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_middle", + ) + .add_annotation( + annotation=Poly2d( points=[ raillabel.format.Point2d(1, 0), raillabel.format.Point2d(1, 1), ], closed=False, attributes={"railSide": "rightRail"}, - ) - - results = _count_rails_per_track_in_frame(frame) - assert len(results) == 2 - assert object1.uid in results.keys() - assert object2.uid in results.keys() - assert results[object1.uid] == (LEFT_COUNT, RIGHT_COUNT) - assert results[object2.uid] == (LEFT_COUNT, RIGHT_COUNT) - - -def test_validate_rail_side__no_errors(empty_scene, empty_frame, example_camera_1, example_track_1): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 0 - - -def test_validate_rail_side__rail_sides_switched( - empty_scene, empty_frame, example_camera_1, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 1 - - -def test_validate_rail_side__rail_sides_intersect_at_top( - empty_scene, empty_frame, example_camera_1, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(20, 0), - raillabel.format.Point2d(20, 10), - raillabel.format.Point2d(10, 20), - raillabel.format.Point2d(10, 100), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(10, 0), - raillabel.format.Point2d(10, 10), - raillabel.format.Point2d(20, 20), - raillabel.format.Point2d(20, 100), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 1 - - -def test_validate_rail_side__rail_sides_correct_with_early_end_of_one_side( - empty_scene, empty_frame, example_camera_1, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(70, 0), - raillabel.format.Point2d(30, 20), - raillabel.format.Point2d(15, 40), - raillabel.format.Point2d(10, 50), - raillabel.format.Point2d(10, 100), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(20, 50), - raillabel.format.Point2d(20, 100), - ], - closed=False, - attributes={"railSide": "rightRail"}, + object_id="IGNORE_THIS", + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_middle", + ) + .result ) - scene.frames[frame.uid] = frame actual = validate_rail_side(scene) assert len(actual) == 0 -def test_validate_rail_side__two_left_rails( - empty_scene, empty_frame, example_camera_1, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "leftRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 1 - - -def test_validate_rail_side__two_right_rails( - empty_scene, empty_frame, example_camera_1, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor = example_camera_1 - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 1 - - -def test_validate_rail_side__two_sensors_with_two_right_rails_each( - empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor1 = example_camera_1 - sensor2 = example_camera_2 - for sensor in [sensor1, sensor2]: - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor1, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( - uid="be7d136a-8364-4fbd-b098-6f4a21205d22", - object=object, - sensor=sensor1, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( - uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", - object=object, - sensor=sensor2, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["89f8cf2c-1dc9-4956-9661-f1054ff069f9"] = raillabel.format.Poly2d( - uid="89f8cf2c-1dc9-4956-9661-f1054ff069f9", - object=object, - sensor=sensor2, - points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 2 - - -def test_validate_rail_side__two_sensors_with_one_right_rail_each( - empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 -): - scene = empty_scene - object = example_track_1 - scene.objects[object.uid] = object - sensor1 = example_camera_1 - sensor2 = example_camera_2 - for sensor in [sensor1, sensor2]: - scene.sensors[sensor.uid] = sensor - frame = empty_frame - frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( - uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", - object=object, - sensor=sensor1, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( - uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", - object=object, - sensor=sensor2, - points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), - ], - closed=False, - attributes={"railSide": "rightRail"}, - ) - scene.frames[frame.uid] = frame - - actual = validate_rail_side(scene) - assert len(actual) == 0 +# def test_validate_rail_side__rail_sides_switched( +# empty_scene, empty_frame, example_camera_1, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor = example_camera_1 +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 1 + + +# def test_validate_rail_side__rail_sides_intersect_at_top( +# empty_scene, empty_frame, example_camera_1, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor = example_camera_1 +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(20, 0), +# raillabel.format.Point2d(20, 10), +# raillabel.format.Point2d(10, 20), +# raillabel.format.Point2d(10, 100), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(10, 0), +# raillabel.format.Point2d(10, 10), +# raillabel.format.Point2d(20, 20), +# raillabel.format.Point2d(20, 100), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 1 + + +# def test_validate_rail_side__rail_sides_correct_with_early_end_of_one_side( +# empty_scene, empty_frame, example_camera_1, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor = example_camera_1 +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(70, 0), +# raillabel.format.Point2d(30, 20), +# raillabel.format.Point2d(15, 40), +# raillabel.format.Point2d(10, 50), +# raillabel.format.Point2d(10, 100), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(20, 50), +# raillabel.format.Point2d(20, 100), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 0 + + +# def test_validate_rail_side__two_left_rails( +# empty_scene, empty_frame, example_camera_1, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor = example_camera_1 +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "leftRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 1 + + +# def test_validate_rail_side__two_right_rails( +# empty_scene, empty_frame, example_camera_1, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor = example_camera_1 +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 1 + + +# def test_validate_rail_side__two_sensors_with_two_right_rails_each( +# empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor1 = example_camera_1 +# sensor2 = example_camera_2 +# for sensor in [sensor1, sensor2]: +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor1, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( +# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", +# object=object, +# sensor=sensor1, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( +# uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", +# object=object, +# sensor=sensor2, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["89f8cf2c-1dc9-4956-9661-f1054ff069f9"] = raillabel.format.Poly2d( +# uid="89f8cf2c-1dc9-4956-9661-f1054ff069f9", +# object=object, +# sensor=sensor2, +# points=[ +# raillabel.format.Point2d(1, 0), +# raillabel.format.Point2d(1, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 2 + + +# def test_validate_rail_side__two_sensors_with_one_right_rail_each( +# empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 +# ): +# scene = empty_scene +# object = example_track_1 +# scene.objects[object.uid] = object +# sensor1 = example_camera_1 +# sensor2 = example_camera_2 +# for sensor in [sensor1, sensor2]: +# scene.sensors[sensor.uid] = sensor +# frame = empty_frame +# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( +# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", +# object=object, +# sensor=sensor1, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( +# uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", +# object=object, +# sensor=sensor2, +# points=[ +# raillabel.format.Point2d(0, 0), +# raillabel.format.Point2d(0, 1), +# ], +# closed=False, +# attributes={"railSide": "rightRail"}, +# ) +# scene.frames[frame.uid] = frame + +# actual = validate_rail_side(scene) +# assert len(actual) == 0 if __name__ == "__main__": From 5a986e108692cad96e552d835064bbe873ce2909 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 09:53:31 +0100 Subject: [PATCH 03/27] fix: raillabel deprecated imports --- .../format/understand_ai/test_uai_bounding_box_2d.py | 4 ++-- .../format/understand_ai/test_uai_bounding_box_3d.py | 4 ++-- .../format/understand_ai/test_uai_coordinate_system.py | 2 +- .../format/understand_ai/test_uai_frame.py | 4 ++-- .../format/understand_ai/test_uai_metadata.py | 2 +- .../format/understand_ai/test_uai_point_3d.py | 2 +- .../format/understand_ai/test_uai_polygon_2d.py | 4 ++-- .../format/understand_ai/test_uai_polyline_2d.py | 4 ++-- .../format/understand_ai/test_uai_quaternion.py | 2 +- .../format/understand_ai/test_uai_scene.py | 4 ++-- .../format/understand_ai/test_uai_segmentation_3d.py | 4 ++-- .../format/understand_ai/test_uai_sensor_reference.py | 2 +- .../format/understand_ai/test_uai_size_3d.py | 2 +- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_2d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_2d.py index a97eb7b..2ea2a17 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_2d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_2d.py @@ -4,8 +4,8 @@ from uuid import UUID import pytest -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit.format.understand_ai._translation import translate_class_id # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_3d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_3d.py index 8f2d74d..b72a4b3 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_3d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_bounding_box_3d.py @@ -4,8 +4,8 @@ from uuid import UUID import pytest -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit.format.understand_ai._translation import translate_class_id # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_coordinate_system.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_coordinate_system.py index 714ef0f..9cc3cb3 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_coordinate_system.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_coordinate_system.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_frame.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_frame.py index b4bdde8..f27cc32 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_frame.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_frame.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format -from raillabel._util._warning import _WarningsLogger +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit._util._warning import _WarningsLogger # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_metadata.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_metadata.py index fa58ec8..4e96e83 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_metadata.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_metadata.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_point_3d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_point_3d.py index de1b214..a482956 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_point_3d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_point_3d.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polygon_2d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polygon_2d.py index c3a69de..c99d153 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polygon_2d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polygon_2d.py @@ -4,8 +4,8 @@ from uuid import UUID import pytest -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit.format.understand_ai._translation import translate_class_id # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polyline_2d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polyline_2d.py index b4763fe..41d7c79 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polyline_2d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_polyline_2d.py @@ -4,8 +4,8 @@ from uuid import UUID import pytest -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit.format.understand_ai._translation import translate_class_id # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_quaternion.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_quaternion.py index 861b720..b29992a 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_quaternion.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_quaternion.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_scene.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_scene.py index 4c04b70..40894f3 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_scene.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_scene.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format -from raillabel._util._warning import _WarningsLogger +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit._util._warning import _WarningsLogger # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_segmentation_3d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_segmentation_3d.py index ee4e0c2..5395169 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_segmentation_3d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_segmentation_3d.py @@ -4,8 +4,8 @@ from uuid import UUID import pytest -import raillabel.format.understand_ai as uai_format -from raillabel.format.understand_ai._translation import translate_class_id +import raillabel_providerkit.format.understand_ai as uai_format +from raillabel_providerkit.format.understand_ai._translation import translate_class_id # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_sensor_reference.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_sensor_reference.py index 313e0d1..58cbaa6 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_sensor_reference.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_sensor_reference.py @@ -4,7 +4,7 @@ from decimal import Decimal import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= diff --git a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_size_3d.py b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_size_3d.py index b523e28..319cad5 100644 --- a/tests/test_raillabel_providerkit/format/understand_ai/test_uai_size_3d.py +++ b/tests/test_raillabel_providerkit/format/understand_ai/test_uai_size_3d.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel.format.understand_ai as uai_format +import raillabel_providerkit.format.understand_ai as uai_format # == Fixtures ========================= From da7a31721ad0998e0481cc5d66d54ee4685673ce Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 09:56:29 +0100 Subject: [PATCH 04/27] lint: Apply pre-commit changes --- .../validate_rail_side/validate_rail_side.py | 21 ++++++++++++------- .../validation/conftest.py | 2 +- .../test_validate_rail_side.py | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py index 2deb0d9..aa09b51 100644 --- a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py +++ b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py @@ -5,9 +5,12 @@ import numpy as np import raillabel -from raillabel.filter import IncludeObjectTypeFilter, IncludeSensorIdFilter, IncludeAnnotationTypeFilter, IncludeSensorTypeFilter, IncludeAttributesFilter - -from raillabel_providerkit._util._filters import filter_sensor_uids_by_type +from raillabel.filter import ( + IncludeAnnotationTypeFilter, + IncludeObjectTypeFilter, + IncludeSensorIdFilter, + IncludeSensorTypeFilter, +) def validate_rail_side(scene: raillabel.Scene) -> list[str]: @@ -33,11 +36,13 @@ def validate_rail_side(scene: raillabel.Scene) -> list[str]: # Check per camera for camera in cameras: # Filter scene for track annotations in the selected camera sensor - filtered_scene = scene.filter([ - IncludeObjectTypeFilter(["track"]), - IncludeSensorIdFilter([camera]), - IncludeAnnotationTypeFilter(["poly2d"]), - ]) + filtered_scene = scene.filter( + [ + IncludeObjectTypeFilter(["track"]), + IncludeSensorIdFilter([camera]), + IncludeAnnotationTypeFilter(["poly2d"]), + ] + ) # Check per frame for frame_uid, frame in filtered_scene.frames.items(): diff --git a/tests/test_raillabel_providerkit/validation/conftest.py b/tests/test_raillabel_providerkit/validation/conftest.py index 36e0d2d..dd5d085 100644 --- a/tests/test_raillabel_providerkit/validation/conftest.py +++ b/tests/test_raillabel_providerkit/validation/conftest.py @@ -1,2 +1,2 @@ # Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 \ No newline at end of file +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py index 2322dfa..6d5497f 100644 --- a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py +++ b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py @@ -51,7 +51,7 @@ def test_count_rails_per_track_in_frame__empty(empty_frame): # def test_count_rails_per_track_in_frame__many_rails_for_one_track( # empty_frame, example_camera_1, example_track_1 # ): - + # frame = empty_frame # sensor = example_camera_1 From 59d8fec10595ad2b66aa73ae7dc410b5453edf9f Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 10:13:04 +0100 Subject: [PATCH 05/27] fix: Refactor validate_rail_side for raillabel 4 --- .../validate_rail_side/validate_rail_side.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py index aa09b51..56cce82 100644 --- a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py +++ b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py @@ -3,6 +3,8 @@ from __future__ import annotations +from uuid import UUID + import numpy as np import raillabel from raillabel.filter import ( @@ -31,7 +33,7 @@ def validate_rail_side(scene: raillabel.Scene) -> list[str]: errors: list[str] = [] # Get a list of camera uids - cameras = list(scene.filter([IncludeSensorTypeFilter("camera")]).sensors.keys()) + cameras = list(scene.filter([IncludeSensorTypeFilter(["camera"])]).sensors.keys()) # Check per camera for camera in cameras: @@ -123,9 +125,9 @@ def _check_rails_for_swap( return None -def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[str, tuple[int, int]]: +def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[UUID, tuple[int, int]]: # For each track, the left and right rail counts are stored as a list (left, right) - counts: dict[str, list[int, int]] = {} + counts: dict[UUID, list[int]] = {} # For each track, count the left and right rails unfiltered_annotations = list(frame.annotations.values()) @@ -148,11 +150,14 @@ def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[str, continue # Return results - return {key: tuple(value) for key, value in counts.items()} + return { + object_id: (object_counts[0], object_counts[1]) + for object_id, object_counts in counts.items() + } def _filter_for_poly2ds( - unfiltered_annotations: list[type[raillabel.format._ObjectAnnotation]], + unfiltered_annotations: list, ) -> list[raillabel.format.Poly2d]: return [ annotation @@ -271,9 +276,12 @@ def _find_x_by_y(y: float, poly2d: raillabel.format.Poly2d) -> float | None: def _get_track_from_frame( - frame: raillabel.format.Frame, object_uid: str, rail_side: str + frame: raillabel.format.Frame, object_uid: UUID, rail_side: str ) -> raillabel.format.Poly2d | None: for annotation in frame.annotations.values(): + if not isinstance(annotation, raillabel.format.Poly2d): + continue + if annotation.object_id != object_uid: continue From 4eb13e7d0b3ee3302db293a398a448d24ff4bc89 Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 10:23:11 +0100 Subject: [PATCH 06/27] fix: test_validate_empty_frames for raillabel 4 --- tests/conftest.py | 14 +++++--------- .../test_validate_empty_frames.py | 6 ++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 79914f3..eac2d85 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import json import typing as t from pathlib import Path +from uuid import UUID import pytest import raillabel @@ -88,11 +89,10 @@ def empty_scene() -> raillabel.Scene: @pytest.fixture def default_frame(empty_annotation) -> raillabel.format.Frame: return raillabel.format.Frame( - uid=0, timestamp=None, sensors={}, frame_data={}, - annotations={"0fb4fc0b-3eeb-443a-8dd0-2caf9912d016": empty_annotation}, + annotations={UUID("0fb4fc0b-3eeb-443a-8dd0-2caf9912d016"): empty_annotation}, ) @@ -104,13 +104,9 @@ def empty_frame() -> raillabel.format.Frame: @pytest.fixture def empty_annotation() -> raillabel.format.Bbox: return raillabel.format.Bbox( - uid="1f654afe-0a18-497f-9db8-afac360ce94c", - object=raillabel.format.Object( - uid="7df959d7-0ec2-4722-8b62-bb2e529de2ec", - name="person0000", - type="person", - ), - sensor=None, + object_id=UUID("7df959d7-0ec2-4722-8b62-bb2e529de2ec"), + sensor_id="IGNORE_THIS", pos=raillabel.format.Point2d(0.0, 0.0), size=raillabel.format.Size2d(0.0, 0.0), + attributes={}, ) diff --git a/tests/test_raillabel_providerkit/validation/validate_empty_frame/test_validate_empty_frames.py b/tests/test_raillabel_providerkit/validation/validate_empty_frame/test_validate_empty_frames.py index 5b1f3c8..7f1eea1 100644 --- a/tests/test_raillabel_providerkit/validation/validate_empty_frame/test_validate_empty_frames.py +++ b/tests/test_raillabel_providerkit/validation/validate_empty_frame/test_validate_empty_frames.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel from raillabel_providerkit.validation.validate_empty_frames.validate_empty_frames import ( _is_frame_empty, @@ -10,9 +9,8 @@ ) -def test_is_frame_empty__true(): - frame = raillabel.format.Frame(uid=0, annotations={}) - assert _is_frame_empty(frame) +def test_is_frame_empty__true(empty_frame): + assert _is_frame_empty(empty_frame) def test_is_frame_empty__false(empty_annotation, empty_frame): From 9b8263cfa6815b5e876537bf650b1d45f03e336e Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 11:19:55 +0100 Subject: [PATCH 07/27] fix: Refactor test_validate_rail_side for raillabel 4 --- tests/conftest.py | 5 + .../test_validate_rail_side.py | 827 +++++++++--------- 2 files changed, 412 insertions(+), 420 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index eac2d85..b180a35 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -110,3 +110,8 @@ def empty_annotation() -> raillabel.format.Bbox: size=raillabel.format.Size2d(0.0, 0.0), attributes={}, ) + + +@pytest.fixture +def ignore_uuid() -> UUID: + return UUID("00000000-0000-0000-0000-000000000000") diff --git a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py index 6d5497f..32e61f2 100644 --- a/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py +++ b/tests/test_raillabel_providerkit/validation/validate_rail_side/test_validate_rail_side.py @@ -2,9 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel -from raillabel.scene_builder import SceneBuilder from raillabel.format import Poly2d, Point2d +from raillabel.scene_builder import SceneBuilder from raillabel_providerkit.validation.validate_rail_side.validate_rail_side import ( validate_rail_side, @@ -12,166 +11,262 @@ ) -# @pytest.fixture -# def example_camera_1() -> raillabel.format.Camera: -# return raillabel.format.Sensor( -# uid="rgb_center", -# type=raillabel.format.SensorType.CAMERA, -# ) +def test_count_rails_per_track_in_frame__empty(empty_frame): + frame = empty_frame + results = _count_rails_per_track_in_frame(frame) + assert len(results) == 0 -# @pytest.fixture -# def example_camera_2() -> raillabel.format.Sensor: -# return raillabel.format.Sensor( -# uid="ir_center", -# type=raillabel.format.SensorType.CAMERA, -# ) +def test_count_rails_per_track_in_frame__many_rails_for_one_track(ignore_uuid): + LEFT_COUNT = 32 + RIGHT_COUNT = 42 + TRACK_NAME = "track_0001" + builder = SceneBuilder.empty() -# @pytest.fixture -# def example_track_1() -> raillabel.format.Object: -# return raillabel.format.Object( -# uid="a1082ef9-555b-4b69-a888-7da531d8a2eb", name="track0001", type="track" -# ) + for _ in range(LEFT_COUNT): + builder = builder.add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name=TRACK_NAME, + sensor_id="rgb_center", + ) + for _ in range(RIGHT_COUNT): + builder = builder.add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name=TRACK_NAME, + sensor_id="rgb_center", + ) -# @pytest.fixture -# def example_track_2() -> raillabel.format.Object: -# return raillabel.format.Object( -# uid="6e92e7af-3bc8-4225-b538-16d19e3f8aa7", name="track0002", type="track" -# ) + scene = builder.result + frame = scene.frames[list(scene.frames.keys())[0]] + object_id = frame.annotations[list(frame.annotations.keys())[0]].object_id + results = _count_rails_per_track_in_frame(frame) + assert len(results) == 1 + assert object_id in results.keys() + assert results[object_id] == (LEFT_COUNT, RIGHT_COUNT) + + +def test_count_rails_per_track_in_frame__many_rails_for_two_tracks(ignore_uuid): + LEFT_COUNT = 32 + RIGHT_COUNT = 42 + + builder = SceneBuilder.empty() + + for track_name in ["track_0001", "track_0002"]: + for _ in range(LEFT_COUNT): + builder = builder.add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name=track_name, + sensor_id="rgb_center", + ) + + for _ in range(RIGHT_COUNT): + builder = builder.add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name=track_name, + sensor_id="rgb_center", + ) + + scene = builder.result + frame = scene.frames[list(scene.frames.keys())[0]] -def test_count_rails_per_track_in_frame__empty(empty_frame): - frame = empty_frame results = _count_rails_per_track_in_frame(frame) - assert len(results) == 0 + assert len(results) == 2 + + for object_id in scene.objects.keys(): + assert object_id in results.keys() + assert results[object_id] == (LEFT_COUNT, RIGHT_COUNT) + + +def test_validate_rail_side__no_errors(ignore_uuid): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 0 + + +def test_validate_rail_side__rail_sides_switched(ignore_uuid): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 1 -# def test_count_rails_per_track_in_frame__many_rails_for_one_track( -# empty_frame, example_camera_1, example_track_1 -# ): - - -# frame = empty_frame -# sensor = example_camera_1 -# object = example_track_1 - -# LEFT_COUNT = 32 -# RIGHT_COUNT = 42 - -# for i in range(LEFT_COUNT): -# uid = f"test_left_{i}" -# frame.annotations[uid] = raillabel.format.Poly2d( -# uid=uid, -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) - -# for i in range(RIGHT_COUNT): -# uid = f"test_right_{i}" -# frame.annotations[uid] = raillabel.format.Poly2d( -# uid=uid, -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) - -# results = _count_rails_per_track_in_frame(frame) -# assert len(results) == 1 -# assert object.uid in results.keys() -# assert results[object.uid] == (LEFT_COUNT, RIGHT_COUNT) - - -# def test_count_rails_per_track_in_frame__many_rails_for_two_tracks( -# empty_frame, example_camera_1, example_track_1, example_track_2 -# ): -# frame = empty_frame -# sensor = example_camera_1 -# object1 = example_track_1 -# object2 = example_track_2 - -# LEFT_COUNT = 32 -# RIGHT_COUNT = 42 - -# for object in [object1, object2]: -# for i in range(LEFT_COUNT): -# uid = f"test_left_{i}_object_{object.uid}" -# frame.annotations[uid] = raillabel.format.Poly2d( -# uid=uid, -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) - -# for i in range(RIGHT_COUNT): -# uid = f"test_right_{i}_object_{object.uid}" -# frame.annotations[uid] = raillabel.format.Poly2d( -# uid=uid, -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) - -# results = _count_rails_per_track_in_frame(frame) -# assert len(results) == 2 -# assert object1.uid in results.keys() -# assert object2.uid in results.keys() -# assert results[object1.uid] == (LEFT_COUNT, RIGHT_COUNT) -# assert results[object2.uid] == (LEFT_COUNT, RIGHT_COUNT) - - -def test_validate_rail_side__no_errors(): +def test_validate_rail_side__rail_sides_intersect_at_top(ignore_uuid): scene = ( SceneBuilder.empty() .add_annotation( annotation=Poly2d( points=[ - raillabel.format.Point2d(0, 0), - raillabel.format.Point2d(0, 1), + Point2d(20, 0), + Point2d(20, 10), + Point2d(10, 20), + Point2d(10, 100), ], closed=False, attributes={"railSide": "leftRail"}, - object_id="IGNORE_THIS", + object_id=ignore_uuid, sensor_id="IGNORE_THIS", ), object_name="track_0001", - sensor_id="rgb_middle", + sensor_id="rgb_center", ) .add_annotation( annotation=Poly2d( points=[ - raillabel.format.Point2d(1, 0), - raillabel.format.Point2d(1, 1), + Point2d(10, 0), + Point2d(10, 10), + Point2d(20, 20), + Point2d(20, 100), ], closed=False, attributes={"railSide": "rightRail"}, - object_id="IGNORE_THIS", + object_id=ignore_uuid, sensor_id="IGNORE_THIS", ), object_name="track_0001", - sensor_id="rgb_middle", + sensor_id="rgb_center", + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 1 + + +def test_validate_rail_side__rail_sides_correct_with_early_end_of_one_side(ignore_uuid): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(70, 0), + Point2d(30, 20), + Point2d(15, 40), + Point2d(10, 50), + Point2d(10, 100), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(20, 50), + Point2d(20, 100), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", ) .result ) @@ -180,296 +275,188 @@ def test_validate_rail_side__no_errors(): assert len(actual) == 0 -# def test_validate_rail_side__rail_sides_switched( -# empty_scene, empty_frame, example_camera_1, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor = example_camera_1 -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 1 - - -# def test_validate_rail_side__rail_sides_intersect_at_top( -# empty_scene, empty_frame, example_camera_1, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor = example_camera_1 -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(20, 0), -# raillabel.format.Point2d(20, 10), -# raillabel.format.Point2d(10, 20), -# raillabel.format.Point2d(10, 100), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(10, 0), -# raillabel.format.Point2d(10, 10), -# raillabel.format.Point2d(20, 20), -# raillabel.format.Point2d(20, 100), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 1 - - -# def test_validate_rail_side__rail_sides_correct_with_early_end_of_one_side( -# empty_scene, empty_frame, example_camera_1, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor = example_camera_1 -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(70, 0), -# raillabel.format.Point2d(30, 20), -# raillabel.format.Point2d(15, 40), -# raillabel.format.Point2d(10, 50), -# raillabel.format.Point2d(10, 100), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(20, 50), -# raillabel.format.Point2d(20, 100), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 0 - - -# def test_validate_rail_side__two_left_rails( -# empty_scene, empty_frame, example_camera_1, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor = example_camera_1 -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "leftRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 1 - - -# def test_validate_rail_side__two_right_rails( -# empty_scene, empty_frame, example_camera_1, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor = example_camera_1 -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 1 - - -# def test_validate_rail_side__two_sensors_with_two_right_rails_each( -# empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor1 = example_camera_1 -# sensor2 = example_camera_2 -# for sensor in [sensor1, sensor2]: -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor1, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["be7d136a-8364-4fbd-b098-6f4a21205d22"] = raillabel.format.Poly2d( -# uid="be7d136a-8364-4fbd-b098-6f4a21205d22", -# object=object, -# sensor=sensor1, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( -# uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", -# object=object, -# sensor=sensor2, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["89f8cf2c-1dc9-4956-9661-f1054ff069f9"] = raillabel.format.Poly2d( -# uid="89f8cf2c-1dc9-4956-9661-f1054ff069f9", -# object=object, -# sensor=sensor2, -# points=[ -# raillabel.format.Point2d(1, 0), -# raillabel.format.Point2d(1, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 2 - - -# def test_validate_rail_side__two_sensors_with_one_right_rail_each( -# empty_scene, empty_frame, example_camera_1, example_camera_2, example_track_1 -# ): -# scene = empty_scene -# object = example_track_1 -# scene.objects[object.uid] = object -# sensor1 = example_camera_1 -# sensor2 = example_camera_2 -# for sensor in [sensor1, sensor2]: -# scene.sensors[sensor.uid] = sensor -# frame = empty_frame -# frame.annotations["325b1f55-a2ef-475f-a780-13e1a9e823c3"] = raillabel.format.Poly2d( -# uid="325b1f55-a2ef-475f-a780-13e1a9e823c3", -# object=object, -# sensor=sensor1, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# frame.annotations["f6db5b28-bdcd-437f-bf39-c044bb516de8"] = raillabel.format.Poly2d( -# uid="f6db5b28-bdcd-437f-bf39-c044bb516de8", -# object=object, -# sensor=sensor2, -# points=[ -# raillabel.format.Point2d(0, 0), -# raillabel.format.Point2d(0, 1), -# ], -# closed=False, -# attributes={"railSide": "rightRail"}, -# ) -# scene.frames[frame.uid] = frame - -# actual = validate_rail_side(scene) -# assert len(actual) == 0 +def test_validate_rail_side__two_left_rails(ignore_uuid): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "leftRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 1 + + +def test_validate_rail_side__two_right_rails(ignore_uuid): + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id="rgb_center", + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 1 + + +def test_validate_rail_side__two_sensors_with_two_right_rails_each(ignore_uuid): + SENSOR1_ID = "rgb_center" + SENSOR2_ID = "ir_center" + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR1_ID, + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR1_ID, + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR2_ID, + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(1, 0), + Point2d(1, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR2_ID, + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 2 + + +def test_validate_rail_side__two_sensors_with_one_right_rail_each(ignore_uuid): + SENSOR1_ID = "rgb_center" + SENSOR2_ID = "ir_center" + scene = ( + SceneBuilder.empty() + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR1_ID, + ) + .add_annotation( + annotation=Poly2d( + points=[ + Point2d(0, 0), + Point2d(0, 1), + ], + closed=False, + attributes={"railSide": "rightRail"}, + object_id=ignore_uuid, + sensor_id="IGNORE_THIS", + ), + object_name="track_0001", + sensor_id=SENSOR2_ID, + ) + .result + ) + + actual = validate_rail_side(scene) + assert len(actual) == 0 if __name__ == "__main__": From 4635a9f9dcac9d92b5c4348d168a2dac12caf3b2 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 11:20:56 +0100 Subject: [PATCH 08/27] feat: validate_schema for missing field --- pyproject.toml | 1 + raillabel_providerkit/validation/__init__.py | 3 +- .../validation/validate_schema/__init__.py | 8 ++++ .../validate_schema/validate_schema.py | 41 +++++++++++++++++++ .../validate_schema/test_validate_schema.py | 28 +++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 raillabel_providerkit/validation/validate_schema/__init__.py create mode 100644 raillabel_providerkit/validation/validate_schema/validate_schema.py create mode 100644 tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py diff --git a/pyproject.toml b/pyproject.toml index 3931199..cbd68f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "raillabel>=4.0.0", "pyyaml>=6.0.0", "numpy>=1.24.4", + "pydantic<3.0.0", ] [project.urls] diff --git a/raillabel_providerkit/validation/__init__.py b/raillabel_providerkit/validation/__init__.py index cf219d6..fd71faf 100644 --- a/raillabel_providerkit/validation/__init__.py +++ b/raillabel_providerkit/validation/__init__.py @@ -3,5 +3,6 @@ """Package for validating raillabel data regarding the format requirements.""" from .validate_onthology.validate_onthology import validate_onthology +from .validate_schema import validate_schema -__all__ = ["validate_onthology"] +__all__ = ["validate_onthology", "validate_schema"] diff --git a/raillabel_providerkit/validation/validate_schema/__init__.py b/raillabel_providerkit/validation/validate_schema/__init__.py new file mode 100644 index 0000000..7859e36 --- /dev/null +++ b/raillabel_providerkit/validation/validate_schema/__init__.py @@ -0,0 +1,8 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +"""Code for schema validation.""" + +from .validate_schema import validate_schema + +__all__ = ["validate_schema"] diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py new file mode 100644 index 0000000..c9f8570 --- /dev/null +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -0,0 +1,41 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import json + +from pydantic_core import ValidationError +from raillabel.json_format import JSONScene + + +def validate_schema(data: dict) -> list[str]: + """Validate a scene for adherence to the raillabel schema.""" + try: + JSONScene(**data) + except ValidationError as errors: + return _make_errors_readable(errors) + else: + return [] + + +def _make_errors_readable(errors: ValidationError) -> list[str]: + readable_errors = [] + for error in json.loads(errors.json()): + if error["type"] == "missing": + readable_errors.append(_convert_missing_error_to_string(error)) + else: + raise ValueError + + return readable_errors + + +def _convert_missing_error_to_string(error: dict) -> str: + return f"{_build_error_path(error["loc"][:-1])}: required field '{error["loc"][-1]}' is missing." + + +def _build_error_path(loc: list[str]) -> str: + path = "$" + for part in loc: + path += f".{part}" + return path diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py new file mode 100644 index 0000000..10a113f --- /dev/null +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -0,0 +1,28 @@ +# Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import pytest + +from raillabel_providerkit.validation import validate_schema + + +def test_no_errors__empty(): + data = {"openlabel": {"metadata": {"schema_version": "1.0.0"}}} + + actual = validate_schema(data) + assert actual == [] + + +def test_required_field_missing(): + data = {"openlabel": {}} + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel" in actual[0] + assert "required" in actual[0] + assert "metadata" in actual[0] + assert "missing" in actual[0] + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 216139f9f9d75cc3aa3f91edd187810e20e2cade Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 11:25:23 +0100 Subject: [PATCH 09/27] feat: validate_schema for unexpected field --- .../validation/validate_schema/validate_schema.py | 6 ++++++ .../validation/validate_schema/test_validate_schema.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index c9f8570..c3320a4 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -24,6 +24,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: for error in json.loads(errors.json()): if error["type"] == "missing": readable_errors.append(_convert_missing_error_to_string(error)) + elif error["type"] == "extra_forbidden": + readable_errors.append(_convert_unexpected_field_error_to_string(error)) else: raise ValueError @@ -34,6 +36,10 @@ def _convert_missing_error_to_string(error: dict) -> str: return f"{_build_error_path(error["loc"][:-1])}: required field '{error["loc"][-1]}' is missing." +def _convert_unexpected_field_error_to_string(error: dict) -> str: + return f"{_build_error_path(error["loc"][:-1])}: found unexpected field '{error["loc"][-1]}'." + + def _build_error_path(loc: list[str]) -> str: path = "$" for part in loc: diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index 10a113f..ab65ccb 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -24,5 +24,15 @@ def test_required_field_missing(): assert "missing" in actual[0] +def test_unsupported_field(): + data = {"openlabel": {"metadata": {"schema_version": "1.0.0"}, "UNSUPPORTED_FIELD": {}}} + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel" in actual[0] + assert "unexpected" in actual[0] + assert "UNSUPPORTED_FIELD" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 65c1e82f46e74a7bdef8bff2fb9fedb234e66b7f Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 12:56:35 +0100 Subject: [PATCH 10/27] feat: validate_schema for literal error --- .../validate_schema/validate_schema.py | 9 +++++++++ .../validate_schema/test_validate_schema.py | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index c3320a4..82ea0cb 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -26,6 +26,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_missing_error_to_string(error)) elif error["type"] == "extra_forbidden": readable_errors.append(_convert_unexpected_field_error_to_string(error)) + elif error["type"] == "literal_error": + readable_errors.append(_convert_literal_error_to_string(error)) else: raise ValueError @@ -40,6 +42,13 @@ def _convert_unexpected_field_error_to_string(error: dict) -> str: return f"{_build_error_path(error["loc"][:-1])}: found unexpected field '{error["loc"][-1]}'." +def _convert_literal_error_to_string(error: dict) -> str: + return ( + f"{_build_error_path(error["loc"])}: value '{error["input"]}' does not match allowed values " + f"({error["ctx"]["expected"]})." + ) + + def _build_error_path(loc: list[str]) -> str: path = "$" for part in loc: diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index ab65ccb..f3fc990 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -14,13 +14,13 @@ def test_no_errors__empty(): def test_required_field_missing(): - data = {"openlabel": {}} + data = {"openlabel": {"metadata": {}}} actual = validate_schema(data) assert len(actual) == 1 - assert "$.openlabel" in actual[0] + assert "$.openlabel.metadata" in actual[0] assert "required" in actual[0] - assert "metadata" in actual[0] + assert "schema_version" in actual[0] assert "missing" in actual[0] @@ -34,5 +34,16 @@ def test_unsupported_field(): assert "UNSUPPORTED_FIELD" in actual[0] +def test_unexpected_value(): + data = {"openlabel": {"metadata": {"schema_version": "SOMETHING UNSUPPORTED"}}} + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.metadata.schema_version" in actual[0] + assert "value" in actual[0] + assert "SOMETHING UNSUPPORTED" in actual[0] + assert "'1.0.0'" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From bdddbc10df379282be07b787bb67a920392d56df Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:16:50 +0100 Subject: [PATCH 11/27] feat: validate_schema for false bool error --- .../validate_schema/validate_schema.py | 19 +++++++--- .../validate_schema/test_validate_schema.py | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 82ea0cb..4853238 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -28,12 +28,21 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_unexpected_field_error_to_string(error)) elif error["type"] == "literal_error": readable_errors.append(_convert_literal_error_to_string(error)) + elif error["type"] in ["bool_type", "bool_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "bool")) else: raise ValueError return readable_errors +def _build_error_path(loc: list[str]) -> str: + path = "$" + for part in loc: + path += f".{part}" + return path + + def _convert_missing_error_to_string(error: dict) -> str: return f"{_build_error_path(error["loc"][:-1])}: required field '{error["loc"][-1]}' is missing." @@ -49,8 +58,8 @@ def _convert_literal_error_to_string(error: dict) -> str: ) -def _build_error_path(loc: list[str]) -> str: - path = "$" - for part in loc: - path += f".{part}" - return path +def _convert_false_type_error_to_string(error: dict, target_type: str) -> str: + return ( + f"{_build_error_path(error["loc"][:-1])}: value '{error["input"]}' could not be interpreted " + f"as {target_type}." + ) diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index f3fc990..36ab020 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -45,5 +45,41 @@ def test_unexpected_value(): assert "'1.0.0'" in actual[0] +def test_wrong_type_bool(): + data = { + "openlabel": { + "metadata": {"schema_version": "1.0.0"}, + "frames": { + "1": { + "objects": { + "113c2b35-0965-4c80-a212-08b262e94203": { + "object_data": { + "poly2d": [ + { + "closed": "NOT A BOOLEAN", + "name": "not_important", + "val": [], + "mode": "MODE_POLY2D_ABSOLUTE", + "coordinate_system": "not_important", + } + ] + } + } + } + } + }, + } + } + + actual = validate_schema(data) + assert len(actual) == 1 + assert ( + "$.openlabel.frames.1.objects.113c2b35-0965-4c80-a212-08b262e94203.object_data.poly2d.0" + in actual[0] + ) + assert "bool" in actual[0] + assert "NOT A BOOLEAN" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 17795a4f53dcd395a5c00014cef9db5baf93d5a0 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:21:25 +0100 Subject: [PATCH 12/27] feat: validate_schema for int parsing error --- .../validation/validate_schema/validate_schema.py | 2 ++ .../validation/validate_schema/test_validate_schema.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 4853238..81f9124 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -30,6 +30,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_literal_error_to_string(error)) elif error["type"] in ["bool_type", "bool_parsing"]: readable_errors.append(_convert_false_type_error_to_string(error, "bool")) + elif error["type"] in ["int_type", "int_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "int")) else: raise ValueError diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index 36ab020..64ddf2c 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -81,5 +81,15 @@ def test_wrong_type_bool(): assert "NOT A BOOLEAN" in actual[0] +def test_wrong_type_int(): + data = {"openlabel": {"metadata": {"schema_version": "1.0.0"}, "frames": {"NOT AN INT": {}}}} + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.frames" in actual[0] + assert "int" in actual[0] + assert "NOT AN INT" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From ce578d725ef79d5414d902405a5236a2ba904f19 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:38:11 +0100 Subject: [PATCH 13/27] feat: validate_schema for str parsing error --- .../validate_schema/validate_schema.py | 16 +++++++++++----- .../validate_schema/test_validate_schema.py | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 81f9124..704890b 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -30,8 +30,12 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_literal_error_to_string(error)) elif error["type"] in ["bool_type", "bool_parsing"]: readable_errors.append(_convert_false_type_error_to_string(error, "bool")) - elif error["type"] in ["int_type", "int_parsing"]: + elif error["type"] in ["int_type", "int_parsing", "int_from_float"]: readable_errors.append(_convert_false_type_error_to_string(error, "int")) + elif error["type"] in ["decimal_type", "decimal_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "Decimal")) + elif error["type"] in ["string_type", "string_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "str")) else: raise ValueError @@ -61,7 +65,9 @@ def _convert_literal_error_to_string(error: dict) -> str: def _convert_false_type_error_to_string(error: dict, target_type: str) -> str: - return ( - f"{_build_error_path(error["loc"][:-1])}: value '{error["input"]}' could not be interpreted " - f"as {target_type}." - ) + if "[key]" in error["loc"]: + error_path = _build_error_path(error["loc"][:-2]) + else: + error_path = _build_error_path(error["loc"]) + + return f"{error_path}: value '{error["input"]}' could not be interpreted " f"as {target_type}." diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index 64ddf2c..b5bcd0f 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -74,7 +74,7 @@ def test_wrong_type_bool(): actual = validate_schema(data) assert len(actual) == 1 assert ( - "$.openlabel.frames.1.objects.113c2b35-0965-4c80-a212-08b262e94203.object_data.poly2d.0" + "$.openlabel.frames.1.objects.113c2b35-0965-4c80-a212-08b262e94203.object_data.poly2d.0.closed:" in actual[0] ) assert "bool" in actual[0] @@ -86,10 +86,20 @@ def test_wrong_type_int(): actual = validate_schema(data) assert len(actual) == 1 - assert "$.openlabel.frames" in actual[0] + assert "$.openlabel.frames:" in actual[0] assert "int" in actual[0] assert "NOT AN INT" in actual[0] +def test_wrong_type_string(): + data = {"openlabel": {"metadata": {"schema_version": "1.0.0", "comment": False}}} + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.metadata.comment:" in actual[0] + assert "str" in actual[0] + assert "False" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From f91e4f7fd20c06bd53b328ef36454905b1a99a32 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:43:10 +0100 Subject: [PATCH 14/27] feat: validate_schema for float parsing error --- .../validate_schema/validate_schema.py | 2 ++ .../validate_schema/test_validate_schema.py | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 704890b..4341bd9 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -36,6 +36,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_false_type_error_to_string(error, "Decimal")) elif error["type"] in ["string_type", "string_parsing"]: readable_errors.append(_convert_false_type_error_to_string(error, "str")) + elif error["type"] in ["float_type", "float_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "float")) else: raise ValueError diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index b5bcd0f..a20d802 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -101,5 +101,29 @@ def test_wrong_type_string(): assert "False" in actual[0] +def test_wrong_type_float(): + data = { + "openlabel": { + "metadata": {"schema_version": "1.0.0"}, + "coordinate_systems": { + "rgb_middle": { + "pose_wrt_parent": { + "translation": (None, 0.0, 0.0), + "quaternion": (0.0, 0.0, 0.0, 0.0), + }, + "parent": "", + "type": "sensor", + } + }, + } + } + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.coordinate_systems.rgb_middle.pose_wrt_parent.translation.0:" in actual[0] + assert "float" in actual[0] + assert "None" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 595dbcec8367bc277517fb387315ba4ed2dc5253 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:48:42 +0100 Subject: [PATCH 15/27] feat: validate_schema for uuid parsing error --- .../validate_schema/validate_schema.py | 4 +++- .../validate_schema/test_validate_schema.py | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 4341bd9..8f045da 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -19,7 +19,7 @@ def validate_schema(data: dict) -> list[str]: return [] -def _make_errors_readable(errors: ValidationError) -> list[str]: +def _make_errors_readable(errors: ValidationError) -> list[str]: # noqa: C901 readable_errors = [] for error in json.loads(errors.json()): if error["type"] == "missing": @@ -38,6 +38,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: readable_errors.append(_convert_false_type_error_to_string(error, "str")) elif error["type"] in ["float_type", "float_parsing"]: readable_errors.append(_convert_false_type_error_to_string(error, "float")) + elif error["type"] in ["uuid_type", "uuid_parsing"]: + readable_errors.append(_convert_false_type_error_to_string(error, "UUID")) else: raise ValueError diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index a20d802..a5e8d0e 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -125,5 +125,25 @@ def test_wrong_type_float(): assert "None" in actual[0] +def test_wrong_type_uuid(): + data = { + "openlabel": { + "metadata": {"schema_version": "1.0.0"}, + "objects": { + "NOT A VALID UUID": { + "name": "person_0001", + "type": "person", + } + }, + } + } + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.objects:" in actual[0] + assert "UUID" in actual[0] + assert "NOT A VALID UUID" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 2792a76ccad6e14d8fa478c80c2fa9657d342744 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:57:58 +0100 Subject: [PATCH 16/27] feat: validate_schema for tuple too long --- .../validate_schema/validate_schema.py | 9 +++++++ .../validate_schema/test_validate_schema.py | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 8f045da..30e2602 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -40,6 +40,8 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: # noqa: C901 readable_errors.append(_convert_false_type_error_to_string(error, "float")) elif error["type"] in ["uuid_type", "uuid_parsing"]: readable_errors.append(_convert_false_type_error_to_string(error, "UUID")) + elif error["type"] == "too_long": + readable_errors.append(_convert_too_long_error_to_string(error)) else: raise ValueError @@ -75,3 +77,10 @@ def _convert_false_type_error_to_string(error: dict, target_type: str) -> str: error_path = _build_error_path(error["loc"]) return f"{error_path}: value '{error["input"]}' could not be interpreted " f"as {target_type}." + + +def _convert_too_long_error_to_string(error: dict) -> str: + return ( + f"{_build_error_path(error["loc"])}: should have length of {error["ctx"]["actual_length"]} " + f"but has length of {error["ctx"]["max_length"]}." + ) diff --git a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py index a5e8d0e..632cfc1 100644 --- a/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py +++ b/tests/test_raillabel_providerkit/validation/validate_schema/test_validate_schema.py @@ -145,5 +145,30 @@ def test_wrong_type_uuid(): assert "NOT A VALID UUID" in actual[0] +def test_tuple_too_long(): + data = { + "openlabel": { + "metadata": {"schema_version": "1.0.0"}, + "coordinate_systems": { + "rgb_middle": { + "pose_wrt_parent": { + "translation": (0.0, 0.0, 0.0, 0.0), # should have length of 3 + "quaternion": (0.0, 0.0, 0.0, 0.0), + }, + "parent": "", + "type": "sensor", + } + }, + } + } + + actual = validate_schema(data) + assert len(actual) == 1 + assert "$.openlabel.coordinate_systems.rgb_middle.pose_wrt_parent.translation:" in actual[0] + assert "length" in actual[0] + assert "4" in actual[0] + assert "3" in actual[0] + + if __name__ == "__main__": pytest.main([__file__, "-v"]) From 32f8ddf11f366a3924ba7a17f89cd09389a13a2d Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 13:58:31 +0100 Subject: [PATCH 17/27] feat: validate_schema for unsupported error --- .../validation/validate_schema/validate_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 30e2602..ea35c4b 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -43,7 +43,7 @@ def _make_errors_readable(errors: ValidationError) -> list[str]: # noqa: C901 elif error["type"] == "too_long": readable_errors.append(_convert_too_long_error_to_string(error)) else: - raise ValueError + readable_errors.append(str(error)) return readable_errors From fa1e6255a98072c3813c4b578754bb561a595d0f Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 15:44:21 +0100 Subject: [PATCH 18/27] refactor: remove unused filter_sensor_uids_by_type --- raillabel_providerkit/_util/_filters.py | 27 -------- .../_util/test_filters.py | 61 ------------------- 2 files changed, 88 deletions(-) delete mode 100644 raillabel_providerkit/_util/_filters.py delete mode 100644 tests/test_raillabel_providerkit/_util/test_filters.py diff --git a/raillabel_providerkit/_util/_filters.py b/raillabel_providerkit/_util/_filters.py deleted file mode 100644 index 434c7ab..0000000 --- a/raillabel_providerkit/_util/_filters.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import raillabel - - -def filter_sensor_uids_by_type( - sensors: list[raillabel.format.Sensor], sensor_type: raillabel.format.SensorType -) -> set[str]: - """Get the uids of all given sensors matching the given SensorType. - - Parameters - ---------- - sensors : list[raillabel.format.Sensor] - The sensors to filter. - sensor_type : raillabel.format.SensorType - The SensorType to filter by. - - Returns - ------- - set[str] - The list of uids of matching sensors. - - """ - return {sensor.uid for sensor in sensors if sensor.type == sensor_type} diff --git a/tests/test_raillabel_providerkit/_util/test_filters.py b/tests/test_raillabel_providerkit/_util/test_filters.py deleted file mode 100644 index 2728049..0000000 --- a/tests/test_raillabel_providerkit/_util/test_filters.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -import pytest -import raillabel - -from raillabel_providerkit._util._filters import filter_sensor_uids_by_type - - -@pytest.fixture -def sensor_types() -> list[raillabel.format.SensorType]: - return [sensor_type for sensor_type in raillabel.format.SensorType] - - -def test_filter_sensor_uids_by_type__empty(sensor_types): - sensors = [] - for sensor_type in sensor_types: - assert len(filter_sensor_uids_by_type(sensors, sensor_type)) == 0 - - -def test_filter_sensor_uids_by_type__exactly_one_match(sensor_types): - # Create a list of sensors where each sensor type occurs exactly once - sensors = [] - for i in range(len(sensor_types)): - sensors.append(raillabel.format.Sensor(uid=f"test_{i}", type=sensor_types[i])) - - # Ensure the filter works for each sensor type - for sensor_type in sensor_types: - results = filter_sensor_uids_by_type(sensors, sensor_type) - assert len(results) == 1 - # Assert the result is of correct type - matches = 0 - for sensor in sensors: - if sensor.uid in results: - assert sensor.type == sensor_type - matches += 1 - assert matches == len(results) - - -def test_filter_sensor_uids_by_type__multiple_matches(sensor_types): - # Create a list of sensors where each sensor type occurs three times - sensors = [] - i = 0 - for sensor_type in sensor_types: - for j in range(3): - sensors.append(raillabel.format.Sensor(uid=f"test_{i}", type=sensor_type)) - i += 1 - - # Ensure the filter works for each sensor type - for sensor_type in sensor_types: - results = filter_sensor_uids_by_type(sensors, sensor_type) - assert len(results) == 3 - # Assert the results are of correct type - matches = 0 - for sensor in sensors: - if sensor.uid in results: - assert sensor.type == sensor_type - matches += 1 - assert matches == len(results) From b0e8c1b7105c934921ba98ca189e3a1b12b83cbe Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Mon, 25 Nov 2024 15:46:29 +0100 Subject: [PATCH 19/27] refactor: Reduce comments in validate_rail_side --- .../validate_rail_side/validate_rail_side.py | 119 +++++++++--------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py index 56cce82..0fbca8f 100644 --- a/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py +++ b/raillabel_providerkit/validation/validate_rail_side/validate_rail_side.py @@ -32,109 +32,106 @@ def validate_rail_side(scene: raillabel.Scene) -> list[str]: """ errors: list[str] = [] - # Get a list of camera uids - cameras = list(scene.filter([IncludeSensorTypeFilter(["camera"])]).sensors.keys()) + camera_uids = list(scene.filter([IncludeSensorTypeFilter(["camera"])]).sensors.keys()) - # Check per camera - for camera in cameras: - # Filter scene for track annotations in the selected camera sensor + for camera_uid in camera_uids: filtered_scene = scene.filter( [ IncludeObjectTypeFilter(["track"]), - IncludeSensorIdFilter([camera]), + IncludeSensorIdFilter([camera_uid]), IncludeAnnotationTypeFilter(["poly2d"]), ] ) - # Check per frame for frame_uid, frame in filtered_scene.frames.items(): - # Count rails per track counts_per_track = _count_rails_per_track_in_frame(frame) - # Add errors if there is more than one left or right rail for object_uid, (left_count, right_count) in counts_per_track.items(): - if left_count > 1 or right_count > 1: - if left_count > 1: - errors.append( - f"In sensor {camera} frame {frame_uid}, the track with" - f" object_uid {object_uid} has more than one ({left_count}) left rail." - ) - if right_count > 1: - errors.append( - f"In sensor {camera} frame {frame_uid}, the track with" - f" object_uid {object_uid} has more than one ({right_count}) right rail." - ) + context = { + "frame_uid": frame_uid, + "object_uid": object_uid, + "camera_uid": camera_uid, + } + + count_errors = _check_rail_counts(context, left_count, right_count) + exactly_one_left_and_right_rail_exist = count_errors != [] + if exactly_one_left_and_right_rail_exist: + errors.extend(count_errors) continue - # If exactly one left and right rail exists, check if the track has its rails swapped - # or intersects with itself - # Get the two annotations in question - left_rail: raillabel.format.Poly2d | None = _get_track_from_frame( - frame, object_uid, "leftRail" - ) - right_rail: raillabel.format.Poly2d | None = _get_track_from_frame( - frame, object_uid, "rightRail" - ) + left_rail = _get_track_from_frame(frame, object_uid, "leftRail") + right_rail = _get_track_from_frame(frame, object_uid, "rightRail") if left_rail is None or right_rail is None: continue - swap_error: str | None = _check_rails_for_swap(left_rail, right_rail, frame_uid) - if swap_error is not None: - errors.append(swap_error) + errors.extend( + _check_rails_for_swap_or_intersection(left_rail, right_rail, frame_uid) + ) + + return errors + +def _check_rail_counts(context: dict, left_count: int, right_count: int) -> list[str]: + errors = [] + if left_count > 1: + errors.append( + f"In sensor {context['camera_uid']} frame {context['frame_uid']}, the track with" + f" object_uid {context['object_uid']} has more than one ({left_count}) left rail." + ) + if right_count > 1: + errors.append( + f"In sensor {context['camera_uid']} frame {context['frame_uid']}, the track with" + f" object_uid {context['object_uid']} has more than one ({right_count}) right rail." + ) return errors -def _check_rails_for_swap( +def _check_rails_for_swap_or_intersection( left_rail: raillabel.format.Poly2d, right_rail: raillabel.format.Poly2d, frame_uid: str | int = "unknown", -) -> str | None: - # Ensure the rails belong to the same track +) -> list[str]: if left_rail.object_id != right_rail.object_id: - return None + return [] max_common_y = _find_max_common_y(left_rail, right_rail) if max_common_y is None: - return None + return [] left_x = _find_x_by_y(max_common_y, left_rail) right_x = _find_x_by_y(max_common_y, right_rail) if left_x is None or right_x is None: - return None + return [] object_uid = left_rail.object_id sensor_uid = left_rail.sensor_id if left_rail.sensor_id is not None else "unknown" if left_x >= right_x: - return ( + return [ f"In sensor {sensor_uid} frame {frame_uid}, the track with" f" object_uid {object_uid} has its rails swapped." f" At the maximum common y={max_common_y}, the left rail has x={left_x}" f" while the right rail has x={right_x}." - ) + ] intersect_interval = _find_intersect_interval(left_rail, right_rail) if intersect_interval is not None: - return ( + return [ f"In sensor {sensor_uid} frame {frame_uid}, the track with" f" object_uid {object_uid} intersects with itself." f" The left and right rail intersect in y interval {intersect_interval}." - ) + ] - return None + return [] def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[UUID, tuple[int, int]]: - # For each track, the left and right rail counts are stored as a list (left, right) + """For each track, count the left and right rails.""" counts: dict[UUID, list[int]] = {} - # For each track, count the left and right rails unfiltered_annotations = list(frame.annotations.values()) - # Ensure we work only on Poly2d annotations poly2ds: list[raillabel.format.Poly2d] = _filter_for_poly2ds(unfiltered_annotations) - # Count left and right rails for poly2d in poly2ds: object_id = poly2d.object_id if object_id not in counts: @@ -149,7 +146,6 @@ def _count_rails_per_track_in_frame(frame: raillabel.format.Frame) -> dict[UUID, # NOTE: This is ignored because it is covered by validate_onthology continue - # Return results return { object_id: (object_counts[0], object_counts[1]) for object_id, object_counts in counts.items() @@ -169,16 +165,14 @@ def _filter_for_poly2ds( def _find_intersect_interval( line1: raillabel.format.Poly2d, line2: raillabel.format.Poly2d ) -> tuple[float, float] | None: - # If the two polylines intersect anywhere, return the y interval where they intersect. - - # Get all y values where either polyline has points - y_values: list[float] = sorted( + """If the two polylines intersect anywhere, return the y interval where they intersect.""" + y_values_with_points_in_either_polyline: list[float] = sorted( _get_y_of_all_points_of_poly2d(line1).union(_get_y_of_all_points_of_poly2d(line2)) ) order: bool | None = None last_y: float | None = None - for y in y_values: + for y in y_values_with_points_in_either_polyline: x1 = _find_x_by_y(y, line1) x2 = _find_x_by_y(y, line2) @@ -191,9 +185,9 @@ def _find_intersect_interval( new_order = x1 < x2 - if order is not None and new_order != order and last_y is not None: - # The order has flipped. There is an intersection between previous and current y - return (last_y, y) + order_has_flipped = order is not None and new_order != order and last_y is not None + if order_has_flipped: + return (last_y, y) # type: ignore # noqa: PGH003 order = new_order last_y = y @@ -208,21 +202,20 @@ def _find_max_y(poly2d: raillabel.format.Poly2d) -> float: def _find_max_common_y( line1: raillabel.format.Poly2d, line2: raillabel.format.Poly2d ) -> float | None: - if len(line1.points) == 0 or len(line2.points) == 0: - # One of the lines is empty + one_line_is_empty = len(line1.points) == 0 or len(line2.points) == 0 + if one_line_is_empty: return None max_y_of_line1: float = _find_max_y(line1) - if _y_in_poly2d(max_y_of_line1, line2): - # The highest y is the bottom of line 1 + highest_y_is_bottom_of_line1 = _y_in_poly2d(max_y_of_line1, line2) + if highest_y_is_bottom_of_line1: return max_y_of_line1 max_y_of_line2: float = _find_max_y(line2) - if _y_in_poly2d(max_y_of_line2, line1): - # The highest y is the bottom of line 2 + highest_y_is_bottom_of_line2 = _y_in_poly2d(max_y_of_line2, line1) + if highest_y_is_bottom_of_line2: return max_y_of_line2 - # There is no y overlap return None From 71703d57a655a097dc48e029fa24389c2ed13d70 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:03:58 +0100 Subject: [PATCH 20/27] feat: validate_schema in validate --- raillabel_providerkit/validation/validate.py | 21 +++++++------------ .../validation/test_validate.py | 14 +++++++------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/raillabel_providerkit/validation/validate.py b/raillabel_providerkit/validation/validate.py index 2933b70..f2c8b72 100644 --- a/raillabel_providerkit/validation/validate.py +++ b/raillabel_providerkit/validation/validate.py @@ -3,24 +3,16 @@ from __future__ import annotations -from pathlib import Path +from . import validate_schema -import raillabel -from . import validate_onthology - - -def validate(scene: raillabel.Scene, onthology: dict | Path) -> list[str]: +def validate(scene_dict: dict) -> list[str]: """Validate a scene based on the Deutsche Bahn Requirements. Parameters ---------- - scene : raillabel.Scene - The scene containing the annotations. - onthology : dict or Path - Onthology YAML-data or file containing a information about all classes and their - attributes. The onthology must adhere to the onthology_schema. If a path is provided, the - file is loaded as a YAML. + scene_dict : dict + The scene as a dictionary directly from `json.load()` in the raillabel format. Returns ------- @@ -31,6 +23,9 @@ def validate(scene: raillabel.Scene, onthology: dict | Path) -> list[str]: """ errors = [] - errors += validate_onthology(scene, onthology) + errors.extend(validate_schema(scene_dict)) + + if len(errors) > 0: + return errors return errors diff --git a/tests/test_raillabel_providerkit/validation/test_validate.py b/tests/test_raillabel_providerkit/validation/test_validate.py index 0ac5b60..ee5c160 100644 --- a/tests/test_raillabel_providerkit/validation/test_validate.py +++ b/tests/test_raillabel_providerkit/validation/test_validate.py @@ -5,15 +5,17 @@ from raillabel_providerkit import validate -# == Tests ============================ +def test_no_errors_in_empty_scene(): + scene_dict = {"openlabel": {"metadata": {"schema_version": "1.0.0"}}} + actual = validate(scene_dict) + assert len(actual) == 0 -def test_no_errors(demo_onthology, valid_onthology_scene): - assert validate(valid_onthology_scene, demo_onthology) == [] - -def test_onthology_errors(demo_onthology, invalid_onthology_scene): - assert len(validate(invalid_onthology_scene, demo_onthology)) == 1 +def test_schema_errors(): + scene_dict = {"openlabel": {}} + actual = validate(scene_dict) + assert len(actual) == 1 if __name__ == "__main__": From c20df02d7f2238709efd7c97ebe78b4c9917dc46 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:20:53 +0100 Subject: [PATCH 21/27] refactor: disable functionality that is probably not used --- .../loader_classes/loader_understand_ai.py | 19 +++-- .../test_loader_understand_ai.py | 74 +++++++++---------- .../convert/test_convert.py | 18 ++--- 3 files changed, 55 insertions(+), 56 deletions(-) diff --git a/raillabel_providerkit/convert/loader_classes/loader_understand_ai.py b/raillabel_providerkit/convert/loader_classes/loader_understand_ai.py index dfe384d..fa9f4c8 100644 --- a/raillabel_providerkit/convert/loader_classes/loader_understand_ai.py +++ b/raillabel_providerkit/convert/loader_classes/loader_understand_ai.py @@ -7,9 +7,9 @@ from pathlib import Path import jsonschema -import raillabel +from raillabel import Scene +from raillabel.json_format import JSONScene -from raillabel_providerkit._util._warning import _WarningsLogger from raillabel_providerkit.format import understand_ai as uai_format from ._loader_abc import LoaderABC @@ -52,18 +52,17 @@ def load(self, data: dict, validate_schema: bool = False) -> uai_format.Scene: The loaded scene with the data. """ + raise NotImplementedError( + "We were not sure if this class is even used anymore. If you see this error, contact us " # noqa: EM101 + "and we will re-implement the class." + ) + if validate_schema: self.validate_schema(data) - with _WarningsLogger() as logger: - data_converted_to_raillabel = uai_format.Scene.fromdict(data).to_raillabel() - - raillabel_loader = raillabel.load_.loader_classes.LoaderRailLabel() - raillabel_scene = raillabel_loader.load(data_converted_to_raillabel, validate=False) - - self.warnings = logger.warnings + raillabel_loader.warnings + data_converted_to_raillabel = uai_format.Scene.fromdict(data).to_raillabel() - return raillabel_scene + return Scene.from_json(JSONScene(**data_converted_to_raillabel)) def supports(self, data: dict) -> bool: """Determine if the loader is suitable for the data (lightweight). diff --git a/tests/test_raillabel_providerkit/convert/loader_classes/test_loader_understand_ai.py b/tests/test_raillabel_providerkit/convert/loader_classes/test_loader_understand_ai.py index ed38471..9251e7d 100644 --- a/tests/test_raillabel_providerkit/convert/loader_classes/test_loader_understand_ai.py +++ b/tests/test_raillabel_providerkit/convert/loader_classes/test_loader_understand_ai.py @@ -2,64 +2,64 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import raillabel +from raillabel import Scene +from raillabel.json_format import JSONScene from raillabel_providerkit.convert.loader_classes.loader_understand_ai import LoaderUnderstandAi -def test_supports__true(json_data): - assert LoaderUnderstandAi().supports(json_data["understand_ai_real_life"]) +# def test_supports__true(json_data): +# assert LoaderUnderstandAi().supports(json_data["understand_ai_real_life"]) -def test_supports__false(json_data): - data = json_data["understand_ai_real_life"] - del data["metadata"]["project_id"] - assert not LoaderUnderstandAi().supports(data) +# def test_supports__false(json_data): +# data = json_data["understand_ai_real_life"] +# del data["metadata"]["project_id"] +# assert not LoaderUnderstandAi().supports(data) -def test_validate_schema__real_life_file__no_errors(json_data): - actual = LoaderUnderstandAi().validate_schema(json_data["understand_ai_real_life"]) - assert actual == [] +# def test_validate_schema__real_life_file__no_errors(json_data): +# actual = LoaderUnderstandAi().validate_schema(json_data["understand_ai_real_life"]) +# assert actual == [] -def test_validate_schema__real_life_file__errors(json_data): - data = json_data["understand_ai_real_life"] - del data["coordinateSystems"][0]["topic"] +# def test_validate_schema__real_life_file__errors(json_data): +# data = json_data["understand_ai_real_life"] +# del data["coordinateSystems"][0]["topic"] - actual = LoaderUnderstandAi().validate_schema(json_data["understand_ai_real_life"]) - assert len(actual) == 1 - assert "topic" in actual[0] +# actual = LoaderUnderstandAi().validate_schema(json_data["understand_ai_real_life"]) +# assert len(actual) == 1 +# assert "topic" in actual[0] -def test_load(json_data): - input_data_raillabel = remove_non_parsed_fields(json_data["openlabel_v1_short"]) - input_data_uai = json_data["understand_ai_t4_short"] +# def test_load(json_data): +# input_data_raillabel = remove_non_parsed_fields(json_data["openlabel_v1_short"]) +# input_data_uai = json_data["understand_ai_t4_short"] - scene_ground_truth = raillabel.load_.loader_classes.loader_raillabel.LoaderRailLabel().load( - input_data_raillabel, validate=False - ) - scene = LoaderUnderstandAi().load(input_data_uai, validate_schema=False) +# scene_ground_truth = Scene.from_json(JSONScene(**input_data_raillabel)) +# scene = LoaderUnderstandAi().load(input_data_uai, validate_schema=False) - scene.metadata = scene_ground_truth.metadata - assert scene == scene_ground_truth +# scene.metadata = scene_ground_truth.metadata +# assert scene.frames[0].annotations == scene_ground_truth.frames[0].annotations +# assert scene == scene_ground_truth -def remove_non_parsed_fields(raillabel_data: dict) -> dict: - """Return RailLabel file with frame_data and poly3ds removed.""" +# def remove_non_parsed_fields(raillabel_data: dict) -> dict: +# """Return RailLabel file with frame_data and poly3ds removed.""" - for frame in raillabel_data["openlabel"]["frames"].values(): - if "frame_data" in frame["frame_properties"]: - del frame["frame_properties"]["frame_data"] +# for frame in raillabel_data["openlabel"]["frames"].values(): +# if "frame_data" in frame["frame_properties"]: +# del frame["frame_properties"]["frame_data"] - for object_id, object in list(frame["objects"].items()): - if "poly3d" not in object["object_data"]: - continue +# for object_id, object in list(frame["objects"].items()): +# if "poly3d" not in object["object_data"]: +# continue - del object["object_data"]["poly3d"] - if len(object["object_data"]) == 0: - del frame["objects"][object_id] +# del object["object_data"]["poly3d"] +# if len(object["object_data"]) == 0: +# del frame["objects"][object_id] - return raillabel_data +# return raillabel_data if __name__ == "__main__": diff --git a/tests/test_raillabel_providerkit/convert/test_convert.py b/tests/test_raillabel_providerkit/convert/test_convert.py index 7ebc6bb..51eb7e8 100644 --- a/tests/test_raillabel_providerkit/convert/test_convert.py +++ b/tests/test_raillabel_providerkit/convert/test_convert.py @@ -10,17 +10,17 @@ import raillabel_providerkit -def test_convert_uai_select_class(json_data): - scene = raillabel_providerkit.convert(data=json_data["understand_ai_t4_short"]) - assert len(scene.frames) != 0 +# def test_convert_uai_select_class(json_data): +# scene = raillabel_providerkit.convert(data=json_data["understand_ai_t4_short"]) +# assert len(scene.frames) != 0 -def test_convert_uai_provide_class(json_data): - scene = raillabel_providerkit.convert( - data=json_data["understand_ai_t4_short"], - loader_class=raillabel_providerkit.loader_classes.LoaderUnderstandAi, - ) - assert len(scene.frames) != 0 +# def test_convert_uai_provide_class(json_data): +# scene = raillabel_providerkit.convert( +# data=json_data["understand_ai_t4_short"], +# loader_class=raillabel_providerkit.loader_classes.LoaderUnderstandAi, +# ) +# assert len(scene.frames) != 0 # Executes the test if the file is called From 77ab4d5f2ce908d272bbf6e29305534aac4f5651 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:23:29 +0100 Subject: [PATCH 22/27] refactor: drop support for python 3.9 and 3.10 --- .github/workflows/build-test-publish.yml | 4 +--- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- pyproject.toml | 8 +++----- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-test-publish.yml b/.github/workflows/build-test-publish.yml index 80a0da4..cbc58cc 100644 --- a/.github/workflows/build-test-publish.yml +++ b/.github/workflows/build-test-publish.yml @@ -18,15 +18,13 @@ jobs: matrix: os: [ubuntu-latest] python_version: - - "3.8" - - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" include: - os: windows-latest - python_version: "3.12" + python_version: "3.13" steps: - uses: actions/checkout@v2 - name: Set up Python ${{matrix.python_version}} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 612c67a..5d8b9eb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,7 +18,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.12" - name: Upgrade pip run: | python -m pip install -U pip diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 080db8b..485ce96 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.12" - name: Upgrade pip run: |- python -m pip install -U pip diff --git a/pyproject.toml b/pyproject.toml index cbd68f9..9655646 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dynamic = ["version"] name = "raillabel-providerkit" description = "A devkit for working with recorded and annotated train ride data from Deutsche Bahn." readme = "README.md" -requires-python = ">=3.8, <3.14" +requires-python = ">=3.10, <3.14" license = { text = "Apache-2.0" } authors = [ { name = "DB InfraGO AG" }, @@ -27,8 +27,6 @@ classifiers = [ "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -37,7 +35,7 @@ classifiers = [ dependencies = [ "jsonschema>=4.4.0", "fastjsonschema>=2.16.2", - "raillabel>=4.0.0", + "raillabel==4.0.0", "pyyaml>=6.0.0", "numpy>=1.24.4", "pydantic<3.0.0", @@ -52,7 +50,7 @@ docs = [ "furo", "sphinx", "sphinx-copybutton", - "tomli; python_version<'3.11'", + "tomli; python_version<'3.14'", ] test = [ From edc5f8ebf04590b238c92436ec01fdde03bcf9ae Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:26:56 +0100 Subject: [PATCH 23/27] fix: remove wrong ticks in docstring --- .../validation/validate_schema/validate_schema.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index ea35c4b..8d42b5b 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -56,17 +56,17 @@ def _build_error_path(loc: list[str]) -> str: def _convert_missing_error_to_string(error: dict) -> str: - return f"{_build_error_path(error["loc"][:-1])}: required field '{error["loc"][-1]}' is missing." + return f"{_build_error_path(error['loc'][:-1])}: required field '{error['loc'][-1]}' is missing." def _convert_unexpected_field_error_to_string(error: dict) -> str: - return f"{_build_error_path(error["loc"][:-1])}: found unexpected field '{error["loc"][-1]}'." + return f"{_build_error_path(error['loc'][:-1])}: found unexpected field '{error['loc'][-1]}'." def _convert_literal_error_to_string(error: dict) -> str: return ( - f"{_build_error_path(error["loc"])}: value '{error["input"]}' does not match allowed values " - f"({error["ctx"]["expected"]})." + f"{_build_error_path(error['loc'])}: value '{error['input']}' does not match allowed values " + f"({error['ctx']['expected']})." ) @@ -76,11 +76,11 @@ def _convert_false_type_error_to_string(error: dict, target_type: str) -> str: else: error_path = _build_error_path(error["loc"]) - return f"{error_path}: value '{error["input"]}' could not be interpreted " f"as {target_type}." + return f"{error_path}: value '{error['input']}' could not be interpreted " f"as {target_type}." def _convert_too_long_error_to_string(error: dict) -> str: return ( - f"{_build_error_path(error["loc"])}: should have length of {error["ctx"]["actual_length"]} " - f"but has length of {error["ctx"]["max_length"]}." + f"{_build_error_path(error['loc'])}: should have length of {error['ctx']['actual_length']} " + f"but has length of {error['ctx']['max_length']}." ) From 3fd8e0b00da89c25de233ed6d704a7028844f6f5 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:32:50 +0100 Subject: [PATCH 24/27] refactor: replace if-else tree with match --- .../validate_schema/validate_schema.py | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/raillabel_providerkit/validation/validate_schema/validate_schema.py b/raillabel_providerkit/validation/validate_schema/validate_schema.py index 8d42b5b..01a9879 100644 --- a/raillabel_providerkit/validation/validate_schema/validate_schema.py +++ b/raillabel_providerkit/validation/validate_schema/validate_schema.py @@ -22,28 +22,39 @@ def validate_schema(data: dict) -> list[str]: def _make_errors_readable(errors: ValidationError) -> list[str]: # noqa: C901 readable_errors = [] for error in json.loads(errors.json()): - if error["type"] == "missing": - readable_errors.append(_convert_missing_error_to_string(error)) - elif error["type"] == "extra_forbidden": - readable_errors.append(_convert_unexpected_field_error_to_string(error)) - elif error["type"] == "literal_error": - readable_errors.append(_convert_literal_error_to_string(error)) - elif error["type"] in ["bool_type", "bool_parsing"]: - readable_errors.append(_convert_false_type_error_to_string(error, "bool")) - elif error["type"] in ["int_type", "int_parsing", "int_from_float"]: - readable_errors.append(_convert_false_type_error_to_string(error, "int")) - elif error["type"] in ["decimal_type", "decimal_parsing"]: - readable_errors.append(_convert_false_type_error_to_string(error, "Decimal")) - elif error["type"] in ["string_type", "string_parsing"]: - readable_errors.append(_convert_false_type_error_to_string(error, "str")) - elif error["type"] in ["float_type", "float_parsing"]: - readable_errors.append(_convert_false_type_error_to_string(error, "float")) - elif error["type"] in ["uuid_type", "uuid_parsing"]: - readable_errors.append(_convert_false_type_error_to_string(error, "UUID")) - elif error["type"] == "too_long": - readable_errors.append(_convert_too_long_error_to_string(error)) - else: - readable_errors.append(str(error)) + match error["type"]: + case "missing": + readable_errors.append(_convert_missing_error_to_string(error)) + + case "extra_forbidden": + readable_errors.append(_convert_unexpected_field_error_to_string(error)) + + case "literal_error": + readable_errors.append(_convert_literal_error_to_string(error)) + + case "bool_type" | "bool_parsing": + readable_errors.append(_convert_false_type_error_to_string(error, "bool")) + + case "int_type" | "int_parsing" | "int_from_float": + readable_errors.append(_convert_false_type_error_to_string(error, "int")) + + case "decimal_type" | "decimal_parsing": + readable_errors.append(_convert_false_type_error_to_string(error, "Decimal")) + + case "string_type" | "string_parsing": + readable_errors.append(_convert_false_type_error_to_string(error, "str")) + + case "float_type" | "float_parsing": + readable_errors.append(_convert_false_type_error_to_string(error, "float")) + + case "uuid_type" | "uuid_parsing": + readable_errors.append(_convert_false_type_error_to_string(error, "UUID")) + + case "too_long": + readable_errors.append(_convert_too_long_error_to_string(error)) + + case _: + readable_errors.append(str(error)) return readable_errors From fef17e107bd678b0526a48b5e3c614b67d74f68d Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:53:46 +0100 Subject: [PATCH 25/27] chore: remove unused dependencies --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9655646..945dc66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,8 +33,6 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "jsonschema>=4.4.0", - "fastjsonschema>=2.16.2", "raillabel==4.0.0", "pyyaml>=6.0.0", "numpy>=1.24.4", From 7e9804fedb4386f93872de2ca2e05172af56d819 Mon Sep 17 00:00:00 2001 From: Tobias Klockau Date: Mon, 25 Nov 2024 16:55:24 +0100 Subject: [PATCH 26/27] Revert "chore: remove unused dependencies" This reverts commit fef17e107bd678b0526a48b5e3c614b67d74f68d. --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 945dc66..9655646 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ + "jsonschema>=4.4.0", + "fastjsonschema>=2.16.2", "raillabel==4.0.0", "pyyaml>=6.0.0", "numpy>=1.24.4", From 997637de42eb294d1cca08d7322191aca8b28fb9 Mon Sep 17 00:00:00 2001 From: Niklas Freund Date: Fri, 29 Nov 2024 09:32:11 +0100 Subject: [PATCH 27/27] lint: Remove one redundant if-clause --- raillabel_providerkit/validation/validate.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/raillabel_providerkit/validation/validate.py b/raillabel_providerkit/validation/validate.py index f2c8b72..3b402f8 100644 --- a/raillabel_providerkit/validation/validate.py +++ b/raillabel_providerkit/validation/validate.py @@ -25,7 +25,4 @@ def validate(scene_dict: dict) -> list[str]: errors.extend(validate_schema(scene_dict)) - if len(errors) > 0: - return errors - return errors