Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DAR-3771][External] Import of annotation-level properties #925

Merged
merged 5 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions darwin/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
except ImportError:
NDArray = Any # type:ignore

from darwin.future.data_objects.properties import PropertyType, SelectedProperty
from darwin.future.data_objects.properties import (
PropertyType,
SelectedProperty,
PropertyGranularity,
)
from darwin.path_utils import construct_full_path, is_properties_enabled, parse_metadata

# Utility types
Expand Down Expand Up @@ -422,6 +426,9 @@ class Property:
# Description of the property
description: Optional[str] = None

# Granularity of the property
granularity: PropertyGranularity = PropertyGranularity("section")


@dataclass
class PropertyClass:
Expand Down Expand Up @@ -454,17 +461,27 @@ def parse_property_classes(metadata: dict[str, Any]) -> list[PropertyClass]:
assert (
"properties" in metadata_cls
), "Metadata class does not contain properties"
properties = [
Property(
name=p["name"],
type=p["type"],
required=p["required"],
property_values=p["property_values"],
description=p.get("description"),
granularity=PropertyGranularity(p.get("granularity", "section")),
)
for p in metadata_cls["properties"]
]
classes.append(
PropertyClass(
name=metadata_cls["name"],
type=metadata_cls["type"],
description=metadata_cls.get("description"),
color=metadata_cls.get("color"),
sub_types=metadata_cls.get("sub_types"),
properties=[Property(**p) for p in metadata_cls["properties"]],
properties=properties,
)
)

return classes


Expand Down
25 changes: 21 additions & 4 deletions darwin/future/data_objects/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import json
import os
from pathlib import Path
from typing import List, Literal, Optional, Tuple
from typing import List, Literal, Optional, Tuple, Union
from enum import Enum

from pydantic import field_validator

Expand All @@ -19,6 +20,12 @@
]


class PropertyGranularity(str, Enum):
section = "section"
annotation = "annotation"
item = "item"


class PropertyValue(DefaultDarwin):
"""
Describes a single option for a property
Expand Down Expand Up @@ -60,6 +67,8 @@ class FullProperty(DefaultDarwin):
type (str): Type of the property
required (bool): If the property is required
options (List[PropertyOption]): List of all options for the property
granularity (PropertyGranularity): Granularity of the property

"""

id: Optional[str] = None
Expand All @@ -73,6 +82,9 @@ class FullProperty(DefaultDarwin):
annotation_class_id: Optional[int] = None
property_values: Optional[List[PropertyValue]] = None
options: Optional[List[PropertyValue]] = None
granularity: PropertyGranularity = PropertyGranularity("section")

# model_config = ConfigDict(use_enum_values=True)

def to_create_endpoint(
self,
Expand All @@ -87,14 +99,16 @@ def to_create_endpoint(
"annotation_class_id": True,
"property_values": {"__all__": {"value", "color"}},
"description": True,
"granularity": True,
}
)

def to_update_endpoint(self) -> Tuple[str, dict]:
if self.id is None:
raise ValueError("id must be set")
updated_base = self.to_create_endpoint()
del updated_base["annotation_class_id"] # can't update this field
del updated_base["annotation_class_id"] # Can't update this field
del updated_base["granularity"] # Can't update this field
return self.id, updated_base


Expand All @@ -110,6 +124,7 @@ class MetaDataClass(DefaultDarwin):
description (Optional[str]): Description of the class
color (Optional[str]): Color of the class in the UI
sub_types (Optional[List[str]]): Sub types of the class
granularity:(PropertyGranularity): Granularity of the property
properties (List[FullProperty]): List of all properties for the class with all options
"""

Expand All @@ -118,6 +133,7 @@ class MetaDataClass(DefaultDarwin):
description: Optional[str] = None
color: Optional[str] = None
sub_types: Optional[List[str]] = None
granularity: PropertyGranularity = PropertyGranularity("section")
properties: List[FullProperty]

@classmethod
Expand All @@ -141,13 +157,14 @@ class SelectedProperty(DefaultDarwin):
Selected property for an annotation found inside a darwin annotation

Attributes:
frame_index (int): Frame index of the annotation
frame_index (int | str): Frame index of the annotation
int for section-level properties, and "global" for annotation-level properties
name (str): Name of the property
type (str | None): Type of the property (if it exists)
value (str): Value of the property
"""

frame_index: Optional[int] = None
frame_index: Optional[Union[int, str]] = None
name: str
type: Optional[str] = None
value: Optional[str] = None
7 changes: 6 additions & 1 deletion darwin/future/tests/core/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from darwin.future.core.client import ClientCore, DarwinConfig
from darwin.future.data_objects.dataset import DatasetCore
from darwin.future.data_objects.item import ItemCore, ItemLayout, ItemSlot
from darwin.future.data_objects.properties import FullProperty, PropertyValue
from darwin.future.data_objects.properties import (
FullProperty,
PropertyValue,
PropertyGranularity,
)
from darwin.future.data_objects.team import TeamCore, TeamMemberCore
from darwin.future.data_objects.team_member_role import TeamMemberRole
from darwin.future.data_objects.workflow import WorkflowCore
Expand Down Expand Up @@ -38,6 +42,7 @@ def base_property_object(base_property_value: PropertyValue) -> FullProperty:
annotation_class_id=0,
property_values=[base_property_value],
options=[base_property_value],
granularity=PropertyGranularity("section"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is questionable: currently the BE has a default 'section' if no granularity is provided. Here we're building a fixture that disregards the BE default. It's not wrong, but I think it'd be interesting to also have a test that doesn't specify the granularity at all when creating a property

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit tests I just added test creating section-level properties from a manifest file where no granularity is specified

)


Expand Down
4 changes: 4 additions & 0 deletions darwin/importer/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
PropertyType,
PropertyValue,
SelectedProperty,
PropertyGranularity,
)
from darwin.item import DatasetItem
from darwin.path_utils import is_properties_enabled, parse_metadata
Expand Down Expand Up @@ -412,6 +413,7 @@ def _import_properties(
# if property value is None, update annotation_property_map with empty set
if a_prop.value is None:
assert t_prop.id is not None

annotation_property_map[annotation_id][str(a_prop.frame_index)][
t_prop.id
] = set()
Expand Down Expand Up @@ -516,6 +518,7 @@ def _import_properties(
slug=client.default_team,
annotation_class_id=int(annotation_class_id),
property_values=property_values,
granularity=PropertyGranularity(m_prop.granularity.value),
)
create_properties.append(full_property)
continue
Expand Down Expand Up @@ -649,6 +652,7 @@ def _import_properties(
slug=client.default_team,
annotation_class_id=t_prop.annotation_class_id,
property_values=extra_property_values,
granularity=PropertyGranularity(t_prop.granularity.value),
)
console.print(
f"Updating property {full_property.name} ({full_property.type}) with extra metadata values {extra_values}",
Expand Down
3 changes: 2 additions & 1 deletion darwin/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,9 +1149,10 @@ def _parse_properties(
) -> Optional[List[SelectedProperty]]:
selected_properties = []
for property in properties:
frame_index = property.get("frame_index")
selected_properties.append(
SelectedProperty(
frame_index=property.get("frame_index", None),
frame_index=frame_index if frame_index is not None else "global",
name=property.get("name", None),
value=property.get("value", None),
)
Expand Down
1 change: 1 addition & 0 deletions tests/darwin/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ def test_get_team_properties(self, darwin_client: Client) -> None:
"slug": "property-question",
"team_id": 128,
"type": "multi_select",
"granularity": "section",
},
]
},
Expand Down
Loading