Skip to content

Commit a21b128

Browse files
authored
Merge pull request #258 from superannotateai/friday
Friday
2 parents 0723840 + c32ac98 commit a21b128

File tree

7 files changed

+420
-50
lines changed

7 files changed

+420
-50
lines changed

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

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,8 @@ def upload_annotations_from_folder_to_project(
24452445
"""
24462446

24472447
project_name, folder_name = extract_project_folder(project)
2448+
project_folder_name = project_name + (f"/{folder_name}" if folder_name else "")
2449+
24482450

24492451
if recursive_subfolders:
24502452
logger.info(
@@ -2460,9 +2462,7 @@ def upload_annotations_from_folder_to_project(
24602462
if not annotation_paths:
24612463
raise AppException("Could not find annotations matching existing items on the platform.")
24622464

2463-
logger.info(
2464-
"Uploading %s annotations to project %s.", len(annotation_paths), project_name
2465-
)
2465+
logger.info(f"Uploading {len(annotation_paths)} annotations from {folder_path} to the project {project_folder_name}.")
24662466
response = controller.upload_annotations_from_folder(
24672467
project_name=project_name,
24682468
folder_name=folder_name,
@@ -2506,32 +2506,28 @@ def upload_preannotations_from_folder_to_project(
25062506
:rtype: tuple of list of strs
25072507
"""
25082508
project_name, folder_name = extract_project_folder(project)
2509+
project_folder_name = project_name + (f"/{folder_name}" if folder_name else "")
25092510
project = controller.get_project_metadata(project_name).data
25102511
if project["project"].project_type in [
25112512
constances.ProjectType.VIDEO.value,
25122513
constances.ProjectType.DOCUMENT.value,
25132514
]:
25142515
raise AppException(LIMITED_FUNCTIONS[project["project"].project_type])
2515-
25162516
if recursive_subfolders:
25172517
logger.info(
2518-
"When using recursive subfolder parsing same name annotations in different subfolders will overwrite each other.",
2518+
"When using recursive subfolder parsing same name annotations in different "
2519+
"subfolders will overwrite each other.",
25192520
)
2520-
2521-
logger.info(
2522-
"The JSON files should follow specific naming convention. For Vector projects they should be named '<image_name>___objects.json', for Pixel projects JSON file should be names '<image_name>___pixel.json' and also second mask image file should be present with the name '<image_name>___save.png'. In both cases image with <image_name> should be already present on the platform."
2523-
)
2521+
logger.info("The JSON files should follow a specific naming convention, matching file names already present "
2522+
"on the platform. Existing annotations will be overwritten")
25242523
logger.info("Existing annotations will be overwritten.",)
2525-
logger.info(
2526-
"Uploading all annotations from %s to project %s.", folder_path, project_name
2527-
)
2528-
25292524
annotation_paths = get_annotation_paths(
25302525
folder_path, from_s3_bucket, recursive_subfolders
25312526
)
2527+
if not annotation_paths:
2528+
raise AppException("Could not find annotations matching existing items on the platform.")
25322529
logger.info(
2533-
"Uploading %s annotations to project %s.", len(annotation_paths), project_name
2534-
)
2530+
f"Uploading {len(annotation_paths)} annotations from {folder_path} to the project {project_folder_name}.")
25352531
response = controller.upload_annotations_from_folder(
25362532
project_name=project_name,
25372533
folder_name=folder_name,

src/superannotate/lib/core/helpers.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ def fill_annotation_ids(
119119

120120

121121
def convert_to_video_editor_json(data: dict, class_name_mapper: dict, reporter: Reporter):
122+
id_generator = ClassIdGenerator()
123+
122124
def safe_time(timestamp):
123125
return "0" if str(timestamp) == "0.0" else timestamp
124126

@@ -138,14 +140,17 @@ def convert_timestamp(timestamp):
138140
}
139141
for instance in data["instances"]:
140142
meta = instance["meta"]
141-
class_name = meta["className"]
143+
class_name = meta.get("className")
142144
editor_instance = {
143145
"attributes": [],
144146
"timeline": {},
145147
"type": meta["type"],
146-
"classId": class_name_mapper.get(class_name, {}).get("id", -1),
147148
"locked": True,
148149
}
150+
if class_name:
151+
editor_instance["classId"] = class_name_mapper.get(class_name, {}).get("id", id_generator.send(class_name))
152+
else:
153+
editor_instance["classId"] = id_generator.send("unknown_class")
149154
if meta.get("pointLabels", None):
150155
editor_instance["pointLabels"] = meta["pointLabels"]
151156
active_attributes = set()
@@ -168,8 +173,9 @@ def convert_timestamp(timestamp):
168173
editor_instance["timeline"][timestamp]["points"] = timestamp_data[
169174
"points"
170175
]
171-
172-
if not class_name_mapper.get(meta["className"], None):
176+
if not class_name:
177+
continue
178+
elif not class_name_mapper.get(class_name):
173179
reporter.store_message("missing_classes", meta["className"])
174180
continue
175181

@@ -227,3 +233,15 @@ def default(self, obj):
227233
if isinstance(obj, set):
228234
return list(obj)
229235
return json.JSONEncoder.default(self, obj)
236+
237+
238+
class ClassIdGenerator:
239+
def __init__(self):
240+
self.classes = defaultdict(int)
241+
self.idx = -1
242+
243+
def send(self, class_name: str):
244+
if class_name not in self.classes:
245+
self.classes[class_name] = self.idx
246+
self.idx -= 1
247+
return self.classes[class_name]

src/superannotate/lib/core/plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def get_extractable_frames(
259259
total_with_fps = sum(
260260
1
261261
for _ in VideoPlugin.frames_generator(
262-
video_path,start_time, end_time, target_fps, log=False
262+
video_path, start_time, end_time, target_fps, log=False
263263
)
264264
)
265265
zero_fill_count = len(str(total))
@@ -288,7 +288,7 @@ def extract_frames(
288288
for frame in VideoPlugin.frames_generator(
289289
video_path, start_time, end_time, target_fps
290290
):
291-
if len(extracted_frames_paths) > limit:
291+
if len(extracted_frames_paths) >= limit:
292292
break
293293
path = str(
294294
Path(extract_path)

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

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ def _upload_annotation(
173173
return path, False
174174
return path, True
175175
except Exception as e:
176-
# raise e
177176
return path, False
178177

179178
def get_bucket_to_upload(self, ids: List[int]):
@@ -322,25 +321,29 @@ def s3_bucket(self):
322321
self._s3_bucket = resource.Bucket(upload_data.bucket)
323322
return self._s3_bucket
324323

325-
def get_s3_annotation(self, s3, path: str):
324+
def get_s3_file(self, s3, path: str):
326325
file = io.BytesIO()
327326
s3_object = s3.Object(self._client_s3_bucket, path)
328327
s3_object.download_fileobj(file)
329328
file.seek(0)
330-
return json.load(file)
329+
return file
331330

332331
@property
333332
def from_s3(self):
334333
if self._client_s3_bucket:
335334
from_session = boto3.Session()
336335
return from_session.resource("s3")
337336

338-
def get_annotation_json(self):
337+
def set_annotation_json(self):
339338
if not self._annotation_json:
340339
if self._client_s3_bucket:
341-
return self.get_s3_annotation(self.from_s3, self._annotation_path)
342-
return json.load(open(self._annotation_path))
343-
return self._annotation_json
340+
self._annotation_json = json.load(self.get_s3_file(self.from_s3, self._annotation_path))
341+
if self._project.project_type == constances.ProjectType.PIXEL.value:
342+
self._mask = self.get_s3_file(self.from_s3, self._annotation_path.replace(constances.PIXEL_ANNOTATION_POSTFIX, constances.ANNOTATION_MASK_POSTFIX))
343+
else:
344+
self._annotation_json = json.load(open(self._annotation_path))
345+
if self._project.project_type == constances.ProjectType.PIXEL.value:
346+
self._mask = open(self._annotation_path.replace(constances.PIXEL_ANNOTATION_POSTFIX, constances.ANNOTATION_MASK_POSTFIX))
344347

345348
def _is_valid_json(self, json_data: dict):
346349
use_case = ValidateAnnotationUseCase(
@@ -389,12 +392,12 @@ def is_valid_json(
389392

390393
def execute(self):
391394
if self.is_valid():
392-
annotation_json = self.get_annotation_json()
393-
if self.is_valid_json(annotation_json):
395+
self.set_annotation_json()
396+
if self.is_valid_json(self._annotation_json):
394397
bucket = self.s3_bucket
395398
annotation_json = self.prepare_annotations(
396399
project_type=self._project.project_type,
397-
annotations=annotation_json,
400+
annotations=self._annotation_json,
398401
annotation_classes=self._annotation_classes,
399402
templates=self._templates,
400403
reporter=self.reporter,
@@ -405,18 +408,13 @@ def execute(self):
405408
],
406409
Body=json.dumps(annotation_json),
407410
)
408-
if self._project.project_type == constances.ProjectType.PIXEL.value:
409-
mask_path = self._annotation_path.replace(
410-
"___pixel.json", constances.ANNOTATION_MASK_POSTFIX
411+
if self._project.project_type == constances.ProjectType.PIXEL.value and self._mask:
412+
bucket.put_object(
413+
Key=self.annotation_upload_data.images[self._image.uuid][
414+
"annotation_bluemap_path"
415+
],
416+
Body=self._mask,
411417
)
412-
with open(mask_path, "rb") as mask_file:
413-
file = io.BytesIO(mask_file.read())
414-
bucket.put_object(
415-
Key=self.annotation_upload_data.images[self._image.uuid][
416-
"annotation_bluemap_path"
417-
],
418-
Body=file,
419-
)
420418
if self._verbose:
421419
logger.info(
422420
"Uploading annotations for image %s in project %s.",

0 commit comments

Comments
 (0)