cvat-ai--cvat
2009 行
75 KiB
Python
2009 行
75 KiB
Python
# Copyright (C) 2021-2022 Intel Corporation
|
|
# Copyright (C) CVAT.ai Corporation
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import base64
|
|
import io
|
|
import json
|
|
import os
|
|
from collections import Counter
|
|
from itertools import groupby
|
|
from unittest import mock, skip
|
|
|
|
import requests
|
|
from django.contrib.auth.models import Group, User
|
|
from django.core.signing import TimestampSigner
|
|
from django.http import HttpResponseNotFound, HttpResponseServerError
|
|
from PIL import Image
|
|
from rest_framework import status
|
|
|
|
from cvat.apps.engine.tests.utils import (
|
|
ApiTestBase,
|
|
ForceLogin,
|
|
check_annotation_response,
|
|
filter_dict,
|
|
generate_image_file,
|
|
get_paginated_collection,
|
|
)
|
|
|
|
LAMBDA_ROOT_PATH = "/api/lambda"
|
|
LAMBDA_FUNCTIONS_PATH = f"{LAMBDA_ROOT_PATH}/functions"
|
|
LAMBDA_REQUESTS_PATH = f"{LAMBDA_ROOT_PATH}/requests"
|
|
|
|
id_function_detector = "test-openvino-omz-public-yolo-v3-tf"
|
|
id_function_reid_with_response_data = "test-openvino-omz-intel-person-reidentification-retail-0300"
|
|
id_function_reid_with_no_response_data = (
|
|
"test-openvino-omz-intel-person-reidentification-retail-1234"
|
|
)
|
|
id_function_interactor = "test-openvino-dextr"
|
|
id_function_tracker = "test-pth-foolwood-siammask"
|
|
id_function_tracker_with_supported_shape_types = "test-tracker-with-supported-shape-types"
|
|
id_function_non_type = "test-model-has-non-type"
|
|
id_function_wrong_type = "test-model-has-wrong-type"
|
|
id_function_unknown_type = "test-model-has-unknown-type"
|
|
id_function_non_unique_labels = "test-model-has-non-unique-labels"
|
|
id_function_state_building = "test-model-has-state-building"
|
|
id_function_state_error = "test-model-has-state-error"
|
|
|
|
expected_keys_in_response_all_functions = ["id", "kind", "labels_v2", "description", "name"]
|
|
expected_keys_in_response_function_interactor = ["min_pos_points", "startswith_box"]
|
|
expected_keys_in_response_function_tracker = ["supported_shape_types"]
|
|
expected_keys_in_response_requests = [
|
|
"id",
|
|
"function",
|
|
"status",
|
|
"progress",
|
|
"enqueued",
|
|
"started",
|
|
"ended",
|
|
"exc_info",
|
|
]
|
|
|
|
path = os.path.join(os.path.dirname(__file__), "assets", "tasks.json")
|
|
with open(path) as f:
|
|
tasks = json.load(f)
|
|
|
|
# removed unnecessary data
|
|
path = os.path.join(os.path.dirname(__file__), "assets", "functions.json")
|
|
with open(path) as f:
|
|
functions = json.load(f)
|
|
|
|
|
|
class _LambdaTestCaseBase(ApiTestBase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.client = self.client_class(raise_request_exception=False)
|
|
|
|
http_patcher = mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway._http",
|
|
side_effect=self._get_data_from_lambda_manager_http,
|
|
)
|
|
self.addCleanup(http_patcher.stop)
|
|
http_patcher.start()
|
|
|
|
invoke_patcher = mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway.invoke", side_effect=self._invoke_function
|
|
)
|
|
self.addCleanup(invoke_patcher.stop)
|
|
invoke_patcher.start()
|
|
|
|
def _get_data_from_lambda_manager_http(self, **kwargs):
|
|
url = kwargs["url"]
|
|
if url == "/api/functions":
|
|
return functions["positive"]
|
|
else:
|
|
func_id = url.split("/")[-1]
|
|
if func_id in functions["positive"]:
|
|
if func_id in [id_function_state_building, id_function_state_error]:
|
|
r = requests.RequestException()
|
|
r.response = HttpResponseServerError()
|
|
raise r # raise 500 Internal_Server error
|
|
|
|
return functions["positive"][func_id]
|
|
else:
|
|
r = requests.HTTPError()
|
|
r.response = HttpResponseNotFound()
|
|
raise r # raise 404 Not Found error
|
|
|
|
def _invoke_function(self, func, payload):
|
|
data = []
|
|
func_id = func.id
|
|
annotations = functions["positive"][func_id]["metadata"]["annotations"]
|
|
type_function = annotations["type"]
|
|
if type_function == "reid":
|
|
if func_id == id_function_reid_with_response_data:
|
|
data = [0, 1]
|
|
else:
|
|
data = []
|
|
elif type_function == "tracker":
|
|
if "supported_shape_types" in annotations:
|
|
for shape in payload["shapes"]:
|
|
self.assertIsInstance(shape, dict)
|
|
self.assertIn("type", shape)
|
|
self.assertIn("points", shape)
|
|
|
|
data = {
|
|
"shapes": [{"type": "rectangle", "points": [12.34, 34.0, 35.01, 41.99]}],
|
|
"states": [{"key": "value"}],
|
|
}
|
|
else:
|
|
for shape in payload["shapes"]:
|
|
self.assertIsInstance(shape, list)
|
|
|
|
data = {
|
|
"shapes": [[12.34, 34.0, 35.01, 41.99]],
|
|
"states": [{"key": "value"}],
|
|
}
|
|
elif type_function == "interactor":
|
|
data = [
|
|
[8, 12],
|
|
[34, 56],
|
|
[77, 77],
|
|
]
|
|
elif type_function == "detector":
|
|
data = [
|
|
{
|
|
"confidence": "0.9959098",
|
|
"label": "car",
|
|
"points": [3, 3, 15, 15],
|
|
"type": "rectangle",
|
|
},
|
|
{
|
|
"confidence": "0.89535173",
|
|
"label": "car",
|
|
"points": [20, 25, 30, 35],
|
|
"type": "rectangle",
|
|
},
|
|
{
|
|
"confidence": "0.59464583",
|
|
"label": "car",
|
|
"points": [10, 10, 10, 20, 20, 10],
|
|
"type": "polygon",
|
|
},
|
|
{
|
|
"confidence": "0.59464583",
|
|
"label": "car",
|
|
"mask": [255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 0, 0, 2, 3],
|
|
"type": "mask",
|
|
},
|
|
]
|
|
|
|
return data
|
|
|
|
@classmethod
|
|
def _create_db_users(cls):
|
|
group_admin, _ = Group.objects.get_or_create(name="admin")
|
|
group_user, _ = Group.objects.get_or_create(name="user")
|
|
|
|
user_admin = User.objects.create_superuser(username="admin", email="", password="admin")
|
|
user_admin.groups.add(group_admin)
|
|
user_dummy = User.objects.create_user(
|
|
username="user", password="user", email="user@example.com"
|
|
)
|
|
user_dummy.groups.add(group_user)
|
|
|
|
cls.admin = user_admin
|
|
cls.user = user_dummy
|
|
|
|
def _create_task(self, task_spec, data, *, owner=None, org_id=None):
|
|
with ForceLogin(owner or self.admin, self.client):
|
|
response = self.client.post(
|
|
"/api/tasks",
|
|
query_params={"org_id": org_id} if org_id is not None else None,
|
|
data=task_spec,
|
|
format="json",
|
|
)
|
|
assert response.status_code == status.HTTP_201_CREATED, response.status_code
|
|
tid = response.data["id"]
|
|
|
|
response = self.client.post(
|
|
f"/api/tasks/{tid}/data",
|
|
query_params={"org_id": org_id} if org_id is not None else None,
|
|
data=data,
|
|
)
|
|
assert response.status_code == status.HTTP_202_ACCEPTED, response.status_code
|
|
rq_id = response.json()["rq_id"]
|
|
|
|
response = self.client.get(f"/api/requests/{rq_id}")
|
|
assert response.status_code == status.HTTP_200_OK, response.status_code
|
|
assert response.json()["status"] == "finished", response.json().get("status")
|
|
|
|
response = self.client.get(
|
|
f"/api/tasks/{tid}",
|
|
query_params={"org_id": org_id} if org_id is not None else None,
|
|
)
|
|
task = response.data
|
|
|
|
return task
|
|
|
|
def _generate_task_images(self, count): # pylint: disable=no-self-use
|
|
images = {f"client_files[{i}]": generate_image_file(f"image_{i}.jpg") for i in range(count)}
|
|
images["image_quality"] = 75
|
|
return images
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls._create_db_users()
|
|
|
|
def _check_expected_keys_in_response_function(self, data):
|
|
kind = data["kind"]
|
|
if kind == "interactor":
|
|
for key in expected_keys_in_response_function_interactor:
|
|
self.assertIn(key, data)
|
|
elif kind == "tracker":
|
|
for key in expected_keys_in_response_function_tracker:
|
|
self.assertIn(key, data)
|
|
|
|
for key in expected_keys_in_response_all_functions:
|
|
self.assertIn(key, data)
|
|
|
|
def _delete_lambda_request(self, request_id: str, user: User | None = None) -> None:
|
|
response = self._delete_request(f"{LAMBDA_REQUESTS_PATH}/{request_id}", user or self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
|
|
|
def _wait_lambda_request(self, request_id: str, user: User | None = None) -> str:
|
|
request_status = "started"
|
|
while request_status != "finished" and request_status != "failed":
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{request_id}", user or self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
request_status = response.json().get("status")
|
|
|
|
return request_status
|
|
|
|
def _get_payload_image_size(self, payload: dict) -> tuple[int, int]:
|
|
image_data = base64.b64decode(payload["image"])
|
|
with Image.open(io.BytesIO(image_data)) as image:
|
|
return image.size
|
|
|
|
|
|
class LambdaTestCases(_LambdaTestCaseBase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
images_main_task = self._generate_task_images(3)
|
|
images_assigneed_to_user_task = self._generate_task_images(3)
|
|
self.main_task = self._create_task(tasks["main"], images_main_task)
|
|
self.assigneed_to_user_task = self._create_task(
|
|
tasks["assigneed_to_user"], images_assigneed_to_user_task
|
|
)
|
|
|
|
def test_api_v2_lambda_functions_list(self):
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for data in response.data:
|
|
self._check_expected_keys_in_response_function(data)
|
|
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for data in response.data:
|
|
self._check_expected_keys_in_response_function(data)
|
|
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
@mock.patch("cvat.apps.lambda_manager.views.LambdaGateway._http", return_value={})
|
|
def test_api_v2_lambda_functions_list_empty(self, mock_http):
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 0)
|
|
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 0)
|
|
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
@mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway._http",
|
|
return_value={
|
|
**functions["negative"],
|
|
id_function_detector: functions["positive"][id_function_detector],
|
|
},
|
|
)
|
|
def test_api_v2_lambda_functions_list_negative(self, mock_http):
|
|
response = self._get_request(LAMBDA_FUNCTIONS_PATH, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
# the positive function must remain visible
|
|
visible_ids = {f["id"] for f in response.data}
|
|
self.assertEqual(visible_ids, {id_function_detector})
|
|
|
|
def test_api_v2_lambda_functions_read(self):
|
|
ids_functions = [
|
|
id_function_detector,
|
|
id_function_interactor,
|
|
id_function_tracker,
|
|
id_function_tracker_with_supported_shape_types,
|
|
id_function_reid_with_response_data,
|
|
]
|
|
|
|
for id_func in ids_functions:
|
|
path = f"{LAMBDA_FUNCTIONS_PATH}/{id_func}"
|
|
|
|
response = self._get_request(path, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self._check_expected_keys_in_response_function(response.data)
|
|
|
|
response = self._get_request(path, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self._check_expected_keys_in_response_function(response.data)
|
|
|
|
response = self._get_request(path, None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_read_wrong_id(self):
|
|
id_wrong_function = "test-functions-wrong-id"
|
|
response = self._get_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
response = self._get_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}", self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
response = self._get_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_wrong_function}", None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_read_negative(self):
|
|
for id_func in [
|
|
id_function_non_type,
|
|
id_function_wrong_type,
|
|
id_function_unknown_type,
|
|
id_function_non_unique_labels,
|
|
]:
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway._http",
|
|
return_value=functions["negative"][id_func],
|
|
):
|
|
response = self._get_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_func}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
@skip("Fail: add mock")
|
|
def test_api_v2_lambda_requests_list(self):
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data[0])
|
|
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data[0])
|
|
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_requests_list_empty(self):
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 0)
|
|
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 0)
|
|
|
|
response = self._get_request(LAMBDA_REQUESTS_PATH, None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_requests_read(self):
|
|
# create request
|
|
data_main_task = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"threshold": 55,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data_main_task)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
id_request = response.data["id"]
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_requests_read_wrong_id(self):
|
|
id_request = "cf343b95-afeb-475e-ab53-8d7e64991d30-wrong-id"
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_requests_delete_finished_request(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
id_request = response.data["id"]
|
|
|
|
response = self._delete_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", None)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
response = self._delete_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
id_request = response.data["id"]
|
|
response = self._delete_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
@skip("Fail: add mock")
|
|
def test_api_v2_lambda_requests_delete_not_finished_request(self):
|
|
pass
|
|
|
|
def test_api_v2_lambda_requests_create(self):
|
|
ids_functions = [
|
|
id_function_detector,
|
|
id_function_interactor,
|
|
id_function_tracker,
|
|
id_function_reid_with_response_data,
|
|
id_function_detector,
|
|
id_function_reid_with_no_response_data,
|
|
]
|
|
|
|
for id_func in ids_functions:
|
|
data_main_task = {
|
|
"function": id_func,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"threshold": 55,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
data_assigneed_to_user_task = {
|
|
"function": id_func,
|
|
"task": self.assigneed_to_user_task["id"],
|
|
"cleanup": False,
|
|
"max_distance": 70,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data_main_task)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
self._delete_lambda_request(response.data["id"])
|
|
|
|
response = self._post_request(
|
|
LAMBDA_REQUESTS_PATH, self.user, data=data_assigneed_to_user_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
self._delete_lambda_request(response.data["id"], self.user)
|
|
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.user, data=data_main_task)
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, None, data=data_main_task)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_requests_create_negative(self):
|
|
for id_func in [
|
|
id_function_non_type,
|
|
id_function_wrong_type,
|
|
id_function_unknown_type,
|
|
id_function_non_unique_labels,
|
|
]:
|
|
data = {
|
|
"function": id_func,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway._http",
|
|
return_value=functions["negative"][id_func],
|
|
):
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
def test_api_v2_lambda_requests_create_empty_data(self):
|
|
data = {}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_requests_create_without_function(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_requests_create_wrong_id_function(self):
|
|
data = {
|
|
"function": "test-requests-wrong-id",
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
@skip("Fail: add mock")
|
|
def test_api_v2_lambda_requests_create_two_requests(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
request_id = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data).data["id"]
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
|
|
|
|
self._delete_lambda_request(request_id)
|
|
|
|
def test_api_v2_lambda_requests_create_empty_mapping(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
self._delete_lambda_request(response.data["id"])
|
|
|
|
def test_api_v2_lambda_requests_create_without_cleanup(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
self._delete_lambda_request(response.data["id"])
|
|
|
|
def test_api_v2_lambda_requests_create_without_mapping(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
for key in expected_keys_in_response_requests:
|
|
self.assertIn(key, response.data)
|
|
|
|
self._delete_lambda_request(response.data["id"])
|
|
|
|
def test_api_v2_lambda_requests_create_without_task(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_requests_create_wrong_id_task(self):
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": 12345,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_requests_create_is_not_ready(self):
|
|
ids_functions = [id_function_state_building, id_function_state_error]
|
|
|
|
for id_func in ids_functions:
|
|
data = {
|
|
"function": id_func,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
def test_api_v2_lambda_functions_create_detector(self):
|
|
data_main_task = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"threshold": 0.55,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
data_assigneed_to_user_task = {
|
|
"task": self.assigneed_to_user_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}",
|
|
self.user,
|
|
data=data_assigneed_to_user_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", None, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_with_roi(self):
|
|
roi = [11, 13, 31, 43]
|
|
|
|
def invoke_detector_with_roi(func, payload):
|
|
self.assertEqual(func.id, id_function_detector)
|
|
self.assertEqual(self._get_payload_image_size(payload), (20, 30))
|
|
return [
|
|
{
|
|
"confidence": "0.9959098",
|
|
"label": "car",
|
|
"points": [1, 2, 5, 6],
|
|
"type": "rectangle",
|
|
},
|
|
]
|
|
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
"roi": roi,
|
|
}
|
|
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway.invoke",
|
|
side_effect=invoke_detector_with_roi,
|
|
):
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
|
|
self.assertEqual(response.json()["shapes"][0]["points"], [12.0, 15.0, 16.0, 19.0])
|
|
|
|
def test_api_v2_lambda_requests_create_detector_with_roi(self):
|
|
roi = [10, 20, 30, 50]
|
|
|
|
def invoke_detector_with_roi(func, payload):
|
|
self.assertEqual(func.id, id_function_detector)
|
|
self.assertEqual(self._get_payload_image_size(payload), (20, 30))
|
|
return [
|
|
{
|
|
"confidence": "0.9959098",
|
|
"label": "car",
|
|
"points": [3, 4, 15, 16],
|
|
"type": "rectangle",
|
|
},
|
|
]
|
|
|
|
data = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
"roi": roi,
|
|
}
|
|
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway.invoke",
|
|
side_effect=invoke_detector_with_roi,
|
|
) as mock_invoke:
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
|
|
request_id = response.data["id"]
|
|
|
|
request_status = self._wait_lambda_request(request_id)
|
|
self.assertEqual(request_status, "finished")
|
|
|
|
self.assertEqual(mock_invoke.call_count, 3)
|
|
self._delete_lambda_request(request_id)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.main_task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
shapes = response.json()["shapes"]
|
|
self.assertEqual(len(shapes), 3)
|
|
for shape in shapes:
|
|
self.assertEqual(shape["points"], [13.0, 24.0, 25.0, 36.0])
|
|
|
|
def test_api_v2_lambda_functions_detector_invalid_roi(self):
|
|
invalid_rois = {
|
|
"too_few_coordinates": [0, 0, 10],
|
|
"too_many_coordinates": [0, 0, 10, 10, 10],
|
|
"negative_coordinate": [-1, 0, 10, 10],
|
|
"zero_width": [10, 10, 10, 20],
|
|
"zero_height": [10, 10, 20, 10],
|
|
"outside_image": [0, 0, 101, 100],
|
|
}
|
|
|
|
with mock.patch("cvat.apps.lambda_manager.views.LambdaGateway.invoke") as mock_invoke:
|
|
for name, roi in invalid_rois.items():
|
|
with self.subTest(path="request", roi=name):
|
|
response = self._post_request(
|
|
LAMBDA_REQUESTS_PATH,
|
|
self.admin,
|
|
data={
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
"roi": roi,
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
with self.subTest(path="call", roi=name):
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}",
|
|
self.admin,
|
|
data={
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
"roi": roi,
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
mock_invoke.assert_not_called()
|
|
|
|
@skip(
|
|
"Fail: expected result != actual result"
|
|
) # TODO move test to test_api_v2_lambda_functions_create
|
|
def test_api_v2_lambda_functions_create_user_assigned_to_no_user(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.user, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
|
|
def test_api_v2_lambda_functions_create_interactor(self):
|
|
data_main_task = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"pos_points": [
|
|
[3.45, 6.78],
|
|
[12.1, 12.1],
|
|
[34.1, 41.0],
|
|
[43.01, 43.99],
|
|
],
|
|
"neg_points": [
|
|
[3.25, 6.58],
|
|
[11.1, 11.0],
|
|
[35.5, 44.44],
|
|
[45.01, 45.99],
|
|
],
|
|
}
|
|
data_assigneed_to_user_task = {
|
|
"task": self.assigneed_to_user_task["id"],
|
|
"frame": 0,
|
|
"threshold": 0.1,
|
|
"pos_points": [
|
|
[3.45, 6.78],
|
|
[12.1, 12.1],
|
|
[34.1, 41.0],
|
|
[43.01, 43.99],
|
|
],
|
|
"neg_points": [
|
|
[3.25, 6.58],
|
|
[11.1, 11.0],
|
|
[35.5, 44.44],
|
|
[45.01, 45.99],
|
|
],
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", self.admin, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}",
|
|
self.user,
|
|
data=data_assigneed_to_user_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", None, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_create_interactor_with_roi(self):
|
|
roi = [10, 20, 30, 50]
|
|
|
|
def invoke_interactor_with_roi(func, payload):
|
|
self.assertEqual(func.id, id_function_interactor)
|
|
self.assertEqual(self._get_payload_image_size(payload), (20, 30))
|
|
self.assertEqual(payload["pos_points"], [[2, 3], [19, 29]])
|
|
self.assertEqual(payload["neg_points"], [[1, 2]])
|
|
self.assertEqual(payload["obj_bbox"], [[0, 0], [20, 30]])
|
|
return {
|
|
"shapes": [
|
|
{
|
|
"type": "polygon",
|
|
"points": [1, 2, 3, 4, 5, 6],
|
|
},
|
|
],
|
|
}
|
|
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"pos_points": [[12, 23], [29, 49]],
|
|
"neg_points": [[11, 22]],
|
|
"obj_bbox": [[10, 20], [30, 50]],
|
|
"roi": roi,
|
|
}
|
|
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway.invoke",
|
|
side_effect=invoke_interactor_with_roi,
|
|
):
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_interactor}", self.admin, data=data
|
|
)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
|
|
self.assertEqual(response.json()["shapes"][0]["points"], [11, 22, 13, 24, 15, 26])
|
|
|
|
def test_api_v2_lambda_functions_create_tracker(self):
|
|
for id_func in [
|
|
id_function_tracker,
|
|
id_function_tracker_with_supported_shape_types,
|
|
]:
|
|
data_main_task = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"shapes": [{"type": "rectangle", "points": [12.12, 34.45, 54.0, 76.12]}],
|
|
}
|
|
data_assigneed_to_user_task = {
|
|
"task": self.assigneed_to_user_task["id"],
|
|
"frame": 0,
|
|
"shapes": [{"type": "rectangle", "points": [12.12, 34.45, 54.0, 76.12]}],
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_func}", self.admin, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_func}",
|
|
self.user,
|
|
data=data_assigneed_to_user_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_func}", None, data=data_main_task
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_create_tracker_bad_signature(self):
|
|
signer = TimestampSigner(key="bad key")
|
|
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"states": [signer.sign("{}")],
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_tracker}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
self.assertIn("Invalid or expired tracker state", response.content.decode("UTF-8"))
|
|
|
|
def test_api_v2_lambda_functions_create_tracker_unsupported_shape_type(self):
|
|
for id_func in [
|
|
id_function_tracker,
|
|
id_function_tracker_with_supported_shape_types,
|
|
]:
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"shapes": [{"type": "points", "points": [1, 2, 3, 4]}],
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_func}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
self.assertIn("This function does not support shapes", response.content.decode("UTF-8"))
|
|
|
|
def test_api_v2_lambda_functions_create_reid(self):
|
|
data_main_task = {
|
|
"task": self.main_task["id"],
|
|
"frame0": 0,
|
|
"frame1": 1,
|
|
"boxes0": [
|
|
{
|
|
"attributes": [],
|
|
"frame": 0,
|
|
"group": None,
|
|
"id": 11258,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"path_id": 0,
|
|
"points": [137.0, 129.0, 457.0, 676.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
{
|
|
"attributes": [],
|
|
"frame": 0,
|
|
"group": None,
|
|
"id": 11259,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"path_id": 1,
|
|
"points": [1511.0, 224.0, 1537.0, 437.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
],
|
|
"boxes1": [
|
|
{
|
|
"attributes": [],
|
|
"frame": 1,
|
|
"group": None,
|
|
"id": 11260,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"points": [1076.0, 199.0, 1218.0, 593.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
{
|
|
"attributes": [],
|
|
"frame": 1,
|
|
"group": None,
|
|
"id": 11261,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"points": [924.0, 177.0, 1090.0, 615.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
],
|
|
"threshold": 0.5,
|
|
"max_distance": 55,
|
|
}
|
|
data_assigneed_to_user_task = {
|
|
"task": self.assigneed_to_user_task["id"],
|
|
"frame0": 0,
|
|
"frame1": 1,
|
|
"boxes0": [
|
|
{
|
|
"attributes": [],
|
|
"frame": 0,
|
|
"group": None,
|
|
"id": 11258,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"path_id": 0,
|
|
"points": [137.0, 129.0, 457.0, 676.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
{
|
|
"attributes": [],
|
|
"frame": 0,
|
|
"group": None,
|
|
"id": 11259,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"path_id": 1,
|
|
"points": [1511.0, 224.0, 1537.0, 437.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
],
|
|
"boxes1": [
|
|
{
|
|
"attributes": [],
|
|
"frame": 1,
|
|
"group": None,
|
|
"id": 11260,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"points": [1076.0, 199.0, 1218.0, 593.0],
|
|
"source": "auto",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
},
|
|
{
|
|
"attributes": [],
|
|
"frame": 1,
|
|
"group": 0,
|
|
"id": 11398,
|
|
"label_id": 8,
|
|
"occluded": False,
|
|
"points": [
|
|
184.3935546875,
|
|
211.5048828125,
|
|
331.64968722073354,
|
|
97.27792672028772,
|
|
445.87667560321825,
|
|
126.17873100983161,
|
|
454.13404825737416,
|
|
691.8087578194827,
|
|
180.26452189455085,
|
|
],
|
|
"source": "manual",
|
|
"type": "polygon",
|
|
"z_order": 0,
|
|
},
|
|
],
|
|
}
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_response_data}",
|
|
self.admin,
|
|
data=data_main_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_response_data}",
|
|
self.user,
|
|
data=data_assigneed_to_user_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_response_data}",
|
|
None,
|
|
data=data_main_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_no_response_data}",
|
|
self.admin,
|
|
data=data_main_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_no_response_data}",
|
|
self.user,
|
|
data=data_assigneed_to_user_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_reid_with_no_response_data}",
|
|
None,
|
|
data=data_main_task,
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
|
|
def test_api_v2_lambda_functions_create_negative(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
for id_func in [
|
|
id_function_non_type,
|
|
id_function_wrong_type,
|
|
id_function_unknown_type,
|
|
id_function_non_unique_labels,
|
|
]:
|
|
with mock.patch(
|
|
"cvat.apps.lambda_manager.views.LambdaGateway._http",
|
|
return_value=functions["negative"][id_func],
|
|
):
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_func}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
def test_api_v2_lambda_functions_convert_mask_to_rle(self):
|
|
data_main_task = {
|
|
"function": id_function_detector,
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, self.admin, data=data_main_task)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
id_request = response.data["id"]
|
|
|
|
request_status = "started"
|
|
while request_status != "finished" and request_status != "failed":
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{id_request}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
request_status = response.json().get("status")
|
|
self.assertEqual(request_status, "finished")
|
|
|
|
self._delete_lambda_request(id_request)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.main_task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
masks = [shape for shape in response.json().get("shapes", []) if shape["type"] == "mask"]
|
|
|
|
# [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0] -> [0, 2, 2, 2, 2, 2, 2]
|
|
self.assertEqual(masks[0].get("points"), [0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 3])
|
|
|
|
def test_api_v2_lambda_functions_create_empty_data(self):
|
|
data = {}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_empty_mapping(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_without_cleanup(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_without_mapping(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_all_shapes_unmapped(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"mapping": {"person": {"name": "person"}},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
annotations = response.json()
|
|
self.assertEqual(annotations["shapes"], [])
|
|
|
|
def test_api_v2_lambda_functions_create_detector_without_task(self):
|
|
data = {
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_functions_create_detector_without_id_frame(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_api_v2_lambda_functions_create_wrong_id_function(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/test-functions-wrong-id", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
|
|
def test_api_v2_lambda_functions_create_wrong_id_task(self):
|
|
data = {
|
|
"task": 12345,
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
@skip("Fail: expected result != actual result, issue #2770")
|
|
def test_api_v2_lambda_functions_create_detector_wrong_id_frame(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 12345,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
@skip("Fail: add mock and expected result != actual result")
|
|
def test_api_v2_lambda_functions_create_two_functions(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data)
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
|
|
|
|
def test_api_v2_lambda_functions_create_function_is_not_ready(self):
|
|
data = {
|
|
"task": self.main_task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_state_building}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
response = self._post_request(
|
|
f"{LAMBDA_FUNCTIONS_PATH}/{id_function_state_error}", self.admin, data=data
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
class TestComplexFrameSetupCases(_LambdaTestCaseBase):
|
|
def _invoke_function(self, func, payload):
|
|
data = []
|
|
func_id = func.id
|
|
type_function = functions["positive"][func_id]["metadata"]["annotations"]["type"]
|
|
if type_function == "reid":
|
|
if func_id == id_function_reid_with_response_data:
|
|
data = [0]
|
|
else:
|
|
data = []
|
|
elif type_function == "tracker":
|
|
data = {
|
|
"shape": [12.34, 34.0, 35.01, 41.99],
|
|
"state": {"key": "value"},
|
|
}
|
|
elif type_function == "interactor":
|
|
data = [
|
|
[8, 12],
|
|
[34, 56],
|
|
[77, 77],
|
|
]
|
|
elif type_function == "detector":
|
|
data = [
|
|
{
|
|
"confidence": "0.9959098",
|
|
"label": "car",
|
|
"points": [3, 3, 15, 15],
|
|
"type": "rectangle",
|
|
},
|
|
]
|
|
|
|
return data
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
image_count = 50
|
|
frame_step = 5
|
|
start_frame = 3
|
|
stop_frame = image_count - 4
|
|
segment_size = 2
|
|
|
|
data = self._generate_task_images(image_count)
|
|
data["frame_filter"] = f"step={frame_step}"
|
|
data["start_frame"] = start_frame
|
|
data["stop_frame"] = stop_frame
|
|
|
|
self.task = self._create_task(
|
|
task_spec={
|
|
"name": "test_task",
|
|
"labels": [{"name": "car"}],
|
|
"segment_size": segment_size,
|
|
},
|
|
data=data,
|
|
owner=self.user,
|
|
)
|
|
self.task_rel_frame_range = range(len(range(start_frame, stop_frame, frame_step)))
|
|
self.start_frame = start_frame
|
|
self.frame_step = frame_step
|
|
self.segment_size = segment_size
|
|
|
|
self.labels = get_paginated_collection(
|
|
lambda page: self._get_request(
|
|
"/api/labels",
|
|
self.admin,
|
|
query_params={"task_id": self.task["id"], "page": page, "sort": "id"},
|
|
)
|
|
)
|
|
|
|
self.jobs = get_paginated_collection(
|
|
lambda page: self._get_request(
|
|
"/api/jobs",
|
|
self.admin,
|
|
query_params={"task_id": self.task["id"], "page": page},
|
|
)
|
|
)
|
|
|
|
self.detector_function_id = id_function_detector
|
|
self.reid_function_id = id_function_reid_with_response_data
|
|
|
|
self.common_request_data = {
|
|
"task": self.task["id"],
|
|
"cleanup": True,
|
|
}
|
|
|
|
def _run_offline_function(self, function_id, data, user):
|
|
data["function"] = function_id
|
|
response = self._post_request(LAMBDA_REQUESTS_PATH, user, data=data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
|
|
request_id = response.json()["id"]
|
|
|
|
request_status = self._wait_request(request_id)
|
|
self.assertEqual(request_status, "finished")
|
|
self._delete_lambda_request(request_id, user)
|
|
|
|
def _wait_request(self, request_id: str) -> str:
|
|
request_status = "started"
|
|
while request_status != "finished" and request_status != "failed":
|
|
response = self._get_request(f"{LAMBDA_REQUESTS_PATH}/{request_id}", self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
request_status = response.json().get("status")
|
|
|
|
return request_status
|
|
|
|
def _run_online_function(self, function_id, data, user):
|
|
response = self._post_request(f"{LAMBDA_FUNCTIONS_PATH}/{function_id}", user, data=data)
|
|
return response
|
|
|
|
def test_can_run_offline_detector_function_on_whole_task(self):
|
|
data = self.common_request_data.copy()
|
|
self._run_offline_function(self.detector_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["tracks"]), 0)
|
|
|
|
requested_frame_range = self.task_rel_frame_range
|
|
self.assertEqual(
|
|
{frame: 1 for frame in requested_frame_range},
|
|
{
|
|
frame: len(list(group))
|
|
for frame, group in groupby(annotations["shapes"], key=lambda a: a["frame"])
|
|
},
|
|
)
|
|
|
|
def test_can_run_offline_reid_function_on_whole_task(self):
|
|
# Add starting shapes to be tracked on following frames
|
|
requested_frame_range = self.task_rel_frame_range
|
|
shape_template = {
|
|
"attributes": [],
|
|
"group": None,
|
|
"label_id": self.labels[0]["id"],
|
|
"occluded": False,
|
|
"points": [0, 5, 5, 0],
|
|
"source": "manual",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
}
|
|
response = self._put_request(
|
|
f'/api/tasks/{self.task["id"]}/annotations',
|
|
self.admin,
|
|
data={
|
|
"tags": [],
|
|
"shapes": [{"frame": frame, **shape_template} for frame in requested_frame_range],
|
|
"tracks": [],
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
data = self.common_request_data.copy()
|
|
data["cleanup"] = False # cleanup is not compatible with reid
|
|
self._run_offline_function(self.reid_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["shapes"]), 0)
|
|
self.assertEqual(
|
|
[
|
|
# The single track will be split by job segments
|
|
{
|
|
"frame": job["start_frame"],
|
|
"shapes": [
|
|
{"frame": frame, "outside": frame > job["stop_frame"]}
|
|
for frame in requested_frame_range
|
|
if frame in range(job["start_frame"], job["stop_frame"] + self.segment_size)
|
|
],
|
|
}
|
|
for job in sorted(self.jobs, key=lambda j: j["start_frame"])
|
|
],
|
|
[
|
|
{
|
|
"frame": track["frame"],
|
|
"shapes": [
|
|
filter_dict(shape, keep=["frame", "outside"]) for shape in track["shapes"]
|
|
],
|
|
}
|
|
for track in annotations["tracks"]
|
|
],
|
|
)
|
|
|
|
def test_can_run_offline_detector_function_on_whole_job(self):
|
|
data = self.common_request_data.copy()
|
|
job = self.jobs[3]
|
|
data["job"] = job["id"]
|
|
self._run_offline_function(self.detector_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["tracks"]), 0)
|
|
|
|
requested_frame_range = range(job["start_frame"], job["stop_frame"] + 1)
|
|
self.assertEqual(
|
|
{frame: 1 for frame in requested_frame_range},
|
|
{
|
|
frame: len(list(group))
|
|
for frame, group in groupby(annotations["shapes"], key=lambda a: a["frame"])
|
|
},
|
|
)
|
|
|
|
def test_can_run_offline_reid_function_on_whole_job(self):
|
|
job = self.jobs[3]
|
|
requested_frame_range = range(job["start_frame"], job["stop_frame"] + 1)
|
|
|
|
# Add starting shapes to be tracked on following frames
|
|
shape_template = {
|
|
"attributes": [],
|
|
"group": None,
|
|
"label_id": self.labels[0]["id"],
|
|
"occluded": False,
|
|
"points": [0, 5, 5, 0],
|
|
"source": "manual",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
}
|
|
response = self._put_request(
|
|
f'/api/jobs/{job["id"]}/annotations',
|
|
self.admin,
|
|
data={
|
|
"tags": [],
|
|
"shapes": [{"frame": frame, **shape_template} for frame in requested_frame_range],
|
|
"tracks": [],
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
data = self.common_request_data.copy()
|
|
data["cleanup"] = False # cleanup is not compatible with reid
|
|
data["job"] = job["id"]
|
|
self._run_offline_function(self.reid_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/jobs/{job["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["shapes"]), 0)
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
"frame": job["start_frame"],
|
|
"shapes": [
|
|
{"frame": frame, "outside": frame > job["stop_frame"]}
|
|
for frame in requested_frame_range
|
|
if frame in range(job["start_frame"], job["stop_frame"] + self.segment_size)
|
|
],
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"frame": track["frame"],
|
|
"shapes": [
|
|
filter_dict(shape, keep=["frame", "outside"]) for shape in track["shapes"]
|
|
],
|
|
}
|
|
for track in annotations["tracks"]
|
|
],
|
|
)
|
|
|
|
def test_can_run_offline_detector_function_on_whole_gt_job(self):
|
|
requested_frame_range = self.task_rel_frame_range[::3]
|
|
response = self._post_request(
|
|
"/api/jobs",
|
|
self.admin,
|
|
data={
|
|
"type": "ground_truth",
|
|
"task_id": self.task["id"],
|
|
"frame_selection_method": "manual",
|
|
"frames": list(requested_frame_range),
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
job = response.json()
|
|
|
|
data = self.common_request_data.copy()
|
|
data["job"] = job["id"]
|
|
self._run_offline_function(self.detector_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/jobs/{job["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["tracks"]), 0)
|
|
|
|
self.assertEqual(
|
|
{frame: 1 for frame in requested_frame_range},
|
|
Counter(a["frame"] for a in annotations["shapes"]),
|
|
)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
check_annotation_response(self, response, {})
|
|
|
|
def test_can_run_offline_reid_function_on_whole_gt_job(self):
|
|
requested_frame_range = self.task_rel_frame_range[::3]
|
|
response = self._post_request(
|
|
"/api/jobs",
|
|
self.admin,
|
|
data={
|
|
"type": "ground_truth",
|
|
"task_id": self.task["id"],
|
|
"frame_selection_method": "manual",
|
|
"frames": list(requested_frame_range),
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
job = response.json()
|
|
|
|
# Add starting shapes to be tracked on following frames
|
|
shape_template = {
|
|
"attributes": [],
|
|
"group": None,
|
|
"label_id": self.labels[0]["id"],
|
|
"occluded": False,
|
|
"points": [0, 5, 5, 0],
|
|
"source": "manual",
|
|
"type": "rectangle",
|
|
"z_order": 0,
|
|
}
|
|
response = self._put_request(
|
|
f'/api/jobs/{job["id"]}/annotations',
|
|
self.admin,
|
|
data={
|
|
"tags": [],
|
|
"shapes": [{"frame": frame, **shape_template} for frame in requested_frame_range],
|
|
"tracks": [],
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
data = self.common_request_data.copy()
|
|
data["cleanup"] = False # cleanup is not compatible with reid
|
|
data["job"] = job["id"]
|
|
self._run_offline_function(self.reid_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/jobs/{job["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["shapes"]), 0)
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
"frame": job["start_frame"],
|
|
"shapes": [
|
|
{"frame": frame, "outside": frame > job["stop_frame"]}
|
|
for frame in requested_frame_range
|
|
if frame in range(job["start_frame"], job["stop_frame"] + self.segment_size)
|
|
],
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"frame": track["frame"],
|
|
"shapes": [
|
|
filter_dict(shape, keep=["frame", "outside"]) for shape in track["shapes"]
|
|
],
|
|
}
|
|
for track in annotations["tracks"]
|
|
],
|
|
)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
check_annotation_response(self, response, {})
|
|
|
|
def test_offline_function_run_on_task_does_not_affect_gt_job(self):
|
|
response = self._post_request(
|
|
"/api/jobs",
|
|
self.admin,
|
|
data={
|
|
"type": "ground_truth",
|
|
"task_id": self.task["id"],
|
|
"frame_selection_method": "manual",
|
|
"frames": list(self.task_rel_frame_range[::3]),
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
job = response.json()
|
|
|
|
data = self.common_request_data.copy()
|
|
self._run_offline_function(self.detector_function_id, data, self.user)
|
|
|
|
response = self._get_request(f'/api/tasks/{self.task["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
annotations = response.json()
|
|
|
|
self.assertEqual(len(annotations["tags"]), 0)
|
|
self.assertEqual(len(annotations["tracks"]), 0)
|
|
|
|
requested_frame_range = self.task_rel_frame_range
|
|
self.assertEqual(
|
|
{frame: 1 for frame in requested_frame_range},
|
|
Counter(a["frame"] for a in annotations["shapes"]),
|
|
)
|
|
|
|
response = self._get_request(f'/api/jobs/{job["id"]}/annotations', self.admin)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
check_annotation_response(self, response, {})
|
|
|
|
def test_can_run_online_function_on_valid_task_frame(self):
|
|
data = self.common_request_data.copy()
|
|
requested_frame = self.task_rel_frame_range[4]
|
|
data["frame"] = requested_frame
|
|
|
|
response = self._run_online_function(self.detector_function_id, data, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
annotations = response.json()
|
|
self.assertEqual(1, len(annotations["shapes"]))
|
|
|
|
def test_can_run_online_function_on_invalid_task_frame(self):
|
|
data = self.common_request_data.copy()
|
|
requested_frame = self.task_rel_frame_range[-1] + 1
|
|
data["frame"] = requested_frame
|
|
|
|
response = self._run_online_function(self.detector_function_id, data, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_can_run_online_function_on_valid_job_frame(self):
|
|
data = self.common_request_data.copy()
|
|
job = self.jobs[2]
|
|
requested_frame = job["start_frame"] + 1
|
|
data["frame"] = requested_frame
|
|
data["job"] = job["id"]
|
|
|
|
response = self._run_online_function(self.detector_function_id, data, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
annotations = response.json()
|
|
self.assertEqual(1, len(annotations["shapes"]))
|
|
|
|
def test_can_run_online_function_on_invalid_job_frame(self):
|
|
data = self.common_request_data.copy()
|
|
job = self.jobs[2]
|
|
requested_frame = job["stop_frame"] + 1
|
|
data["frame"] = requested_frame
|
|
data["job"] = job["id"]
|
|
|
|
response = self._run_online_function(self.detector_function_id, data, self.user)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class Issue4996_Cases(_LambdaTestCaseBase):
|
|
# Check regressions for https://github.com/cvat-ai/cvat/issues/4996#issuecomment-1266123032
|
|
# We need to check that job assignee can call functions in the assigned jobs
|
|
# This requires to pass the job id in the call request.
|
|
|
|
def _create_org(self, *, owner: int, members: dict[int, str] = None) -> dict:
|
|
org = self._post_request(
|
|
"/api/organizations",
|
|
user=owner,
|
|
data={
|
|
"slug": "testorg",
|
|
"name": "test Org",
|
|
},
|
|
)
|
|
assert org.status_code == status.HTTP_201_CREATED
|
|
org = org.json()
|
|
|
|
for uid, role in members.items():
|
|
user = self._get_request("/api/users/self", user=uid)
|
|
assert user.status_code == status.HTTP_200_OK
|
|
user = user.json()
|
|
|
|
invitation = self._post_request(
|
|
"/api/invitations",
|
|
user=owner,
|
|
data={
|
|
"email": user["email"],
|
|
"role": role,
|
|
},
|
|
query_params={"org_id": org["id"]},
|
|
)
|
|
assert invitation.status_code == status.HTTP_201_CREATED
|
|
|
|
return org
|
|
|
|
def _set_task_assignee(self, task: int, assignee: int | None):
|
|
response = self._patch_request(
|
|
f"/api/tasks/{task}",
|
|
user=self.admin,
|
|
data={
|
|
"assignee_id": assignee,
|
|
},
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
def _set_job_assignee(self, job: int, assignee: int | None):
|
|
response = self._patch_request(
|
|
f"/api/jobs/{job}",
|
|
user=self.admin,
|
|
data={
|
|
"assignee": assignee,
|
|
},
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.org = self._create_org(owner=self.admin, members={self.user: "worker"})
|
|
|
|
task = self._create_task(
|
|
task_spec={"name": "test_task", "labels": [{"name": "car"}], "segment_size": 2},
|
|
data=self._generate_task_images(6),
|
|
owner=self.admin,
|
|
org_id=self.org["id"],
|
|
)
|
|
self.task = task
|
|
|
|
jobs = get_paginated_collection(
|
|
lambda page: self._get_request(
|
|
"/api/jobs",
|
|
self.admin,
|
|
query_params={"task_id": self.task["id"], "page": page, "org_id": self.org["id"]},
|
|
)
|
|
)
|
|
self.job = jobs[1]
|
|
|
|
self.common_request_data = {
|
|
"task": self.task["id"],
|
|
"frame": 0,
|
|
"cleanup": True,
|
|
"mapping": {
|
|
"car": {"name": "car"},
|
|
},
|
|
}
|
|
|
|
self.function_url = f"{LAMBDA_FUNCTIONS_PATH}/{id_function_detector}"
|
|
|
|
def _get_valid_job_request_data(self):
|
|
data = self.common_request_data.copy()
|
|
data.update({"job": self.job["id"], "frame": 2})
|
|
return data
|
|
|
|
def _get_invalid_job_request_data(self):
|
|
data = self.common_request_data.copy()
|
|
data.update({"job": self.job["id"], "frame": 0})
|
|
return data
|
|
|
|
def test_can_call_function_for_job_worker_in_org__deny_unassigned_worker_with_task_request(
|
|
self,
|
|
):
|
|
data = self.common_request_data.copy()
|
|
with self.subTest(job=None, assignee=None):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
|
|
def test_can_call_function_for_job_worker_in_org__deny_unassigned_worker_with_job_request(self):
|
|
data = self._get_valid_job_request_data()
|
|
with self.subTest(job="defined", assignee=None):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
|
|
def test_can_call_function_for_job_worker_in_org__allow_task_assigned_worker_with_task_request(
|
|
self,
|
|
):
|
|
self._set_task_assignee(self.task["id"], self.user.id)
|
|
|
|
data = self.common_request_data.copy()
|
|
with self.subTest(job=None, assignee="task"):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
def test_can_call_function_for_job_worker_in_org__deny_job_assigned_worker_with_task_request(
|
|
self,
|
|
):
|
|
self._set_job_assignee(self.job["id"], self.user.id)
|
|
|
|
data = self.common_request_data.copy()
|
|
with self.subTest(job=None, assignee="job"):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
|
|
def test_can_call_function_for_job_worker_in_org__allow_job_assigned_worker_with_job_request(
|
|
self,
|
|
):
|
|
self._set_job_assignee(self.job["id"], self.user.id)
|
|
|
|
data = self._get_valid_job_request_data()
|
|
with self.subTest(job="defined", assignee="job"):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
def test_can_check_job_boundaries_in_function_call__fail_for_frame_outside_job(self):
|
|
self._set_job_assignee(self.job["id"], self.user.id)
|
|
|
|
data = self._get_invalid_job_request_data()
|
|
with self.subTest(job="defined", frame="outside"):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
|
|
def test_can_check_job_boundaries_in_function_call__ok_for_frame_inside_job(self):
|
|
self._set_job_assignee(self.job["id"], self.user.id)
|
|
|
|
data = self._get_valid_job_request_data()
|
|
with self.subTest(job="defined", frame="inside"):
|
|
response = self._post_request(
|
|
self.function_url, self.user, data=data, query_params={"org_id": self.org["id"]}
|
|
)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|