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

Handle attributes returned from nuclio detector #3917

Merged
merged 8 commits into from
Feb 18, 2022

Conversation

mikhail-treskin
Copy link
Contributor

@mikhail-treskin mikhail-treskin commented Nov 17, 2021

Motivation and context

Currently CVAT ignores attributes info coming from nuclio detection function along with boxes. Such functionality is required for pipelined inference of several models e.g. detector->age/gender/pose recognition.

How has this been tested?

Tested with corresponding "..: test" targets from vscode configurations (logs may be attached if needed).
Manually checked that attributes returned from nuclio applied both in task auto annotation case and using AI tools in annotation mode for certain frame.

Checklist

  • I submit my changes into the develop branch
  • I have added description of my changes into CHANGELOG file
  • I have updated the documentation accordingly - not applicable
  • I have added tests to cover my changes - Added new serveless function
  • I have linked related issues (read github docs)
  • I have increased versions of npm packages if it is necessary (cvat-canvas,
    cvat-core, cvat-data and cvat-ui)

License

  • I submit my code changes under the same MIT License that covers the project.
    Feel free to contact the maintainers if that's a concern.
  • I have updated the license header for each file (see an example below)

@mikhail-treskin
Copy link
Contributor Author

@bsekachev @nmanovic could you please comment if cvat-ui version should be bumped in package.json in case of such change?

@bsekachev
Copy link
Member

bsekachev commented Nov 17, 2021

@mikhail-treskin

case of such change?

Hi, any case (if the patch changes anything). Here, I think need to update patch version number (npm version patch).

