add-new-annotation-types #5

Merged
brian merged 3 commits from add-new-annotation-types into main 2025-09-18 21:19:09 +02:00
9 changed files with 135 additions and 48 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name: Sync Label Studio
on: on:
schedule: schedule:
# This job will run every day at 03:00 AM # This job will run every day at 03:00 AM
- cron: "0 3 * * *" - cron: "0/10 * * * *"
jobs: jobs:
sync_storage: sync_storage:
+6
View File
@@ -5,9 +5,12 @@ Exposes core data transfer objects and enums for use throughout the project.
from .angle_enum import AngleEnum from .angle_enum import AngleEnum
from .annotation import Annotation from .annotation import Annotation
from .colour_enum import ColourEnum
from .contact_enum import ContactEnum from .contact_enum import ContactEnum
from .depth_enum import DepthEnum
from .distance_enum import DistanceEnum from .distance_enum import DistanceEnum
from .job import Job from .job import Job
from .lighting_enum import LightingEnum
from .point_of_view_enum import PointOfViewEnum from .point_of_view_enum import PointOfViewEnum
__all__ = [ __all__ = [
@@ -17,4 +20,7 @@ __all__ = [
"DistanceEnum", "DistanceEnum",
"Job", "Job",
"PointOfViewEnum", "PointOfViewEnum",
"ColourEnum",
"DepthEnum",
"LightingEnum",
] ]
+17 -4
View File
@@ -1,16 +1,29 @@
"""Definition of Annotation DTO.""" """Definition of Annotation DTO."""
from .type_checking_base_model import TypeCheckingBaseModel from .type_checking_base_model import TypeCheckingBaseModel
from .distance_enum import DistanceEnum
from .angle_enum import AngleEnum from .angle_enum import AngleEnum
from .point_of_view_enum import PointOfViewEnum from .colour_enum import ColourEnum
from .contact_enum import ContactEnum from .contact_enum import ContactEnum
from .depth_enum import DepthEnum
from .distance_enum import DistanceEnum
from .lighting_enum import LightingEnum
from .point_of_view_enum import PointOfViewEnum
class Annotation(TypeCheckingBaseModel): class Annotation(TypeCheckingBaseModel):
"""Data Transfer Object representing an Annotation.""" """Data Transfer Object representing an Annotation."""
distance: DistanceEnum
angle: AngleEnum angle: AngleEnum
point_of_view: PointOfViewEnum angle_uncertainty: bool
colour: ColourEnum
colour_uncertainty: bool
contact: ContactEnum contact: ContactEnum
contact_uncertainty: bool
depth: DepthEnum
depth_uncertainty: bool
distance: DistanceEnum
distance_uncertainty: bool
lighting: LightingEnum
lighting_uncertainty: bool
point_of_view: PointOfViewEnum
point_of_view_uncertainty: bool
+11
View File
@@ -0,0 +1,11 @@
"""Definition of ColourEnum DTO."""
from enum import StrEnum
class ColourEnum(StrEnum):
"""Enumeration for Colour values."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
+11
View File
@@ -0,0 +1,11 @@
"""Definition of DepthEnum DTO."""
from enum import StrEnum
class DepthEnum(StrEnum):
"""Enumeration for Depth values."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
+11
View File
@@ -0,0 +1,11 @@
"""Definition of LightingEnum DTO."""
from enum import StrEnum
class LightingEnum(StrEnum):
"""Enumeration for Lighting values."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
@@ -11,10 +11,13 @@ from python_repositories.adapters import MinioAdapter
from data_store.interfaces import AnnotationRepositoryInterface from data_store.interfaces import AnnotationRepositoryInterface
from data_store.dto import ( from data_store.dto import (
Annotation, Annotation,
DistanceEnum,
AngleEnum, AngleEnum,
PointOfViewEnum, ColourEnum,
ContactEnum, ContactEnum,
DepthEnum,
DistanceEnum,
LightingEnum,
PointOfViewEnum,
) )
@@ -55,10 +58,20 @@ class AnnotationRepository(MinioAdapter, AnnotationRepositoryInterface):
data_str = buffer.read().decode(self.encoding) data_str = buffer.read().decode(self.encoding)
data = json.loads(data_str) data = json.loads(data_str)
annotation = Annotation( annotation = Annotation(
distance=DistanceEnum(data["distance"]),
angle=AngleEnum(data["angle"]), angle=AngleEnum(data["angle"]),
point_of_view=PointOfViewEnum(data["point_of_view"]), angle_uncertainty=bool(data["angle_uncertainty"]),
colour=ColourEnum(data["colour"]),
colour_uncertainty=bool(data["colour_uncertainty"]),
contact=ContactEnum(data["contact"]), contact=ContactEnum(data["contact"]),
contact_uncertainty=bool(data["contact_uncertainty"]),
depth=DepthEnum(data["depth"]),
depth_uncertainty=bool(data["depth_uncertainty"]),
distance=DistanceEnum(data["distance"]),
distance_uncertainty=bool(data["distance_uncertainty"]),
lighting=LightingEnum(data["lighting"]),
lighting_uncertainty=bool(data["lighting_uncertainty"]),
point_of_view=PointOfViewEnum(data["point_of_view"]),
point_of_view_uncertainty=bool(data["point_of_view_uncertainty"]),
) )
except (KeyError, ValueError) as e: except (KeyError, ValueError) as e:
self.logger.error( self.logger.error(
+44 -35
View File
@@ -2,14 +2,10 @@
import logging import logging
import os import os
import shutil
import tempfile
from pathlib import Path
import json import json
from collections.abc import Generator from collections.abc import Generator
from uuid import UUID from uuid import UUID
from io import BytesIO from io import BytesIO
import pandas as pd
from PIL import Image from PIL import Image
import pytest import pytest
import structlog import structlog
@@ -23,10 +19,13 @@ from data_store.repositories import (
) )
from data_store.dto import ( from data_store.dto import (
Annotation, Annotation,
DistanceEnum,
AngleEnum, AngleEnum,
PointOfViewEnum, ColourEnum,
ContactEnum, ContactEnum,
DepthEnum,
DistanceEnum,
LightingEnum,
PointOfViewEnum,
Job, Job,
) )
@@ -114,35 +113,35 @@ def job_id_list() -> Generator[list[UUID]]:
] ]
@pytest.fixture(scope="session") # @pytest.fixture(scope="session")
def csv_data_file( # def csv_data_file(
job_id_list: list[UUID], # job_id_list: list[UUID],
) -> Generator[Path]: # ) -> Generator[Path]:
"""Create a CSV data file for testing.""" # """Create a CSV data file for testing."""
# Create csv content # # Create csv content
csv_data = pd.DataFrame( # csv_data = pd.DataFrame(
{ # {
"job_id": job_id_list, # "job_id": job_id_list,
"distance": [ # "distance": [
"close", # "close",
"long", # "long",
"wrong", # "wrong",
], # ],
"angle": ["high", "low", "wrong"], # "angle": ["high", "low", "wrong"],
"point_of_view": ["frontal", "oblique", "wrong"], # "point_of_view": ["frontal", "oblique", "wrong"],
"contact": ["offer", "demand", "wrong"], # "contact": ["offer", "demand", "wrong"],
} # }
) # )
# Create data directory and file path # # Create data directory and file path
data_dir = Path(tempfile.mkdtemp()) # data_dir = Path(tempfile.mkdtemp())
data_path = data_dir / "annotations.csv" # data_path = data_dir / "annotations.csv"
# Write CSV content to file # # Write CSV content to file
csv_data.to_csv(data_path, header=True, index=False) # csv_data.to_csv(data_path, header=True, index=False)
yield data_path # yield data_path
# Cleanup # # Cleanup
shutil.rmtree(data_dir) # shutil.rmtree(data_dir)
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@@ -245,10 +244,20 @@ def annotation_repository() -> Generator[AnnotationRepository]:
def annotation() -> Generator[Annotation]: def annotation() -> Generator[Annotation]:
"""Provide a sample annotation for testing.""" """Provide a sample annotation for testing."""
annotation = Annotation( annotation = Annotation(
distance=DistanceEnum.CLOSE,
angle=AngleEnum.HIGH, angle=AngleEnum.HIGH,
point_of_view=PointOfViewEnum.FRONTAL, angle_uncertainty=True,
colour=ColourEnum.HIGH,
colour_uncertainty=False,
contact=ContactEnum.OFFER, contact=ContactEnum.OFFER,
contact_uncertainty=True,
depth=DepthEnum.LOW,
depth_uncertainty=False,
distance=DistanceEnum.CLOSE,
distance_uncertainty=True,
lighting=LightingEnum.MEDIUM,
lighting_uncertainty=False,
point_of_view=PointOfViewEnum.FRONTAL,
point_of_view_uncertainty=True,
) )
yield annotation yield annotation
+17 -4
View File
@@ -7,10 +7,13 @@ from uuid import UUID
from data_store.repositories.annotation_repository import AnnotationRepository from data_store.repositories.annotation_repository import AnnotationRepository
from data_store.dto import ( from data_store.dto import (
Annotation, Annotation,
DistanceEnum,
AngleEnum, AngleEnum,
PointOfViewEnum, ColourEnum,
ContactEnum, ContactEnum,
DepthEnum,
DistanceEnum,
LightingEnum,
PointOfViewEnum,
) )
@@ -47,10 +50,20 @@ class TestAnnotationRepository(unittest.TestCase):
cls.job_id = UUID("d87022f8-4169-4d69-8512-bbd65cc1cb23") cls.job_id = UUID("d87022f8-4169-4d69-8512-bbd65cc1cb23")
cls.bad_job_id = "not-a-uuid" cls.bad_job_id = "not-a-uuid"
cls.annotation = Annotation( cls.annotation = Annotation(
distance=DistanceEnum.CLOSE,
angle=AngleEnum.HIGH, angle=AngleEnum.HIGH,
point_of_view=PointOfViewEnum.FRONTAL, angle_uncertainty=True,
colour=ColourEnum.HIGH,
colour_uncertainty=False,
contact=ContactEnum.OFFER, contact=ContactEnum.OFFER,
contact_uncertainty=True,
depth=DepthEnum.LOW,
depth_uncertainty=False,
distance=DistanceEnum.CLOSE,
distance_uncertainty=True,
lighting=LightingEnum.MEDIUM,
lighting_uncertainty=False,
point_of_view=PointOfViewEnum.FRONTAL,
point_of_view_uncertainty=True,
) )
cls.bad_annotation = {"not_an_annotation": "irrelevant value"} cls.bad_annotation = {"not_an_annotation": "irrelevant value"}