-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Add attribute support for auto-annotation functions #9090
Conversation
2cb88b3
to
6681b9b
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #9090 +/- ##
===========================================
- Coverage 73.97% 73.81% -0.16%
===========================================
Files 430 431 +1
Lines 44631 44794 +163
Branches 3892 3892
===========================================
+ Hits 33017 33066 +49
- Misses 11614 11728 +114
|
from .exceptions import BadFunctionError | ||
from .interface import DetectionFunction, DetectionFunctionContext, DetectionFunctionSpec | ||
|
||
|
||
@attrs.frozen | ||
class _AttributeNameMapping: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these classes called *Mapping
be collections.abc.Mapping
-compatible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well... it's a more abstract notion of mapping. The idea is that each of the XNameMapping
classes defines how names within X
should be mapped, so it parallels the structure of X
. So, for example, if you want to map attribute.name
, you look up attribute_nm.name
(where attribute_nm
is an _AttributeNameMapping
object).
Maybe they shouldn't be called *Mapping
, but I couldn't think of anything better when I created them.
cvataa.skeleton( | ||
123, # cat | ||
[ | ||
# head | ||
cvataa.keypoint( | ||
10, | ||
[10, 10], | ||
attributes=[ | ||
cvataa.attribute_val(1, "5"), # size | ||
cvataa.attribute_val(2, "forward"), # orientation | ||
], | ||
), | ||
# tail | ||
cvataa.keypoint(30, [30, 30]), | ||
], | ||
attributes=[ | ||
cvataa.attribute_val(1, "calico"), # color | ||
cvataa.attribute_val(2, "McFluffy"), # name | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be convenient to pass attributes as a dict with attr name as a key, what do you think? Probably, they are unique within a label.
The same idea is valid for labels as well, but as we can have nested ones with same names, we should check for name overlaps and also support a list of strings (e.g. ["parent name". "sublabel name"] or the inverse) to explicitly resolve such conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be convenient to pass attributes as a dict with attr name as a key, what do you think?
I think it's irrelevant. detect
outputs LabeledShapeRequest
s, and attribute values inside a LabeledShapeRequest
are identified by IDs.
Furthermore, I'm not sure this would be a meaningful improvement. The attribute identifier (be it name or number) has to occur at least twice in a function (in the spec and in detect
), so in a real-life function I would expect it to be factored out as a named constant. And once you have a constant, it doesn't matter if its value is a name or a number.
The same idea is valid for labels as well
NNs usually output labels as numbers, so I think requiring names in detect
would be a downgrade in convenience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
attribute values inside a LabeledShapeRequest are identified by IDs.
So, basically, this is the problem I'm talking about. If we make attribute ids optional in the attr spec, we can leave only names in the user code.
expect it to be factored out as a named constant. And once you have a constant, it doesn't matter if its value is a name or a number.
Ok, but if you have a number, it means you have to define and maintain it somewhere. If you have only a name, you don't need to map it to a number. To me, returning
attributes={
"color": "calico",
"name": "McFluffy",
},
or, more real:
attributes={
"color": output_tensor[dim_id],
"name": output_tensor[another_dim_id],
},
Looks significantly better than
attributes=[
cvataa.attribute_val(self.attr_name_map["color"], output_tensor[dim_id]),
cvataa.attribute_val(self.attr_name_map["name"], output_tensor[another_dim_id]),
],
If you note, even in the tests there are currently comments with the class and attribute names in every place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, basically, this is the problem I'm talking about. If we make attribute ids optional in the attr spec, we can leave only names in the user code.
How are you proposing to do that? AttributeValRequest
has a mandatory spec_id
field (and no spec_name
field).
Looks significantly better than
I agree, but I don't think it's the whole picture (also, I don't think you'd need anything like self.attr_name_map
). Here's what a full function with attributes would look like, in my view:
ATTR_ID_COLOR = 1
spec = DetectionFunctionSpec(... attributes=[cvaaa.radio_attribute_spec("color", ATTR_ID_COLOR, ...)] ...)
def detect(...):
... attributes=[cvataa.attribute_val(ATTR_ID_COLOR, ...)] ...
And here's what it would look like with attribute names only:
ATTR_NAME_COLOR = "color"
spec = DetectionFunctionSpec(... attributes=[cvaaa.radio_attribute_spec(ATTR_NAME_COLOR, ...)] ...)
def detect(...):
... attributes={ATTR_NAME_COLOR: ...} ...
which is nicer, sure, but I don't think it's a substantial enough improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this example the map and id declaration is converted into a manual declaration of a list of constants. You still need to define and maintain these ids regardless of the declaration style. Technically, in both cases you could write without a constant:
spec = DetectionFunctionSpec(... attributes=[cvaaa.radio_attribute_spec("color", 42, ...)] ...)
def detect(...):
... attributes={42: ...} ...
spec = DetectionFunctionSpec(... attributes=[cvaaa.radio_attribute_spec("color", ...)] ...)
def detect(...):
... attributes={"color": ...} ...
However, with ids you're basically forced to introduce a constant or a mapping or a comment to make the code clear. With attribute names, you can just write the code naturally, even though it's not a good programming style.
In both cases supporting dict of attributes reduces boilerplate code, so it looks like a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I tried to find a solution by adding a more complicated helper function. It takes a dictionary and converts it to a list of AttributeValRequest
s. This function also does type conversions, so it should also cover your concerns from another thread.
However, the attributes are still identified by number. I don't think I can support string identifiers without making everything more complicated than it's worth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think we can stop at this point. It still can be improved, but it's better to do this in a separate PR.
attributes=[ | ||
cvataa.select_attribute_spec("color", 1, ["gray", "calico"]), | ||
cvataa.text_attribute_spec("name (should be ignored)", 2), | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*_attribute_spec
- consider making id optional. Probably, names are unique within a label.
f3ddf32
to
da5ffbf
Compare
da5ffbf
to
1c6212c
Compare
|
Motivation and context
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.
How has this been tested?
Unit tests and manual testing.
Checklist
develop
branch[ ] I have linked related issues (see GitHub docs)License
Feel free to contact the maintainers if that's a concern.