Skip to content

Commit 22e7e7a

Browse files
authored
Merge pull request #412 from superannotateai/friday-964-priority
initial priority
2 parents c3c55fd + 7dd2873 commit 22e7e7a

File tree

10 files changed

+189
-0
lines changed

10 files changed

+189
-0
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ ______
9292
.. autofunction:: superannotate.add_annotation_bbox_to_image
9393
.. autofunction:: superannotate.add_annotation_point_to_image
9494
.. autofunction:: superannotate.add_annotation_comment_to_image
95+
.. autofunction:: superannotate.upload_priority_scores
9596

9697
----------
9798

src/superannotate/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
from superannotate.lib.app.interface.sdk_interface import (
104104
upload_preannotations_from_folder_to_project,
105105
)
106+
from superannotate.lib.app.interface.sdk_interface import (
107+
upload_priority_scores,
108+
)
106109
from superannotate.lib.app.interface.sdk_interface import upload_video_to_project
107110
from superannotate.lib.app.interface.sdk_interface import (
108111
upload_videos_from_folder_to_project,
@@ -151,6 +154,7 @@
151154
"clone_project",
152155
"share_project",
153156
"delete_project",
157+
"upload_priority_scores",
154158
# Images Section
155159
"search_images",
156160
"copy_image",

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from lib.core.exceptions import AppException
4040
from lib.core.types import AttributeGroup
4141
from lib.core.types import MLModel
42+
from lib.core.types import PriorityScore
4243
from lib.core.types import Project
4344
from lib.infrastructure.controller import Controller
4445
from pydantic import conlist
@@ -2900,3 +2901,25 @@ def get_annotations_per_frame(project: NotEmptyStr, video: NotEmptyStr, fps: int
29002901
if response.errors:
29012902
raise AppException(response.errors)
29022903
return response.data
2904+
2905+
2906+
@Trackable
2907+
@validate_arguments
2908+
def upload_priority_scores(project: NotEmptyStr, scores: List[PriorityScore]):
2909+
"""Returns per frame annotations for the given video.
2910+
2911+
:param project: project name or folder path (e.g., “project1/folder1”)
2912+
:type project: str
2913+
2914+
:param scores: list of score objects
2915+
:type scores: list of dicts
2916+
2917+
:return: lists of uploaded, skipped items
2918+
:rtype: tuple (2 members) of lists of strs
2919+
"""
2920+
project_name, folder_name = extract_project_folder(project)
2921+
project_folder_name = project
2922+
response = Controller.get_default().upload_priority_scores(project_name, folder_name, scores, project_folder_name)
2923+
if response.errors:
2924+
raise AppException(response.errors)
2925+
return response.data

src/superannotate/lib/app/mixp/utils/parsers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,3 +1230,11 @@ def get_annotations_per_frame(*args, **kwargs):
12301230
"event_name": "get_annotations_per_frame",
12311231
"properties": {"Project": project, "fps": fps},
12321232
}
1233+
1234+
1235+
def upload_priority_scores(*args, **kwargs):
1236+
scores = kwargs.get("scores", args[1])
1237+
return {
1238+
"event_name": "upload_priority_scores",
1239+
"properties": {"Score Count": len(scores)},
1240+
}

src/superannotate/lib/core/serviceproviders.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,8 @@ def get_annotations(
314314
reporter: Reporter
315315
) -> List[dict]:
316316
raise NotImplementedError
317+
318+
def upload_priority_scores(
319+
self, team_id: int, project_id: int, folder_id: int, priorities: list
320+
) -> dict:
321+
raise NotImplementedError

src/superannotate/lib/core/types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ class MLModel(BaseModel):
4040

4141
class Config:
4242
extra = Extra.allow
43+
44+
45+
class PriorityScore(BaseModel):
46+
name: NotEmptyStr
47+
priority: float

src/superannotate/lib/core/usecases/annotations.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
from lib.core.repositories import BaseManageableRepository
2727
from lib.core.service_types import UploadAnnotationAuthData
2828
from lib.core.serviceproviders import SuerannotateServiceProvider
29+
from lib.core.types import PriorityScore
2930
from lib.core.usecases.base import BaseReportableUseCae
3031
from lib.core.usecases.images import GetBulkImages
3132
from lib.core.usecases.images import ValidateAnnotationUseCase
3233
from lib.core.video_convertor import VideoFrameGenerator
3334
from superannotate.logger import get_default_logger
3435
from superannotate_schemas.validators import AnnotationValidators
3536

37+
3638
logger = get_default_logger()
3739

3840

@@ -622,3 +624,72 @@ def execute(self):
622624
else:
623625
self._response.errors = "Couldn't get annotations."
624626
return self._response
627+
628+
629+
class UploadPriorityScoresUseCase(BaseReportableUseCae):
630+
631+
CHUNK_SIZE = 100
632+
633+
def __init__(
634+
self,
635+
reporter,
636+
project: ProjectEntity,
637+
folder: FolderEntity,
638+
scores: List[PriorityScore],
639+
project_folder_name: str,
640+
backend_service_provider: SuerannotateServiceProvider
641+
):
642+
super().__init__(reporter)
643+
self._project = project
644+
self._folder = folder
645+
self._scores = scores
646+
self._client = backend_service_provider
647+
self._project_folder_name = project_folder_name
648+
649+
@staticmethod
650+
def get_clean_priority(priority):
651+
if len(str(priority)) > 8:
652+
priority = float(str(priority)[:8])
653+
if priority > 1000000:
654+
priority = 1000000
655+
if priority < 0:
656+
priority = 0
657+
if str(float(priority)).split('.')[1:2]:
658+
if len(str(float(priority)).split('.')[1]) > 5:
659+
priority = float(str(float(priority)).split('.')[0] + '.' + str(float(priority)).split('.')[1][:5])
660+
return priority
661+
662+
@property
663+
def folder_path(self):
664+
return f"{self._project.name}{f'/{self._folder.name}'if self._folder.name != 'root' else ''}"
665+
666+
def execute(self):
667+
if self.is_valid():
668+
priorities = []
669+
initial_scores = []
670+
for i in self._scores:
671+
priorities.append({
672+
"name": i.name,
673+
"entropy_value": self.get_clean_priority(i.priority)
674+
})
675+
initial_scores.append(i.name)
676+
uploaded_score_names = []
677+
self.reporter.log_info(f"Uploading priority scores for {len(priorities)} item(s) from {self.folder_path}.")
678+
iterations = range(0, len(priorities), self.CHUNK_SIZE)
679+
self.reporter.start_progress(iterations, "Uploading priority scores")
680+
if iterations:
681+
for i in iterations:
682+
priorities_to_upload = priorities[i : i + self.CHUNK_SIZE] # noqa: E203
683+
res = self._client.upload_priority_scores(
684+
team_id=self._project.team_id,
685+
project_id=self._project.uuid,
686+
folder_id=self._folder.uuid,
687+
priorities=priorities_to_upload
688+
)
689+
self.reporter.update_progress(len(priorities_to_upload))
690+
uploaded_score_names.extend(list(map(lambda x: x["name"], res.get("data", []))))
691+
skipped_score_names = list(set(initial_scores) - set(uploaded_score_names))
692+
self._response.data = (uploaded_score_names, skipped_score_names)
693+
else:
694+
self.reporter.warning_messages("Empty scores.")
695+
return self._response

src/superannotate/lib/infrastructure/controller.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939

4040
class BaseController(metaclass=ABCMeta):
41+
4142
def __init__(self, config_path: str = None, token: str = None):
4243
self._team_data = None
4344
self._token = None
@@ -1647,3 +1648,16 @@ def get_annotations_per_frame(self, project_name: str, folder_name: str, video_n
16471648
backend_service_provider=self.backend_client
16481649
)
16491650
return use_case.execute()
1651+
1652+
def upload_priority_scores(self, project_name, folder_name, scores, project_folder_name):
1653+
project = self._get_project(project_name)
1654+
folder = self._get_folder(project, folder_name)
1655+
use_case = usecases.UploadPriorityScoresUseCase(
1656+
reporter=self.default_reporter,
1657+
project=project,
1658+
folder=folder,
1659+
scores=scores,
1660+
backend_service_provider=self.backend_client,
1661+
project_folder_name=project_folder_name
1662+
)
1663+
return use_case.execute()

src/superannotate/lib/infrastructure/services.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@ class SuperannotateBackendService(BaseBackendService):
224224
URL_DELETE_ANNOTATIONS_PROGRESS = "annotations/getRemoveStatus"
225225
URL_GET_LIMITS = "project/{}/limitationDetails"
226226
URL_GET_ANNOTATIONS = "images/annotations/stream"
227+
URL_UPLOAD_PRIORITY_SCORES = "images/updateEntropy"
228+
229+
def upload_priority_scores(
230+
self, team_id: int, project_id: int, folder_id: int, priorities: list
231+
) -> dict:
232+
upload_priority_score_url = urljoin(self.api_url, self.URL_UPLOAD_PRIORITY_SCORES)
233+
res = self._request(
234+
upload_priority_score_url,
235+
"post",
236+
params={"team_id": team_id, "project_id": project_id, "folder_id": folder_id},
237+
data={"image_entropies": priorities}
238+
)
239+
return res.json()
227240

228241
def get_project(self, uuid: int, team_id: int):
229242
get_project_url = urljoin(self.api_url, self.URL_GET_PROJECT.format(uuid))
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
from pathlib import Path
3+
4+
import src.superannotate as sa
5+
from tests.integration.base import BaseTestCase
6+
7+
8+
class TestUploadPriorityScores(BaseTestCase):
9+
PROJECT_NAME = "TestUploadPriorityScores"
10+
PROJECT_DESCRIPTION = "Desc"
11+
PROJECT_TYPE = "Vector"
12+
TEST_FOLDER_PATH = "data_set/sample_project_vector"
13+
14+
@property
15+
def folder_path(self):
16+
return os.path.join(Path(__file__).parent.parent, self.TEST_FOLDER_PATH)
17+
18+
def test_upload_priority_scores(self):
19+
sa.upload_images_from_folder_to_project(
20+
self.PROJECT_NAME, self.folder_path, annotation_status="InProgress"
21+
)
22+
uploaded, skipped = sa.upload_priority_2scores(self.PROJECT_NAME, scores=[{
23+
"name": "example_image_1.jpg",
24+
"priority": 1
25+
}])
26+
self.assertEqual(len(uploaded), 1)
27+
self.assertEqual(len(skipped), 0)
28+
uploaded, skipped = sa.upload_priority_scores(self.PROJECT_NAME, scores=[{
29+
"name": "non-exist.jpg",
30+
"priority": 1
31+
}, {
32+
"name": "non-exist-2.jpg",
33+
"priority": 1
34+
}])
35+
self.assertEqual(len(uploaded), 0)
36+
self.assertEqual(len(skipped), 2)
37+
uploaded, skipped = sa.upload_priority_scores(self.PROJECT_NAME, scores=[{
38+
"name": "example_image_3.jpg",
39+
"priority": 1.1234567890
40+
}, {
41+
"name": "example_image_4.jpg",
42+
"priority": 100000000
43+
}])
44+
self.assertEqual(sa.get_image_metadata(self.PROJECT_NAME, "example_image_4.jpg")['entropy_value'], 1000000)
45+
self.assertEqual(sa.get_image_metadata(self.PROJECT_NAME, "example_image_3.jpg")['entropy_value'], 1.12345)

0 commit comments

Comments
 (0)