Skip to content

Commit 8ee3a64

Browse files
authored
Merge pull request #413 from superannotateai/friday
Friday
2 parents e06b3f4 + 167365c commit 8ee3a64

File tree

8 files changed

+133
-76
lines changed

8 files changed

+133
-76
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,11 @@ def get_image_annotations(project: Union[NotEmptyStr, dict], image_name: NotEmpt
11401140
"annotation_mask_filename": mask filename on server
11411141
:rtype: dict
11421142
"""
1143+
warning_msg = (
1144+
"The get_image_annotations function is deprecated and will be removed with the coming releases, "
1145+
"please use get_annotations instead."
1146+
)
1147+
warnings.warn(warning_msg, DeprecationWarning)
11431148
project_name, folder_name = extract_project_folder(project)
11441149
res = Controller.get_default().get_image_annotations(
11451150
project_name=project_name, folder_name=folder_name, image_name=image_name
@@ -2859,7 +2864,7 @@ def invite_contributors_to_team(
28592864
def get_annotations(project: NotEmptyStr, items: Optional[List[NotEmptyStr]] = None):
28602865
"""Returns annotations for the given list of items.
28612866
2862-
:param project: project name
2867+
:param project: project name or folder path (e.g., “project1/folder1”).
28632868
:type project: str
28642869
28652870
:param items: item names. If None all items in the project will be exported

src/superannotate/lib/core/reporter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ def __init__(
2727
def disable_warnings(self):
2828
self._log_warning = False
2929

30+
def disable_info(self):
31+
self._log_info = False
32+
3033
def enable_warnings(self):
3134
self._log_warning = True
3235

36+
def enable_info(self):
37+
self._log_info = True
38+
3339
def log_info(self, value: str):
3440
if self._log_info:
3541
self.logger.info(value)

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

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ def __init__(
509509
self._client = backend_service_provider
510510
self._show_process = show_process
511511

512+
def validate_project_type(self):
513+
if self._project.project_type == constances.ProjectType.PIXEL.value:
514+
raise AppException("The function is not supported for Pixel projects.")
515+
512516
def validate_item_names(self):
513517
if self._item_names:
514518
item_names = list(dict.fromkeys(self._item_names))
@@ -532,7 +536,7 @@ def execute(self):
532536
items_count = len(self._item_names)
533537
self.reporter.log_info(
534538
f"Getting {items_count} annotations from "
535-
f"{self._project.name}{f'/{self._folder.name}' if self._folder else ''}."
539+
f"{self._project.name}{f'/{self._folder.name}' if self._folder.name != 'root' else ''}."
536540
)
537541
self.reporter.start_progress(items_count, disable=not self._show_process)
538542
annotations = self._client.get_annotations(
@@ -570,7 +574,12 @@ def __init__(
570574
self._fps = fps
571575
self._client = backend_service_provider
572576

577+
def validate_project_type(self):
578+
if self._project.project_type != constances.ProjectType.VIDEO.value:
579+
raise AppException("The function only supports video projects.")
580+
573581
def execute(self):
582+
self.reporter.disable_info()
574583
response = GetAnnotations(
575584
reporter=self.reporter,
576585
project=self._project,
@@ -580,16 +589,21 @@ def execute(self):
580589
backend_service_provider=self._client,
581590
show_process=False
582591
).execute()
583-
generator = VideoFrameGenerator(response.data[0], fps=self._fps)
584-
self.reporter.log_info(f"Getting annotations for {generator.frames_count} frames from {self._video_name}.")
585-
if response.errors:
586-
self._response.errors = response.errors
587-
return self._response
588-
if not response.data:
589-
self._response.errors = AppException(f"Video {self._video_name} not found.")
590-
annotations = response.data
591-
if annotations:
592-
self._response.data = list(generator)
592+
self.reporter.enable_info()
593+
if response.data:
594+
generator = VideoFrameGenerator(response.data[0], fps=self._fps)
595+
596+
self.reporter.log_info(f"Getting annotations for {generator.frames_count} frames from {self._video_name}.")
597+
if response.errors:
598+
self._response.errors = response.errors
599+
return self._response
600+
if not response.data:
601+
self._response.errors = AppException(f"Video {self._video_name} not found.")
602+
annotations = response.data
603+
if annotations:
604+
self._response.data = list(generator)
605+
else:
606+
self._response.data = []
593607
else:
594-
self._response.data = []
608+
self._response.errors = "Couldn't get annotations."
595609
return self._response

src/superannotate/lib/core/video_convertor.py

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def interpolate_annotations(
6464
):
6565
for idx, frame_idx in enumerate(range(from_frame, to_frame), 1):
6666
keyframe = False
67-
if idx == from_frame or idx - 1 == to_frame:
67+
if idx in (1, len(range(from_frame, to_frame))):
6868
keyframe = True
6969
points = None
7070
if annotation_type == "bbox":
@@ -85,60 +85,67 @@ def interpolate_annotations(
8585

8686
def _process(self):
8787
for instance in self._annotation_data["instances"]:
88-
for parameter in instance["parameters"]:
89-
time_stamp_frame_map = []
90-
for timestamp in parameter["timestamps"]:
91-
time_stamp_frame_map.append((round(timestamp["timestamp"] / self.ratio), timestamp))
92-
for idx, (frame_no, timestamp_data) in enumerate(time_stamp_frame_map):
93-
annotation_type = instance["meta"]["type"]
94-
try:
95-
next_frame_no, next_timestamp = time_stamp_frame_map[idx + 1]
96-
if frame_no == next_frame_no:
97-
continue
98-
frames_diff = next_frame_no - frame_no
99-
steps = None
100-
if annotation_type == "bbox":
101-
if not frames_diff:
102-
steps = {
103-
"y1": 0,
104-
"x2": 0,
105-
"x1": 0,
106-
"y2": 0
107-
}
108-
else:
109-
steps = {
110-
"y1": round(
111-
(next_timestamp["points"]["y1"] - timestamp_data["points"]["y1"]) / frames_diff,
112-
2),
113-
"x2": round(
114-
(next_timestamp["points"]["x2"] - timestamp_data["points"]["x2"]) / frames_diff,
115-
2),
116-
"x1": round(
117-
(next_timestamp["points"]["x1"] - timestamp_data["points"]["x1"]) / frames_diff,
118-
2),
119-
"y2": round(
120-
(next_timestamp["points"]["y2"] - timestamp_data["points"]["y2"]) / frames_diff,
121-
2),
122-
}
123-
self.interpolate_annotations(
124-
class_name=instance["meta"]["className"],
125-
from_frame=frame_no,
126-
to_frame=next_frame_no,
127-
data=timestamp_data,
128-
steps=steps,
129-
annotation_type=annotation_type
130-
)
131-
except IndexError:
132-
last_frame_no, last_timestamp = time_stamp_frame_map[-1]
133-
end = round(parameter["end"] / self.ratio)
134-
self.interpolate_annotations(
135-
annotation_type=annotation_type,
136-
class_name=instance["meta"]["className"],
137-
from_frame=last_frame_no,
138-
to_frame=end,
139-
data=last_timestamp,
140-
steps={"x1": 0, "y1": 0, "x2": 0, "y2": 0}
141-
)
88+
try:
89+
for parameter in instance["parameters"]:
90+
time_stamp_frame_map = []
91+
for timestamp in parameter["timestamps"]:
92+
time_stamp_frame_map.append((round(timestamp["timestamp"] / self.ratio) + 1, timestamp))
93+
for idx, (frame_no, timestamp_data) in enumerate(time_stamp_frame_map):
94+
annotation_type = instance["meta"]["type"]
95+
try:
96+
next_frame_no, next_timestamp = time_stamp_frame_map[idx + 1]
97+
if frame_no == next_frame_no:
98+
median = (timestamp_data["timestamp"] // self.ratio) + (self.ratio / 2)
99+
if abs(median - timestamp_data["timestamp"]) < abs(median - next_timestamp["timestamp"]):
100+
time_stamp_frame_map[idx + 1] = timestamp_data
101+
continue
102+
103+
frames_diff = next_frame_no - frame_no
104+
steps = None
105+
if annotation_type == "bbox":
106+
if not frames_diff:
107+
steps = {
108+
"y1": 0,
109+
"x2": 0,
110+
"x1": 0,
111+
"y2": 0
112+
}
113+
else:
114+
steps = {
115+
"y1": round(
116+
(next_timestamp["points"]["y1"] - timestamp_data["points"]["y1"]) / frames_diff,
117+
2),
118+
"x2": round(
119+
(next_timestamp["points"]["x2"] - timestamp_data["points"]["x2"]) / frames_diff,
120+
2),
121+
"x1": round(
122+
(next_timestamp["points"]["x1"] - timestamp_data["points"]["x1"]) / frames_diff,
123+
2),
124+
"y2": round(
125+
(next_timestamp["points"]["y2"] - timestamp_data["points"]["y2"]) / frames_diff,
126+
2),
127+
}
128+
self.interpolate_annotations(
129+
class_name=instance["meta"]["className"],
130+
from_frame=frame_no,
131+
to_frame=next_frame_no,
132+
data=timestamp_data,
133+
steps=steps,
134+
annotation_type=annotation_type
135+
)
136+
except IndexError:
137+
last_frame_no, last_timestamp = time_stamp_frame_map[-1]
138+
end = round(parameter["end"] / self.ratio)
139+
self.interpolate_annotations(
140+
annotation_type=annotation_type,
141+
class_name=instance["meta"]["className"],
142+
from_frame=last_frame_no,
143+
to_frame=end,
144+
data=last_timestamp,
145+
steps={"x1": 0, "y1": 0, "x2": 0, "y2": 0}
146+
)
147+
except Exception as e:
148+
pass
142149

143150
def __iter__(self):
144151
for frame_no in range(1, int(self.frames_count) + 1):

tests/integration/annotations/test_get_annotations.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
class TestGetAnnotations(BaseTestCase):
1515
PROJECT_NAME = "Test-get_annotations"
16+
FOLDER_NAME = "Test-get_annotations"
1617
PROJECT_DESCRIPTION = "Desc"
1718
PROJECT_TYPE = "Vector"
1819
TEST_FOLDER_PATH = "data_set/sample_project_vector"
@@ -42,6 +43,28 @@ def test_get_annotations(self):
4243
self.assertEqual(len(annotation_data["instances"]), len(annotations[0]["instances"]))
4344
parse_obj_as(List[VectorAnnotation], annotations)
4445

46+
@pytest.mark.flaky(reruns=3)
47+
def test_get_annotations_from_folder(self):
48+
sa.init()
49+
sa.create_folder(self.PROJECT_NAME, self.FOLDER_NAME)
50+
51+
sa.upload_images_from_folder_to_project(
52+
f"{self.PROJECT_NAME}/{self.FOLDER_NAME}", self.folder_path, annotation_status="InProgress"
53+
)
54+
sa.create_annotation_classes_from_classes_json(
55+
self.PROJECT_NAME, f"{self.folder_path}/classes/classes.json"
56+
)
57+
_, _, _ = sa.upload_annotations_from_folder_to_project(
58+
f"{self.PROJECT_NAME}/{self.FOLDER_NAME}", self.folder_path
59+
)
60+
61+
annotations = sa.get_annotations(f"{self.PROJECT_NAME}/{self.FOLDER_NAME}", [self.IMAGE_NAME])
62+
self.assertEqual(len(annotations), 1)
63+
with open(f"{self.folder_path}/{self.IMAGE_NAME}___objects.json", "r") as annotation_file:
64+
annotation_data = json.load(annotation_file)
65+
self.assertEqual(len(annotation_data["instances"]), len(annotations[0]["instances"]))
66+
parse_obj_as(List[VectorAnnotation], annotations)
67+
4568
@pytest.mark.flaky(reruns=3)
4669
def test_get_annotations_all(self):
4770
sa.init()

tests/integration/annotations/test_get_annotations_per_frame.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def test_video_annotation_upload(self):
4747
self.assertEqual(
4848
len(annotations),
4949
int(
50-
json.load(open(f"{self.annotations_path}/{self.VIDEO_NAME}.json"))["metadata"]["duration"] / (
51-
1000 * 1000)
50+
json.load(
51+
open(f"{self.annotations_path}/{self.VIDEO_NAME}.json"))["metadata"]["duration"] / (
52+
1000 * 1000)
5253
)
5354
)

tests/integration/test_cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def test_vector_annotation_folder_upload_download_cli(self):
190190
f'--project "{self.PROJECT_NAME}" '
191191
f'--folder "{self.convertor_data_path}" '
192192
f'--format COCO '
193-
f'--data-set-name "instances_test"',
193+
f'--dataset-name "instances_test"',
194194
check=True,
195195
shell=True,
196196
)
@@ -201,8 +201,8 @@ def test_vector_annotation_folder_upload_download_cli(self):
201201
count_out = len(list(Path(temp_dir).glob("*.json")))
202202
self.assertEqual(count_in, count_out)
203203

204-
# @pytest.mark.skipif(CLI_VERSION and CLI_VERSION != sa.__version__,
205-
# reason=f"Updated package version from {CLI_VERSION} to {sa.__version__}")
204+
@pytest.mark.skipif(CLI_VERSION and CLI_VERSION != sa.__version__,
205+
reason=f"Updated package version from {CLI_VERSION} to {sa.__version__}")
206206
def test_attach_image_urls(self):
207207
self._create_project()
208208
subprocess.run(
@@ -242,8 +242,8 @@ def test_upload_videos(self):
242242
)
243243
self.assertEqual(5, len(sa.search_images(self.PROJECT_NAME)))
244244

245-
# @pytest.mark.skipif(CLI_VERSION and CLI_VERSION != sa.__version__,
246-
# reason=f"Updated package version from {CLI_VERSION} to {sa.__version__}")
245+
@pytest.mark.skipif(CLI_VERSION and CLI_VERSION != sa.__version__,
246+
reason=f"Updated package version from {CLI_VERSION} to {sa.__version__}")
247247
def test_attach_document_urls(self):
248248
self._create_project("Document")
249249
subprocess.run(

tests/unit/test_controller_init.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ class SKDInitTest(TestCase):
7373
FILE_NAME = "config.json"
7474
FILE_NAME_2 = "config.json"
7575

76-
@patch.dict(os.environ, {"SA_TOKEN": TEST_TOKEN})
7776
def test_env_flow(self):
7877
import superannotate as sa
78+
os.environ.update({"SA_TOKEN": self.TEST_TOKEN})
79+
sa.init()
7980
self.assertEqual(sa.get_default_controller()._token, self.TEST_TOKEN)
8081

8182
def test_init_via_config_file(self):

0 commit comments

Comments
 (0)