@@ -1037,7 +1037,10 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
frame,
occluded: false,
source: 'auto',
attributes: {},
attributes: data.attributes.reduce(function(attrs_map: any, attr: any) {
attrs_map[attr.spec_id] = attr.value;
Copy link
Member

Choose a reason for hiding this comment

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

as far as I understand, attr.spec_id is returned by nuclio function.
At the same time spec_id is the CVAT specific thing which additionally differs task by task. It means that nuclio function should not return these ids, only attribute names and matching should be implemented on CVAT side.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, it does make sense. And I guess the same is applicable for the changes in backend?

Copy link
Member

Choose a reason for hiding this comment

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

Yep. Matching better to be implemented on backend (for both methods, when call automatic annotation for one frame and for a whole task)

Copy link
Contributor Author

@mikhail-treskin mikhail-treskin Nov 24, 2021

Choose a reason for hiding this comment

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

The only one question which I have so far is how to avoid attribute names and spec ids matching on client side in case of one frame auto annotation.

What I could figure out so far regarding attributes adding, is that attrs format expected to be like {spec_id: value} and checked here. So in runInference method I can't create some temporary attribute basing only on name and value, and then in the backend match label+attr_name coming from frontend into spec_id (at least without deeper code changes).

My current implementation of runInference looks like that (not pushed yet until agreement):

runInference={async (task: any, model: Model, body: object) => {
                    try {
                        this.setState({ mode: 'detection', fetching: true });
                        const result = await core.lambda.call(task, model, { ...body, frame })
                        let mapping = {};
                        task.labels.forEach((label: any) => {
                            mapping[label.name] = {};
                            label.attributes.forEach((attr: any) => {
                                mapping[label.name][attr.name] = attr.id;
                            })
                        });
                        const states = result.map(
                            (data: any): any =>
                                new core.classes.ObjectState({
                                    shapeType: data.type,
                                    label: task.labels.filter((label: any): boolean => label.name === data.label)[0],
                                    points: data.points,
                                    objectType: ObjectType.SHAPE,
                                    frame,
                                    occluded: false,
                                    source: 'auto',
                                    attributes: data.attributes.reduce(function(attrs_map: any, attr: any) {
                                        attrs_map[mapping[data.label][attr.name]] = attr.value;
                                        return attrs_map;
                                    }, {}),
                                    zOrder: curZOrder,
                                }),
                        );

Finally, my questions 😄

  • Is it ok to keep mapping of attr_name returned from nuclio to spec_id in the FE?
  • If it's ok to keep mapping in the FE, which approach is more preferable, to build mapping object from the code snippet above on render stage or on inference stage?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , could you please look how it is done for labels? For attributes we need to have mostly the same functionality. A serverless function should declare which attributes it supports (as it is done for labels). Based on the information, it is necessary map attributes per task.

Current patch cannot be applied as is to multiple tasks because spec_id will be different for different tasks.

Please let me know if you want to contribute the functionality. It can be quite complex if it is done in production quality. Also need to have a severless function which supports attributes. It will be an example and can be used for testing. I will move the patch into WIP for now.

Copy link
Contributor Author

@mikhail-treskin mikhail-treskin Dec 3, 2021

Choose a reason for hiding this comment

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

@nmanovic, yes the PR is definitely WIP, just forgot to mark it properly.

Could you please have a look on changes in latest commit? as @bsekachev proposed previously i'm handling attributes returned from nuclio by name and then maps them into spec_id basing on db task data. In context of your request to declare supported attributes in function config it's not a finall solution, but I suppose that in general functionality will be almost the same.

could you please look how it is done for labels? For attributes we need to have mostly the same functionality. A serverless function should declare which attributes it supports (as it is done for labels). Based on the information, it is necessary map attributes per task.

Does it presume that we also need to have functionality for attributes mapping in UI like it's done for labels?

Also need to have a severless function which supports attributes

As an example can propose pipeline of few OMZ models like face_detection->emotion_recognition+age_gender_recognition (or only on of _recognition)

In general, we really need this functionality for our production usage of CVAT, so will be really appreciated if if you could assist with PR productisation. Don't want to keep it in the fork and struggle with rebases 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , the updated patch looks much better. We are more than happy to accept any contribution from our community. Also I will be happy to understand how you are using CVAT in your company and which features are missing. Probably it can be a meeting. Let me know if you are ready to share the information.

An example with OMZ models will be great. Could you please add one or several of them to the PR as serverless functions? If you have any requests to these models, please ping @snosov1. Probably he can help somehow. He also is responsible for OTE: https://github.com/openvinotoolkit/training_extensions

It will be great to map attributes in UI in the future. But let's consider the feature as an optional recommendation. It should not block the PR. I will merge it without the functionality.

Better to decelerate attributes in yml config for a serverless function. Thus it is easy to understand what the function supports.

If you can extend the tutorial: https://openvinotoolkit.github.io/cvat/docs/manual/advanced/serverless-tutorial/, it will be great. But it is an optional recommendation as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nmanovic could you please have a look on serverless function?
I'm not sure regarding face-detection model, tried to choose between 0206 and 0205, looks like 0206 more accurate but much slower than 0205, not sure what is more preferable in case of automatic annotation scenario.

@nmanovic nmanovic changed the title Handle attributes returned from nuclio detector [WIP] Handle attributes returned from nuclio detector Dec 1, 2021
@mikhail-treskin mikhail-treskin force-pushed the mt/attrs_in_detector branch 2 times, most recently from 35dd7e6 to 56a2e46 Compare January 10, 2022 11:57
@nmanovic
Copy link
Contributor

@mikhail-treskin , is the PR ready for review?

@mikhail-treskin
Copy link
Contributor Author

mikhail-treskin commented Jan 12, 2022

@mikhail-treskin , is the PR ready for review?

Yep, I guess so. Had the question above regarding omz model

I'm not sure regarding face-detection model, tried to choose between 0206 and 0205...

maybe better to address it to @snosov1?

@mikhail-treskin mikhail-treskin changed the title [WIP] Handle attributes returned from nuclio detector Handle attributes returned from nuclio detector Jan 12, 2022
# attribute names have to be the same as in annotated task, otherwise values will be ignored
spec: |
[
{ "id": 0, "name": "face", "attributes": ["age", "gender", "emotion"]}
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , thanks for the great PR. Could you please help us to polish the functionality? Each attribute should have a type and possible values. For example, when CVAT returns an attribute (e.g. GET /api/v1/tasks), it returns something like the structure below:

            "labels": [
                {
                    "id": 0,
                    "name": "age",
                    "attributes": [
                        {
                            "id": 0,
                            "name": "age",
                            "input_type": "number",
                            "default_value": "25",
                            "values": [
                                "0",
                                "150",
                                "1"
                            ]
                        }
                    ]
                }
            ],

Do you think it is valuable to have something like that here? Otherwise it is unclear which values each attribute has. Also it will be difficult to much them to a corresponding task. Basically, I should be able to take labels and attributes from the definition as is and create a task with them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree that possible values should be part of function config.
But what about attribute type, i'm not really sure that it does make sense to store it in function config since attribute types is CVAT specific information. Maybe it make sense to add additional attributes verification on backend side, e.g. if certain attribute has type "Select" or "Radio" only one value can be accepted, and check if "Number" attribute coming from nuclio is really convertible to number. Otherwise exception can be raised or fallback to default attribute value.
What do you think about such an approach?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , spec section inside function.yaml is also CVAT specific. Basically I believe that CVAT labels and attributes definition and serverless labels and attributes definition should correspond to each other. Also in the future it is good to implement matching of attributes by name in UI (as it is implemented for labels). It will be necessary to understand the type of an attribute and possible values. I don't suggest implementing matching in UI for the PR. It is another story.

Proposal:

  • Let's have the same definition for attributes as CVAT server support (see an example above).
  • If an attribute is supported by a serverless function, but it isn't supported by a corresponding label in CVAT, it should be ignored.
  • If attributes are matched by name, it is necessary to see if we can cast it to a corresponding type. For example, any attribute can be converted to text type. At the same time text type cannot be converted to select. An attribute, which is matched by name but cannot be casted, should be ignored.

Thus we have a set of labels with attributes from a task. Also we have a number of labels with attributes from a serverless function. We match labels by name. After that we match attributes by name. After that we match attributes by type. During the process some labels and attributes will be filtered. The final list of labels with reduced number of attributes can be covered by our serverless function.
It is a typical case when a serverless function supports more labels and attributes when it is necessary. All of them should be ignored as it is implemented for labels.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nmanovic Could you please have a look on logic of attributes filtering in lambda manager in latest commit?

If it looks fine for you I'll implement same logic in FE in cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx

@nmanovic
Copy link
Contributor

@mikhail-treskin , could you please help us to fix eslint and pylint linters warnings for the PR?

I believe the PR is a great improvement. We definitely want to have it inside the develop branch. Could you please resolve mentioned issues and I will merge it.

@mikhail-treskin
Copy link
Contributor Author

@mikhail-treskin , could you please help us to fix eslint and pylint linters warnings for the PR?

I believe the PR is a great improvement. We definitely want to have it inside the develop branch. Could you please resolve mentioned issues and I will merge it.

Yes, sure. Should be fixed in latest commit.

@@ -138,6 +144,10 @@ def to_dict(self):
response.update({
'state': self.state
})
if self.kind is LambdaType.DETECTOR:
response.update({
'attributes': self.attributes
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , Probably it is slightly better to return attributes together with labels.

Copy link
Contributor Author

@mikhail-treskin mikhail-treskin Jan 20, 2022

Choose a reason for hiding this comment

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

In general I agree but I was afraid that response structure changing may broke compatibility between BE and FE in some cases which I'm not aware. If it will not affect any functionality I definitely can change the code to return attributes along with labels.

@nmanovic
Copy link
Contributor

@mikhail-treskin , thanks for all your time and contribution. Will you be able to continue improving the PR?

emotions_model_xml = os.path.join(emotions_base_dir, "emotions-recognition-retail-0003.xml")
emotions_model_bin = os.path.join(emotions_base_dir, "emotions-recognition-retail-0003.bin")
self.emotions_model = ModelLoader(emotions_model_xml, emotions_model_bin)
self.genders_map = ["female", "male"]
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , better to load these data from function.yaml.

emotions_model_bin = os.path.join(emotions_base_dir, "emotions-recognition-retail-0003.bin")
self.emotions_model = ModelLoader(emotions_model_xml, emotions_model_bin)
self.genders_map = ["female", "male"]
self.emotions_map = ["neutral", "happy", "sad", "surprise", "anger"]
Copy link
Contributor

Choose a reason for hiding this comment

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

@mikhail-treskin , better to load these data from function.yaml.

Copy link
Contributor

@nmanovic nmanovic left a comment

Choose a reason for hiding this comment

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

LGTM

@nmanovic nmanovic merged commit ad11b58 into cvat-ai:develop Feb 18, 2022
@nmanovic nmanovic mentioned this pull request Mar 4, 2022
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants