-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add attribute support for auto-annotation functions (#9090)
Remove one of the long-standing limitations on auto-annotation functions by adding the necessary validation and remapping logic to support attribute specifications and values. Add a utility module for attributes with functionality I needed, but felt didn't belong in the auto-annotation layer. Adds the necessary code to support using functions with attributes via agents, as well. I will submit the necesssary server-side code will be submitted to the private repository later; until that is merged, attempts to create native functions with attributes will be rejected.
- Loading branch information
Showing
13 changed files
with
1,006 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
### Added | ||
|
||
- \[SDK\] Auto-annotation detection functions can now output shape/keypoint attributes | ||
(<https://github.com/cvat-ai/cvat/pull/9090>) | ||
|
||
- \{SDK\] Added a utility module for working with label attributes, | ||
`cvat_sdk.attributes` | ||
(<https://github.com/cvat-ai/cvat/pull/9090>) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# Copyright (C) CVAT.ai Corporation | ||
# | ||
# SPDX-License-Identifier: MIT | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Mapping | ||
from typing import Callable, Union | ||
|
||
from . import models | ||
|
||
|
||
class _CheckboxAttributeValueValidator: | ||
def __init__(self, values: list[str]) -> None: | ||
pass | ||
|
||
def __call__(self, value: str) -> bool: | ||
return value in {"true", "false"} | ||
|
||
|
||
class _NumberAttributeValueValidator: | ||
def __init__(self, values: list[str]) -> None: | ||
if len(values) != 3: | ||
raise ValueError(f"wrong number of values: expected 3, got {len(values)}") | ||
|
||
try: | ||
(self._min_value, self._max_value, self._step) = map(int, values) | ||
except ValueError as ex: | ||
raise ValueError(f"values could not be converted to integers") from ex | ||
|
||
try: | ||
number_attribute_values(self._min_value, self._max_value, self._step) | ||
except ValueError as ex: | ||
raise ValueError(f"invalid values: {ex}") from ex | ||
|
||
def __call__(self, value: str) -> bool: | ||
try: | ||
value = int(value) | ||
except ValueError: | ||
return False | ||
|
||
return ( | ||
self._min_value <= value <= self._max_value | ||
and (value - self._min_value) % self._step == 0 | ||
) | ||
|
||
|
||
class _SelectAttributeValueValidator: | ||
def __init__(self, values: list[str]) -> None: | ||
if len(values) == 0: | ||
raise ValueError("empty list of allowed values") | ||
|
||
self._values = frozenset(values) | ||
|
||
def __call__(self, value: str) -> bool: | ||
return value in self._values | ||
|
||
|
||
class _TextAttributeValueValidator: | ||
def __init__(self, values: list[str]) -> None: | ||
pass | ||
|
||
def __call__(self, value: str) -> bool: | ||
return True | ||
|
||
|
||
_VALIDATOR_CLASSES = { | ||
"checkbox": _CheckboxAttributeValueValidator, | ||
"number": _NumberAttributeValueValidator, | ||
"radio": _SelectAttributeValueValidator, | ||
"select": _SelectAttributeValueValidator, | ||
"text": _TextAttributeValueValidator, | ||
} | ||
|
||
# make sure all possible types are covered | ||
assert set(models.InputTypeEnum.allowed_values[("value",)].values()) == _VALIDATOR_CLASSES.keys() | ||
|
||
|
||
def attribute_value_validator(spec: models.IAttributeRequest) -> Callable[[str], bool]: | ||
""" | ||
Returns a callable that can be used to verify | ||
whether an attribute value is suitable for an attribute with the given spec. | ||
The resulting callable takes a single argument (the attribute value as a string) | ||
and returns True if and only if the value is suitable. | ||
The spec's `values` attribute must be consistent with its `input_type` attribute, | ||
otherwise ValueError will be raised. | ||
""" | ||
return _VALIDATOR_CLASSES[spec.input_type.value](spec.values) | ||
|
||
|
||
def number_attribute_values(min_value: int, max_value: int, /, step: int = 1) -> list[str]: | ||
""" | ||
Returns a list suitable as the value of the "values" field of an `AttributeRequest` | ||
with `input_type="number"`. | ||
""" | ||
|
||
if min_value > max_value: | ||
raise ValueError("min_value must be less than or equal to max_value") | ||
|
||
if step <= 0: | ||
raise ValueError("step must be positive") | ||
|
||
if (max_value - min_value) % step != 0: | ||
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() | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.