Skip to content

Commit

Permalink
Add attributes filtering in BE
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhail-treskin committed Feb 16, 2022
1 parent 9d97256 commit 18d4b19
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 34 deletions.
4 changes: 2 additions & 2 deletions cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"name": "cvat-ui",
<<<<<<< HEAD
"version": "1.35.1",
=======
"version": "1.34.1",
>>>>>>> 469379f4 (Add serverless function with attributes handling)
"version": "1.35.2",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
92 changes: 73 additions & 19 deletions cvat/apps/lambda_manager/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
from functools import wraps
from enum import Enum
from copy import deepcopy

import django_rq
import requests
Expand Down Expand Up @@ -106,7 +107,12 @@ def __init__(self, gateway, data):
code=status.HTTP_404_NOT_FOUND)
self.labels = labels
# mapping of labels and corresponding supported attributes
self.attributes = {item['name']: item.get('attributes', []) for item in spec}
self.func_attributes = {item['name']: item.get('attributes', []) for item in spec}
for label, attributes in self.func_attributes.items():
if len([attr['name'] for attr in attributes]) != len(set([attr['name'] for attr in attributes])):
raise ValidationError(
"`{}` lambda function has non-unique attributes for label {}".format(self.id, label),
code=status.HTTP_404_NOT_FOUND)
# state of the function
self.state = data['status']['state']
# description of the function
Expand Down Expand Up @@ -149,7 +155,7 @@ def to_dict(self):
})
if self.kind is LambdaType.DETECTOR:
response.update({
'attributes': self.attributes
'attributes': self.func_attributes
})

return response
Expand All @@ -163,19 +169,32 @@ def invoke(self, db_task, data):
"threshold": threshold,
})
quality = data.get("quality")
mapping = data.get("mapping")
mapping_by_default = {db_label.name:db_label.name
for db_label in (
db_task.project.label_set if db_task.project_id else db_task.label_set
).all()}
mapping = data.get("mapping", {})
mapping_by_default = {}
task_attributes = {}
for db_label in (db_task.project.label_set if db_task.project_id else db_task.label_set).prefetch_related("attributespec_set").all():
mapping_by_default[db_label.name] = db_label.name
task_attributes[db_label.name] = {}
for attribute in db_label.attributespec_set.all():
task_attributes[db_label.name][attribute.name] = {
'input_rype': attribute.input_type,
'values': attribute.values.split('\n')
}
if not mapping:
# use mapping by default to avoid labels in mapping which
# don't exist in the task
mapping = mapping_by_default
else:
# filter labels in mapping which don't exist in the task
mapping = {k:v for k,v in mapping.items() if v in mapping_by_default}

supported_attrs = {}
for func_label, func_attrs in self.func_attributes.items():
if func_label in mapping:
supported_attrs[func_label] = {}
task_attr_names = [task_attr for task_attr in task_attributes[mapping[func_label]]]
for attr in func_attrs:
if attr['name'] in task_attr_names:
supported_attrs[func_label].update({attr["name"] : attr})
if self.kind == LambdaType.DETECTOR:
payload.update({
"image": self._get_image(db_task, data["frame"], quality)
Expand Down Expand Up @@ -217,18 +236,53 @@ def invoke(self, db_task, data):
code=status.HTTP_400_BAD_REQUEST)

response = self.gateway.invoke(self, payload)
response_filtered = []
def check_attr_value(value, func_attr, db_attr):
if db_attr is None:
return False
func_attr_type = func_attr["input_type"]
db_attr_type = db_attr["input_type"]
# Check if attribute types are equal for function configuration and db spec
if func_attr_type == db_attr_type:
if func_attr_type == "number":
return value.isnumeric()
elif func_attr_type == "checkbox":
return value in ["true", "false"]
elif func_attr_type in ["select", "radio", "text"]:
return True
else:
return False
else:
if func_attr_type == "number":
return db_attr_type in ["select", "radio", "text"] and value.isnumeric()
elif func_attr_type == "text":
return db_attr_type == "text" or \
(db_attr_type in ["select", "radio"] and len(value.split(" ")) == 1)
elif func_attr_type == "select":
return db_attr["input_type"] in ["radio", "text"]
elif func_attr_type == "radio":
return db_attr["input_type"] in ["select", "text"]
elif func_attr_type == "checkbox":
return value in ["true", "false"]
else:
return False
if self.kind == LambdaType.DETECTOR:
if mapping:
for item in response:
item["label"] = mapping.get(item["label"])
response = [item for item in response if item["label"]]
# TODO: Need to add attributes mapping similar to labels.
# Currently attribute is expicitely discarded if it is not decalred as supported in function config.
if self.attributes:
for item in response:
item['attributes'] = [attr for attr in item.get("attributes", []) if attr['name'] in self.attributes[item['label']]]

return response
for item in response:
if item['label'] in mapping:
attributes = deepcopy(item.get("attributes", []))
item["attributes"] = []
for attr in attributes:
db_attr = supported_attrs.get(item['label'], {}).get(attr["name"])
func_attr = [func_attr for func_attr in self.func_attributes.get(item['label'], []) if func_attr['name'] == attr["name"]]
# Skip current attribute if it was not declared as supportd in function config
if not func_attr:
continue
if attr["name"] in supported_attrs.get(item['label'], {}) and check_attr_value(attr["value"], func_attr[0], db_attr):
item["attributes"].append(attr)
item['label'] = mapping[item['label']]
response_filtered.append(item)

return response_filtered

def _get_image(self, db_task, frame, quality):
if quality is None or quality == "original":
Expand Down
19 changes: 17 additions & 2 deletions serverless/openvino/omz/intel/face-detection-0205/function.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@ metadata:
name: Attributed face detection
type: detector
framework: openvino
# 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"]}
{ "id": 0, "name": "face", "attributes": [
{
"name": "age",
"input_type": "number",
"values": ["0", "150", "1"]
},
{
"name": "gender",
"input_type": "select",
"values": ["female", "male"]
},
{
"name": "emotion",
"input_type": "select",
"values": ["neutral", "happy", "sad", "surprise", "anger"]
}]
}
]
spec:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ def __init__(self):
self.emotions_map = ["neutral", "happy", "sad", "surprise", "anger"]

def infer(self, image):
age_gender_reqest = self.age_gender_model.async_infer(image)
emotions_reqest = self.emotions_model.async_infer(image)
age_gender_request = self.age_gender_model.async_infer(image)
emotions_request = self.emotions_model.async_infer(image)
# Wait until both age_gender and emotion recognition async inferences finish
while not (age_gender_reqest.wait(0) == 0 and emotions_reqest.wait(0) == 0):
while not (age_gender_request.wait(0) == 0 and emotions_request.wait(0) == 0):
continue
age = int(np.squeeze(age_gender_reqest.output_blobs["age_conv3"].buffer) * 100)
gender = self.genders_map[np.argmax(np.squeeze(age_gender_reqest.output_blobs["prob"].buffer))]
emotion = self.emotions_map[np.argmax(np.squeeze(emotions_reqest.output_blobs['prob_emotion'].buffer))]
age = int(np.squeeze(age_gender_request.output_blobs["age_conv3"].buffer) * 100)
gender = self.genders_map[np.argmax(np.squeeze(age_gender_request.output_blobs["prob"].buffer))]
emotion = self.emotions_map[np.argmax(np.squeeze(emotions_request.output_blobs['prob_emotion'].buffer))]
return {"attributes": [
{"name": "age", "value": str(age)},
{"name": "gender", "value": gender},
Expand Down

0 comments on commit 18d4b19

Please sign in to comment.