Skip to content

Commit

Permalink
Replace attribute_val with a higher-level helper function
Browse files Browse the repository at this point in the history
  • Loading branch information
SpecLad committed Feb 17, 2025
1 parent 0e1ec82 commit da5ffbf
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 33 deletions.
32 changes: 31 additions & 1 deletion cvat-sdk/cvat_sdk/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from __future__ import annotations

from typing import Callable
from collections.abc import Mapping
from typing import Callable, Union

from . import models

Expand Down Expand Up @@ -104,3 +105,32 @@ def number_attribute_values(min_value: int, max_value: int, /, step: int = 1) ->
raise ValueError("step must be a divisor of max_value - min_value")

return [str(min_value), str(max_value), str(step)]


def attribute_vals_from_dict(
id_to_value: Mapping[int, Union[str, int, bool]], /
) -> list[models.AttributeValRequest]:
"""
Returns a list of AttributeValRequest objects with given IDs and values.
The input value must be a mapping from attribute spec IDs to corresponding values.
A value may be specified as a string, an integer, or a boolean.
Integers and booleans will be converted to strings according to the format CVAT expects
for attributes with input type "number" and "checkbox", respectively.
"""

def val_as_string(v: Union[str, int, bool]) -> str:
if v is True:
return "true"
if v is False:
return "false"
if isinstance(v, int):
return str(v)
if isinstance(v, str):
return v
assert False, f"unexpected value {v!r} of type {type(v)}"

