diff --git a/docs/source/conf.py b/docs/source/conf.py index 49ebdb687..95c641434 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -54,3 +54,10 @@ html_static_path = [] autodoc_typehints = "description" +html_show_sourcelink = False + +html_context = { +"display_github": False, # Add 'Edit on Github' link instead of 'View page source' +"last_updated": True, +"commit": False, +} \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index eb70f0774..9415ce9fc 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,2 @@ -superannotate_schemas>=v1.0.45dev1 +superannotate_schemas>=v1.0.45dev5 diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index af98a7dd2..b8acf7eaa 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -2049,7 +2049,7 @@ def upload_image_to_project( def search_models( self, name: Optional[NotEmptyStr] = None, - type_: Optional[NotEmptyStr] = None, + type_: Optional[NotEmptyStr] = None, # noqa project_id: Optional[int] = None, task: Optional[NotEmptyStr] = None, include_global: Optional[StrictBool] = True, @@ -2058,17 +2058,21 @@ def search_models( :param name: search string :type name: str - :param type_: ml model type string - :type type_: str + + :param type\_: ml model type string + :type type\_: str + :param project_id: project id :type project_id: int + :param task: training task :type task: str + :param include_global: include global ml models :type include_global: bool - :return: ml model metadata - :rtype: list of dicts + :return: ml model metadata + :rtype: list of dicts """ res = self.controller.search_models( name=name, @@ -2227,7 +2231,7 @@ def validate_annotations( with open(annotations_json) as file: annotation_data = json.loads(file.read()) response = Controller.validate_annotations( - project_type, annotation_data, allow_extra=False + project_type, annotation_data ) if response.errors: raise AppException(response.errors) diff --git a/src/superannotate/lib/core/usecases/items.py b/src/superannotate/lib/core/usecases/items.py index 96a10f019..20a5fd8ef 100644 --- a/src/superannotate/lib/core/usecases/items.py +++ b/src/superannotate/lib/core/usecases/items.py @@ -155,7 +155,7 @@ def validate_arguments(self): raise AppException( "The query and subset params cannot have the value None at the same time." ) - if all([self._query, self._subset]) and not self._folder.is_root: + if self._subset and not self._folder.is_root: raise AppException( "The folder name should be specified in the query string." ) diff --git a/src/superannotate/lib/core/video_convertor.py b/src/superannotate/lib/core/video_convertor.py index 420f0fc18..cf066c67c 100644 --- a/src/superannotate/lib/core/video_convertor.py +++ b/src/superannotate/lib/core/video_convertor.py @@ -18,7 +18,7 @@ class Annotation(BaseModel): classId: Optional[int] x: Optional[Any] y: Optional[Any] - points: Optional[Dict] + points: Any attributes: Optional[List[Any]] = [] keyframe: bool = False @@ -30,10 +30,11 @@ class FrameAnnotation(BaseModel): class VideoFrameGenerator: def __init__(self, annotation_data: dict, fps: int): - self.validate_annotations(annotation_data) self.id_generator = iter(itertools.count(0)) self._annotation_data = annotation_data - self.duration = annotation_data["metadata"]["duration"] / (1000 * 1000) + duration = annotation_data["metadata"]["duration"] + duration = 0 if not duration else duration + self.duration = duration / (1000 * 1000) self.fps = fps self.ratio = 1000 * 1000 / fps self._frame_id = 1 @@ -42,12 +43,6 @@ def __init__(self, annotation_data: dict, fps: int): self._mapping = {} self._process() - @staticmethod - def validate_annotations(annotation_data: dict): - duration = annotation_data["metadata"].get("duration") - if duration is None: - raise AppException("Video not annotated yet") - def get_frame(self, frame_no: int): try: return self.annotations[frame_no] @@ -81,10 +76,9 @@ def _interpolate( "x": round(data["x"] + steps["x"] * idx, 2), "y": round(data["y"] + steps["y"] * idx, 2), } - elif annotation_type in (AnnotationTypes.POLYGON, AnnotationTypes.POLYLINE): - tmp_data["points"] = [ - point + steps[idx] * 2 for idx, point in enumerate(data["points"]) - ] + else: + tmp_data["points"] = data["points"] + annotations[frame_idx] = Annotation( instanceId=instance_id, type=annotation_type, diff --git a/src/superannotate/lib/infrastructure/controller.py b/src/superannotate/lib/infrastructure/controller.py index de18dbd0d..695de6002 100644 --- a/src/superannotate/lib/infrastructure/controller.py +++ b/src/superannotate/lib/infrastructure/controller.py @@ -1355,7 +1355,7 @@ def delete_annotations( @staticmethod def validate_annotations( - project_type: str, annotation: dict, allow_extra: bool = False + project_type: str, annotation: dict, allow_extra: bool = True ): use_case = usecases.ValidateAnnotationUseCase( project_type, diff --git a/src/superannotate/version.py b/src/superannotate/version.py index 59f031f54..bf2fe805c 100644 --- a/src/superannotate/version.py +++ b/src/superannotate/version.py @@ -1 +1 @@ -__version__ = "4.3.5dev17" +__version__ = "4.3.5dev23" diff --git a/tests/integration/annotations/test_get_annotations_per_frame.py b/tests/integration/annotations/test_get_annotations_per_frame.py index 16bfaa96c..18be86750 100644 --- a/tests/integration/annotations/test_get_annotations_per_frame.py +++ b/tests/integration/annotations/test_get_annotations_per_frame.py @@ -38,7 +38,7 @@ def annotations_path(self): return os.path.join(self.folder_path, self.ANNOTATIONS_PATH) def test_video_annotation_upload(self): - # sa.create_annotation_classes_from_classes_json(self.PROJECT_NAME, self.classes_path) + sa.create_annotation_classes_from_classes_json(self.PROJECT_NAME, self.classes_path) _, _, _ = sa.attach_items( self.PROJECT_NAME, diff --git a/tests/unit/test_validators.py b/tests/unit/test_validators.py index a10421564..e4dea2171 100644 --- a/tests/unit/test_validators.py +++ b/tests/unit/test_validators.py @@ -3,15 +3,13 @@ import tempfile from os.path import dirname from unittest import TestCase -`from unittest.mock import patch -` -from pydantic import ValidationError +from unittest.mock import patch -from src.superannotate import SAClient -sa = SAClient() from superannotate_schemas.validators import AnnotationValidators +from src.superannotate import SAClient +sa = SAClient() VECTOR_ANNOTATION_JSON_WITH_BBOX = """ { "metadata": { @@ -1668,7 +1666,6 @@ def test_validate_video_point_labels(self, mock_print): "instances[0].meta.pointLabels value is not a valid dict", ) - def test_validate_video_point_labels_bad_keys(self): with tempfile.TemporaryDirectory() as tmpdir_name: with open(f"{tmpdir_name}/test_validate_video_point_labels_bad_keys.json", diff --git a/tests/unit/test_video_valitation.py b/tests/unit/test_video_valitation.py new file mode 100644 index 000000000..0b4ae30df --- /dev/null +++ b/tests/unit/test_video_valitation.py @@ -0,0 +1,770 @@ +from unittest import TestCase + +from src.superannotate import SAClient + +sa = SAClient() +PAYLOAD = { + "metadata": { + "name": "video_file_example_1", + "duration": 30526667, + "width": 1920, + "height": 1080, + "lastAction": { + "timestamp": 1656499500695, + "email": "arturn@superannotate.com" + }, + "projectId": 202655, + "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Video+project/video_file_example_1.mp4", + "status": "InProgress", + "error": None, + "annotatorEmail": None, + "qaEmail": None + }, + "instances": [ + { + "meta": { + "type": "bbox", + "classId": 1379945, + "className": "tree", + "start": 0, + "end": 2515879, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-03-02T12:23:10.887Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-05-17T13:30:11.963Z", + "pointLabels": {} + }, + "parameters": [ + { + "start": 0, + "end": 2515879, + "timestamps": [ + { + "points": { + "x1": 496.13, + "y1": 132.02, + "x2": 898.05, + "y2": 515.25 + }, + "timestamp": 0, + "attributes": [ + { + "id": 2699916, + "groupId": 1096002, + "name": "standing", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 744.37, + "y1": 66.41, + "x2": 1146.29, + "y2": 449.64 + }, + "timestamp": 640917, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 857.56, + "y1": 227.21, + "x2": 1259.48, + "y2": 610.44 + }, + "timestamp": 1215864, + "attributes": [ + { + "id": 2699916, + "groupId": 1096002, + "name": "standing", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 857.56, + "y1": 227.21, + "x2": 1259.48, + "y2": 610.44 + }, + "timestamp": 1573648, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 1038.3, + "y1": 270.54, + "x2": 1440.22, + "y2": 653.77 + }, + "timestamp": 2255379, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 1038.3, + "y1": 270.54, + "x2": 1440.22, + "y2": 653.77 + }, + "timestamp": 2515879, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "bbox", + "classId": 1379945, + "className": "tree", + "start": 2790828, + "end": 5068924, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-03-02T12:36:52.126Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-03-02T12:38:24.056Z", + "pointLabels": {} + }, + "parameters": [ + { + "start": 2790828, + "end": 5068924, + "timestamps": [ + { + "points": { + "x1": 507.3, + "y1": 569.55, + "x2": 979.58, + "y2": 965.84 + }, + "timestamp": 2790828, + "attributes": [] + }, + { + "points": { + "x1": 507.3, + "y1": 569.55, + "x2": 979.58, + "y2": 965.84 + }, + "timestamp": 2888183, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": { + "x1": 507.3, + "y1": 569.55, + "x2": 979.58, + "y2": 965.84 + }, + "timestamp": 5068924, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "bbox", + "classId": 1379946, + "className": "car", + "start": 4502645, + "end": 6723950, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-03-02T12:39:19.970Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-03-02T12:41:13.536Z", + "pointLabels": {} + }, + "parameters": [ + { + "start": 4502645, + "end": 6723950, + "timestamps": [ + { + "points": { + "x1": 792.08, + "y1": 671.23, + "x2": 1178.97, + "y2": 987.47 + }, + "timestamp": 4502645, + "attributes": [] + }, + { + "points": { + "x1": 792.08, + "y1": 671.23, + "x2": 1178.97, + "y2": 987.47 + }, + "timestamp": 5330158, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + } + ] + }, + { + "points": { + "x1": 792.08, + "y1": 671.23, + "x2": 1178.97, + "y2": 987.47 + }, + "timestamp": 5825043, + "attributes": [ + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + }, + { + "id": 2699919, + "groupId": 1096003, + "name": "stops", + "groupName": "movement" + } + ] + }, + { + "points": { + "x1": 792.08, + "y1": 671.23, + "x2": 1178.97, + "y2": 987.47 + }, + "timestamp": 6303703, + "attributes": [ + { + "id": 2699919, + "groupId": 1096003, + "name": "stops", + "groupName": "movement" + }, + { + "id": 2699921, + "groupId": 1096004, + "name": "lights_off", + "groupName": "lights" + } + ] + }, + { + "points": { + "x1": 792.08, + "y1": 671.23, + "x2": 1178.97, + "y2": 987.47 + }, + "timestamp": 6723950, + "attributes": [ + { + "id": 2699919, + "groupId": 1096003, + "name": "stops", + "groupName": "movement" + }, + { + "id": 2699921, + "groupId": 1096004, + "name": "lights_off", + "groupName": "lights" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "event", + "classId": 1379946, + "className": "car", + "start": 1655026, + "end": 3365220, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-03-02T12:41:39.135Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-03-02T12:41:39.135Z" + }, + "parameters": [ + { + "start": 1655026, + "end": 3365220, + "timestamps": [ + { + "timestamp": 1655026, + "attributes": [] + }, + { + "timestamp": 3365220, + "attributes": [] + } + ] + } + ] + }, + { + "meta": { + "type": "point", + "classId": 1379946, + "className": "car", + "start": 617519, + "end": 2342388, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-05-17T11:38:50.041Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-05-17T11:50:57.784Z" + }, + "parameters": [ + { + "start": 617519, + "end": 2342388, + "timestamps": [ + { + "x": 612.81, + "y": 606.79, + "timestamp": 617519, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + } + ] + }, + { + "x": 653.05, + "y": 648.81, + "timestamp": 1266439, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + } + ] + }, + { + "x": 691.9399999999999, + "y": 682.7099999999999, + "timestamp": 1569965, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699921, + "groupId": 1096004, + "name": "lights_off", + "groupName": "lights" + } + ] + }, + { + "x": 691.9399999999999, + "y": 682.7099999999999, + "timestamp": 2342388, + "attributes": [ + { + "id": 2699921, + "groupId": 1096004, + "name": "lights_off", + "groupName": "lights" + }, + { + "id": 2699919, + "groupId": 1096003, + "name": "stops", + "groupName": "movement" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "point", + "classId": 1379946, + "className": "car", + "start": 2878270, + "end": 5084595, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-05-17T11:40:28.691Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-05-17T12:02:43.429Z" + }, + "parameters": [ + { + "start": 2878270, + "end": 5084595, + "timestamps": [ + { + "x": 915.5, + "y": 461.21, + "timestamp": 2878270, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + } + ] + }, + { + "x": 915.5, + "y": 461.21, + "timestamp": 5084595, + "attributes": [ + { + "id": 2699918, + "groupId": 1096003, + "name": "goes", + "groupName": "movement" + }, + { + "id": 2699920, + "groupId": 1096004, + "name": "lights_on", + "groupName": "lights" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "polygon", + "classId": 1379945, + "className": "tree", + "start": 5664421, + "end": 8368555, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-06-29T10:43:15.995Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-06-29T10:43:57.540Z" + }, + "parameters": [ + { + "start": 5664421, + "end": 8368555, + "timestamps": [ + { + "points": [ + 651.25, + 365.32, + 855.92, + 240.26, + 1017.63, + 503.6 + ], + "timestamp": 5664421, + "attributes": [] + }, + { + "points": [ + 651.25, + 365.32, + 855.92, + 240.26, + 1017.63, + 503.6 + ], + "timestamp": 6496259, + "attributes": [ + { + "id": 2699916, + "groupId": 1096002, + "name": "standing", + "groupName": "state" + } + ] + }, + { + "points": [ + 839.98, + 358.45, + 1044.65, + 233.39, + 1206.36, + 496.73 + ], + "timestamp": 6826353, + "attributes": [ + { + "id": 2699916, + "groupId": 1096002, + "name": "standing", + "groupName": "state" + } + ] + }, + { + "points": [ + 839.98, + 358.45, + 1044.65, + 233.39, + 1206.36, + 496.73 + ], + "timestamp": 8368555, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "polyline", + "classId": 1379945, + "className": "tree", + "start": 8754105, + "end": 11312997, + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-06-29T10:44:15.979Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-06-29T10:45:00.660Z" + }, + "parameters": [ + { + "start": 8754105, + "end": 11312997, + "timestamps": [ + { + "points": [ + 679.05, + 412.73, + 1050.61, + 484.06, + 885.75, + 737.4 + ], + "timestamp": 8754105, + "attributes": [ + { + "id": 2699916, + "groupId": 1096002, + "name": "standing", + "groupName": "state" + } + ] + }, + { + "points": [ + 679.05, + 412.73, + 1050.61, + 484.06, + 885.75, + 737.4 + ], + "timestamp": 9467109, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": [ + 790.96, + 292.26, + 1162.52, + 363.59, + 997.66, + 616.93 + ], + "timestamp": 9757592, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + }, + { + "points": [ + 790.96, + 292.26, + 1162.52, + 363.59, + 997.66, + 616.93 + ], + "timestamp": 11312997, + "attributes": [ + { + "id": 2699917, + "groupId": 1096002, + "name": "falling", + "groupName": "state" + } + ] + } + ] + } + ] + } + ], + "tags": [ + "tg2", + "tg1" + ] +} + + +class TestVideoValidators(TestCase): + + def test_polygon_polyline(self): + response = sa.controller.validate_annotations("video", PAYLOAD) + assert not response.report_messages