return [
models.AttributeValRequest(spec_id=k, value=val_as_string(v))
for k, v in id_to_value.items()
]
2 changes: 0 additions & 2 deletions cvat-sdk/cvat_sdk/auto_annotation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
DetectionFunctionContext,
DetectionFunctionSpec,
attribute_spec,
attribute_val,
checkbox_attribute_spec,
keypoint,
keypoint_spec,
Expand All @@ -29,7 +28,6 @@
__all__ = [
"annotate_task",
"attribute_spec",
"attribute_val",
"BadFunctionError",
"checkbox_attribute_spec",
"DetectionFunction",
Expand Down
5 changes: 0 additions & 5 deletions cvat-sdk/cvat_sdk/auto_annotation/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,3 @@ def keypoint(label_id: int, points: Sequence[float], **kwargs) -> models.SubLabe
return models.SubLabeledShapeRequest(
label_id=label_id, frame=0, type="points", points=points, **kwargs
)


def attribute_val(spec_id: int, value: str) -> models.AttributeValRequest:
"""Helper factory function for AttributeValRequest."""
return models.AttributeValRequest(spec_id=spec_id, value=value)
28 changes: 19 additions & 9 deletions site/content/en/docs/api_sdk/sdk/auto-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,14 @@ cvataa.number_attribute_spec("size", 1, number_attribute_values(0, 10))

The following helpers are available for use in `detect`:

| Name | Model type | Fixed attributes |
|-----------------|--------------------------|-------------------------------|
| `shape` | `LabeledShapeRequest` | `frame=0` |
| `mask` | `LabeledShapeRequest` | `frame=0`, `type="mask"` |
| `polygon` | `LabeledShapeRequest` | `frame=0`, `type="polygon"` |
| `rectangle` | `LabeledShapeRequest` | `frame=0`, `type="rectangle"` |
| `skeleton` | `LabeledShapeRequest` | `frame=0`, `type="skeleton"` |
| `keypoint` | `SubLabeledShapeRequest` | `frame=0`, `type="points"` |
| `attribute_val` | `AttributeValRequest` | - |
| Name | Model type | Fixed attributes |
|-------------|--------------------------|-------------------------------|
| `shape` | `LabeledShapeRequest` | `frame=0` |
| `mask` | `LabeledShapeRequest` | `frame=0`, `type="mask"` |
| `polygon` | `LabeledShapeRequest` | `frame=0`, `type="polygon"` |
| `rectangle` | `LabeledShapeRequest` | `frame=0`, `type="rectangle"` |
| `skeleton` | `LabeledShapeRequest` | `frame=0`, `type="skeleton"` |
| `keypoint` | `SubLabeledShapeRequest` | `frame=0`, `type="points"` |

For `mask`, it is recommended to create the points list using
the `cvat_sdk.masks.encode_mask` function, which will convert a bitmap into a
Expand All @@ -217,6 +216,17 @@ cvataa.mask(my_label, encode_mask(
))
```

To create shapes with attributes,
it's recommended to use the `cvat_sdk.attributes.attribute_vals_from_dict` function,
which returns a list of objects that can be passed to an `attributes` argument:

```python
cvataa.rectangle(
my_label, [x1, y2, x2, y2],
attributes=attribute_vals_from_dict({my_attr1: val1, my_attr2: val2})
)
```

## Auto-annotation driver

The `annotate_task` function uses an AA function to annotate a CVAT task.
Expand Down
21 changes: 20 additions & 1 deletion tests/python/sdk/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

import pytest
from cvat_sdk import models
from cvat_sdk.attributes import attribute_value_validator, number_attribute_values
from cvat_sdk.attributes import (
attribute_vals_from_dict,
attribute_value_validator,
number_attribute_values,
)


def test_number_attribute_values_can_convert_good_values():
Expand Down Expand Up @@ -99,3 +103,18 @@ def test_attribute_value_validator_text():
)

assert validator("anything")


def test_attribute_vals_from_dict():
assert attribute_vals_from_dict({}) == []

attrs = attribute_vals_from_dict({0: "x", 1: 5, 2: True, 3: False})
assert len(attrs) == 4

for i, attr in enumerate(attrs):
assert attr.spec_id == i

assert attrs[0].value == "x"
assert attrs[1].value == "5"
assert attrs[2].value == "true"
assert attrs[3].value == "false"
30 changes: 15 additions & 15 deletions tests/python/sdk/test_auto_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import PIL.Image
import pytest
from cvat_sdk import Client, models
from cvat_sdk.attributes import number_attribute_values
from cvat_sdk.attributes import attribute_vals_from_dict, number_attribute_values
from cvat_sdk.core.proxies.tasks import ResourceType

from shared.utils.helpers import generate_image_file
Expand Down Expand Up @@ -382,18 +382,12 @@ def detect(context, image: PIL.Image.Image) -> list[models.LabeledShapeRequest]:
cvataa.keypoint(
10,
[10, 10],
attributes=[
cvataa.attribute_val(1, "5"), # size
cvataa.attribute_val(2, "forward"), # orientation
],
attributes=attribute_vals_from_dict({1: 5, 2: "forward"}),
),
# tail
cvataa.keypoint(30, [30, 30]),
],
attributes=[
cvataa.attribute_val(1, "calico"), # color
cvataa.attribute_val(2, "McFluffy"), # name
],
attributes=attribute_vals_from_dict({1: "calico", 2: "McFluffy"}),
),
]

Expand Down Expand Up @@ -944,7 +938,7 @@ def test_attribute_val_with_unknown_id(self):
cvataa.skeleton(
456,
[cvataa.keypoint(12, [1, 2]), cvataa.keypoint(34, [3, 4])],
attributes=[cvataa.attribute_val(2, "gray")],
attributes=attribute_vals_from_dict({2: "gray"}),
),
],
"attribute with unknown ID",
Expand All @@ -955,7 +949,7 @@ def test_attribute_val_with_unknown_id(self):
cvataa.skeleton(
456,
[
cvataa.keypoint(12, [1, 2], attributes=[cvataa.attribute_val(2, "5")]),
cvataa.keypoint(12, [1, 2], attributes=attribute_vals_from_dict({2: 5})),
cvataa.keypoint(34, [3, 4]),
],
),
Expand All @@ -969,7 +963,10 @@ def test_multiple_attribute_vals_with_same_id(self):
cvataa.skeleton(
456,
[cvataa.keypoint(12, [1, 2]), cvataa.keypoint(34, [3, 4])],
attributes=[cvataa.attribute_val(1, "gray"), cvataa.attribute_val(1, "gray")],
attributes=[
models.AttributeValRequest(spec_id=1, value="gray"),
models.AttributeValRequest(spec_id=1, value="gray"),
],
),
],
"multiple attributes with same ID",
Expand All @@ -983,7 +980,10 @@ def test_multiple_attribute_vals_with_same_id(self):
cvataa.keypoint(
12,
[1, 2],
attributes=[cvataa.attribute_val(1, "5"), cvataa.attribute_val(1, "5")],
attributes=[
models.AttributeValRequest(spec_id=1, value="5"),
models.AttributeValRequest(spec_id=1, value="5"),
],
),
cvataa.keypoint(34, [3, 4]),
],
Expand All @@ -998,7 +998,7 @@ def test_attribute_val_unsuitable_for_spec(self):
cvataa.skeleton(
456,
[cvataa.keypoint(12, [1, 2]), cvataa.keypoint(34, [3, 4])],
attributes=[cvataa.attribute_val(1, "purple")],
attributes=attribute_vals_from_dict({1: "purple"}),
),
],
"unsuitable for its attribute",
Expand All @@ -1012,7 +1012,7 @@ def test_attribute_val_unsuitable_for_spec(self):
cvataa.keypoint(
12,
[1, 2],
attributes=[cvataa.attribute_val(1, "-1")],
attributes=attribute_vals_from_dict({1: -1}),
),
cvataa.keypoint(34, [3, 4]),
],
Expand Down

0 comments on commit da5ffbf

Please sign in to comment.