supervisely 6.73.397__py3-none-any.whl → 6.73.399__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +46 -4
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +26 -11
- supervisely/convert/pointcloud/pointcloud_converter.py +1 -0
- supervisely/convert/volume/nii/nii_planes_volume_converter.py +87 -21
- supervisely/convert/volume/nii/nii_volume_helper.py +60 -15
- supervisely/io/exception_handlers.py +17 -8
- supervisely/project/volume_project.py +3 -2
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/METADATA +1 -1
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/RECORD +13 -13
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/LICENSE +0 -0
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/WHEEL +0 -0
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.397.dist-info → supervisely-6.73.399.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from collections import defaultdict
|
|
2
3
|
from typing import Dict, Optional, Set, Union
|
|
3
4
|
|
|
4
|
-
from supervisely import
|
|
5
|
+
from supervisely import (
|
|
6
|
+
Annotation,
|
|
7
|
+
ProjectMeta,
|
|
8
|
+
TagApplicableTo,
|
|
9
|
+
TagMeta,
|
|
10
|
+
TagValueType,
|
|
11
|
+
logger,
|
|
12
|
+
)
|
|
5
13
|
from supervisely.convert.base_converter import AvailableImageConverters
|
|
6
14
|
from supervisely.convert.image.image_converter import ImageConverter
|
|
7
15
|
from supervisely.convert.image.pascal_voc import pascal_voc_helper
|
|
@@ -139,6 +147,7 @@ class PascalVOCConverter(ImageConverter):
|
|
|
139
147
|
|
|
140
148
|
def _create_items(self, possible_pascal_voc_dir: str) -> int:
|
|
141
149
|
existing_cls_names = set([cls.name for cls in self._meta.obj_classes])
|
|
150
|
+
tags_to_values = defaultdict(set)
|
|
142
151
|
detected_ann_cnt = 0
|
|
143
152
|
|
|
144
153
|
images_list = list_files_recursively(self._imgs_dir, valid_extensions=self.allowed_exts)
|
|
@@ -153,13 +162,42 @@ class PascalVOCConverter(ImageConverter):
|
|
|
153
162
|
item_name_noext = get_file_name(item.name)
|
|
154
163
|
item = self._scan_for_item_segm_paths(item, item_name_noext)
|
|
155
164
|
ann_path = img_ann_map.get(item_name_noext) or img_ann_map.get(item.name)
|
|
156
|
-
item = self._scan_for_item_ann_path_and_update_meta(
|
|
165
|
+
item = self._scan_for_item_ann_path_and_update_meta(
|
|
166
|
+
item, ann_path, existing_cls_names, tags_to_values
|
|
167
|
+
)
|
|
157
168
|
|
|
158
169
|
if item.ann_data or item.segm_path:
|
|
159
170
|
detected_ann_cnt += 1
|
|
160
171
|
self._items.append(item)
|
|
172
|
+
self._meta = self._update_meta_with_tags(tags_to_values)
|
|
161
173
|
return detected_ann_cnt
|
|
162
174
|
|
|
175
|
+
def _update_meta_with_tags(self, tags_to_values: Dict[str, Set[str]]) -> ProjectMeta:
|
|
176
|
+
meta = self._meta
|
|
177
|
+
object_class_names = set(meta.obj_classes.keys())
|
|
178
|
+
for tag_name, values in tags_to_values.items():
|
|
179
|
+
tag_meta = meta.get_tag_meta(tag_name)
|
|
180
|
+
if tag_meta is not None:
|
|
181
|
+
continue
|
|
182
|
+
if tag_name in pascal_voc_helper.DEFAULT_SUBCLASSES:
|
|
183
|
+
if values.difference({"0", "1"}):
|
|
184
|
+
logger.warning(
|
|
185
|
+
f"Tag '{tag_name}' has non-binary values.", extra={"values": values}
|
|
186
|
+
)
|
|
187
|
+
tag_meta = TagMeta(tag_name, TagValueType.NONE)
|
|
188
|
+
elif tag_name in object_class_names:
|
|
189
|
+
tag_meta = TagMeta(
|
|
190
|
+
tag_name,
|
|
191
|
+
TagValueType.ONEOF_STRING,
|
|
192
|
+
possible_values=list(values),
|
|
193
|
+
applicable_to=TagApplicableTo.OBJECTS_ONLY,
|
|
194
|
+
applicable_classes=[tag_name],
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
tag_meta = TagMeta(tag_name, TagValueType.ANY_STRING)
|
|
198
|
+
meta = meta.add_tag_meta(tag_meta)
|
|
199
|
+
return meta
|
|
200
|
+
|
|
163
201
|
def _scan_for_item_segm_paths(self, item: Item, item_name_noext: str) -> Item:
|
|
164
202
|
if self._segm_dir is not None:
|
|
165
203
|
segm_path = os.path.join(self._segm_dir, f"{item_name_noext}.png")
|
|
@@ -173,14 +211,18 @@ class PascalVOCConverter(ImageConverter):
|
|
|
173
211
|
return item
|
|
174
212
|
|
|
175
213
|
def _scan_for_item_ann_path_and_update_meta(
|
|
176
|
-
self,
|
|
214
|
+
self,
|
|
215
|
+
item: Item,
|
|
216
|
+
ann_path: Optional[str],
|
|
217
|
+
existing_cls_names: Set[str],
|
|
218
|
+
tags_to_values: Dict[str, Set[str]],
|
|
177
219
|
) -> Item:
|
|
178
220
|
if ann_path is None:
|
|
179
221
|
return item
|
|
180
222
|
if not file_exists(ann_path):
|
|
181
223
|
return item
|
|
182
224
|
self._meta = pascal_voc_helper.update_meta_from_xml(
|
|
183
|
-
ann_path, self._meta, existing_cls_names, self._bbox_classes_map
|
|
225
|
+
ann_path, self._meta, existing_cls_names, self._bbox_classes_map, tags_to_values
|
|
184
226
|
)
|
|
185
227
|
item.ann_data = ann_path
|
|
186
228
|
return item
|
|
@@ -13,7 +13,7 @@ from supervisely.annotation.label import Label
|
|
|
13
13
|
from supervisely.annotation.obj_class import ObjClass
|
|
14
14
|
from supervisely.annotation.obj_class_collection import ObjClassCollection
|
|
15
15
|
from supervisely.annotation.tag import Tag, TagValueType
|
|
16
|
-
from supervisely.annotation.tag_meta import TagMeta
|
|
16
|
+
from supervisely.annotation.tag_meta import TagApplicableTo, TagMeta
|
|
17
17
|
from supervisely.convert.image.image_helper import validate_image_bounds
|
|
18
18
|
from supervisely.geometry.bitmap import Bitmap
|
|
19
19
|
from supervisely.geometry.polygon import Polygon
|
|
@@ -36,6 +36,8 @@ TRAIN_TAG_NAME = "train"
|
|
|
36
36
|
VAL_TAG_NAME = "val"
|
|
37
37
|
TRAINVAL_TAG_NAME = "trainval"
|
|
38
38
|
DEFAULT_OBJECT_FIELDS = {"name", "class", "bndbox"}
|
|
39
|
+
DEFAULT_SUBCLASSES = {"pose", "truncated", "difficult", "occluded", "obstacle", "out-of-scope"}
|
|
40
|
+
|
|
39
41
|
|
|
40
42
|
default_classes_colors = {
|
|
41
43
|
"neutral": (224, 224, 192),
|
|
@@ -179,7 +181,7 @@ def get_ann(
|
|
|
179
181
|
ann = ann.add_labels(labels)
|
|
180
182
|
|
|
181
183
|
if np.sum(colored_img) > 0:
|
|
182
|
-
logger.
|
|
184
|
+
logger.warning(
|
|
183
185
|
f"Not all objects or classes are captured from source segmentation: {item.name}"
|
|
184
186
|
)
|
|
185
187
|
|
|
@@ -214,7 +216,7 @@ def xml_to_sly_labels(
|
|
|
214
216
|
cls_name = renamed_classes[cls_name]
|
|
215
217
|
obj_cls = meta.obj_classes.get(cls_name)
|
|
216
218
|
if obj_cls is None:
|
|
217
|
-
logger.
|
|
219
|
+
logger.warning(f"Class {cls_name} is not found in meta. Skipping.")
|
|
218
220
|
continue
|
|
219
221
|
elif field_name == "bndbox":
|
|
220
222
|
bbox_coords = [
|
|
@@ -227,11 +229,25 @@ def xml_to_sly_labels(
|
|
|
227
229
|
tag_name = renamed_tags[tag_name]
|
|
228
230
|
tag_meta = meta.get_tag_meta(tag_name)
|
|
229
231
|
if tag_meta is None:
|
|
230
|
-
logger.
|
|
232
|
+
logger.warning(f"Tag meta for '{field_name}' is not found in meta. Skipping.")
|
|
231
233
|
continue
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
if tag_meta.value_type == TagValueType.ANY_STRING:
|
|
235
|
+
if not isinstance(value, str):
|
|
236
|
+
value = str(value)
|
|
237
|
+
tags.append(Tag(tag_meta, value))
|
|
238
|
+
elif tag_meta.value_type == TagValueType.NONE:
|
|
239
|
+
if int(value) == 1:
|
|
240
|
+
tags.append(Tag(tag_meta))
|
|
241
|
+
else:
|
|
242
|
+
logger.debug("Tag with value '0' not added to labels.")
|
|
243
|
+
elif tag_meta.value_type == TagValueType.ONEOF_STRING:
|
|
244
|
+
if value not in tag_meta.possible_values:
|
|
245
|
+
logger.warning(
|
|
246
|
+
f"Value '{value}' for tag '{tag_name}' is not in possible values: {tag_meta.possible_values}. Skipping."
|
|
247
|
+
)
|
|
248
|
+
continue
|
|
249
|
+
tags.append(Tag(tag_meta, value))
|
|
250
|
+
|
|
235
251
|
if geometry is None or obj_cls is None:
|
|
236
252
|
continue
|
|
237
253
|
labels.append(Label(geometry, obj_cls, tags))
|
|
@@ -245,6 +261,7 @@ def update_meta_from_xml(
|
|
|
245
261
|
meta: ProjectMeta,
|
|
246
262
|
existing_cls_names: set,
|
|
247
263
|
bbox_classes_map: dict,
|
|
264
|
+
tags_to_values: Dict[str, set],
|
|
248
265
|
) -> ProjectMeta:
|
|
249
266
|
import xml.etree.ElementTree as ET
|
|
250
267
|
|
|
@@ -282,10 +299,8 @@ def update_meta_from_xml(
|
|
|
282
299
|
meta = meta.add_obj_class(obj_cls)
|
|
283
300
|
bbox_classes_map[original_class_name] = class_name
|
|
284
301
|
elif field_name not in DEFAULT_OBJECT_FIELDS:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
tag_meta = TagMeta(field_name, TagValueType.ANY_STRING)
|
|
288
|
-
meta = meta.add_tag_meta(tag_meta)
|
|
302
|
+
value = element.text
|
|
303
|
+
tags_to_values[field_name].add(value)
|
|
289
304
|
|
|
290
305
|
return meta
|
|
291
306
|
|
|
@@ -246,6 +246,7 @@ class PointcloudConverter(BaseConverter):
|
|
|
246
246
|
) or dir_name.endswith("_pcd"):
|
|
247
247
|
rimg_ann_dict[file] = full_path
|
|
248
248
|
elif imghdr.what(full_path):
|
|
249
|
+
dir_name = os.path.basename(root)
|
|
249
250
|
if dir_name not in rimg_dict:
|
|
250
251
|
rimg_dict[dir_name] = []
|
|
251
252
|
rimg_dict[dir_name].append(full_path)
|
|
@@ -164,7 +164,7 @@ class NiiPlaneStructuredConverter(NiiConverter, VolumeConverter):
|
|
|
164
164
|
item.custom_data["scores"] = scores
|
|
165
165
|
except Exception as e:
|
|
166
166
|
logger.warning(f"Failed to read scores from {scores_paths[0]}: {e}")
|
|
167
|
-
item.is_semantic = len(
|
|
167
|
+
item.is_semantic = len(item.ann_data) == 1
|
|
168
168
|
if cls_color_map is not None:
|
|
169
169
|
item.custom_data["cls_color_map"] = cls_color_map
|
|
170
170
|
self._items.append(item)
|
|
@@ -226,6 +226,7 @@ class NiiPlaneStructuredAnnotationConverter(NiiConverter, VolumeConverter):
|
|
|
226
226
|
def __init__(self, *args, **kwargs):
|
|
227
227
|
super().__init__(*args, **kwargs)
|
|
228
228
|
self._is_semantic = False
|
|
229
|
+
self._is_scores = False
|
|
229
230
|
self.volume_meta = None
|
|
230
231
|
|
|
231
232
|
@property
|
|
@@ -236,6 +237,14 @@ class NiiPlaneStructuredAnnotationConverter(NiiConverter, VolumeConverter):
|
|
|
236
237
|
def is_semantic(self, value: bool):
|
|
237
238
|
self._is_semantic = value
|
|
238
239
|
|
|
240
|
+
@property
|
|
241
|
+
def is_scores(self) -> bool:
|
|
242
|
+
return self._is_scores
|
|
243
|
+
|
|
244
|
+
@is_scores.setter
|
|
245
|
+
def is_scores(self, value: bool):
|
|
246
|
+
self._is_scores = value
|
|
247
|
+
|
|
239
248
|
def create_empty_annotation(self):
|
|
240
249
|
return VolumeAnnotation(self.volume_meta)
|
|
241
250
|
|
|
@@ -274,17 +283,22 @@ class NiiPlaneStructuredAnnotationConverter(NiiConverter, VolumeConverter):
|
|
|
274
283
|
for file in files:
|
|
275
284
|
path = os.path.join(root, file)
|
|
276
285
|
name_parts = helper.parse_name_parts(file)
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
try:
|
|
281
|
-
nii = load(path)
|
|
282
|
-
except filebasedimages.ImageFileError:
|
|
283
|
-
logger.warning(f"Failed to load NIfTI file: {path}")
|
|
284
|
-
continue
|
|
286
|
+
if name_parts is None:
|
|
287
|
+
continue
|
|
288
|
+
if is_nii(file) or name_parts.type == helper.SCORE_NAME:
|
|
285
289
|
item = self.Item(item_path=None, ann_data=path)
|
|
286
|
-
item.set_shape(nii.shape)
|
|
287
290
|
item.custom_data["name_parts"] = name_parts
|
|
291
|
+
if name_parts.is_ann:
|
|
292
|
+
try:
|
|
293
|
+
nii = load(path)
|
|
294
|
+
except filebasedimages.ImageFileError:
|
|
295
|
+
logger.warning(f"Failed to load NIfTI file: {path}")
|
|
296
|
+
continue
|
|
297
|
+
item.set_shape(nii.shape)
|
|
298
|
+
elif name_parts.type == helper.SCORE_NAME:
|
|
299
|
+
item.is_scores = True
|
|
300
|
+
scores = helper.get_scores_from_table(path, name_parts.plane)
|
|
301
|
+
item.custom_data["scores"] = scores
|
|
288
302
|
if cls_color_map is not None:
|
|
289
303
|
item.custom_data["cls_color_map"] = cls_color_map
|
|
290
304
|
self._items.append(item)
|
|
@@ -293,6 +307,13 @@ class NiiPlaneStructuredAnnotationConverter(NiiConverter, VolumeConverter):
|
|
|
293
307
|
if cls_color_map is not None:
|
|
294
308
|
obj_classes = [ObjClass(name, Mask3D, color) for name, color in cls_color_map.values()]
|
|
295
309
|
|
|
310
|
+
for item in self._items:
|
|
311
|
+
name_parts = item.custom_data.get("name_parts")
|
|
312
|
+
if item.is_scores:
|
|
313
|
+
continue
|
|
314
|
+
if name_parts.ending_idx is None:
|
|
315
|
+
item.is_semantic = True
|
|
316
|
+
|
|
296
317
|
self._meta = ProjectMeta(obj_classes=obj_classes)
|
|
297
318
|
return len(self._items) > 0
|
|
298
319
|
|
|
@@ -369,20 +390,65 @@ class NiiPlaneStructuredAnnotationConverter(NiiConverter, VolumeConverter):
|
|
|
369
390
|
else:
|
|
370
391
|
progress_cb = None
|
|
371
392
|
|
|
372
|
-
|
|
393
|
+
volumeids_to_objects = defaultdict(list)
|
|
394
|
+
|
|
395
|
+
for item, volume in sorted(matched_dict.items(), key=lambda pair: pair[0].is_scores):
|
|
373
396
|
item.volume_meta = volume.meta
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
397
|
+
if not item.is_scores:
|
|
398
|
+
ann = self.to_supervisely(item, meta, renamed_classes, None)
|
|
399
|
+
if self._meta_changed:
|
|
400
|
+
meta, renamed_classes, _ = self.merge_metas_with_conflicts(api, dataset_id)
|
|
401
|
+
self._meta_changed = False
|
|
402
|
+
api.volume.annotation.append(volume.id, ann, volume_info=volume)
|
|
403
|
+
else:
|
|
404
|
+
class_id_to_pixel_value = helper.get_class_id_to_pixel_value_map(meta)
|
|
405
|
+
scores = item.custom_data.get("scores", {})
|
|
406
|
+
if not scores:
|
|
407
|
+
logger.warning(f"No scores found for {item.ann_data}. Skipping.")
|
|
408
|
+
continue
|
|
409
|
+
|
|
410
|
+
if volume.dataset_id not in volumeids_to_objects:
|
|
411
|
+
for obj in api.volume.object.get_list(volume.dataset_id):
|
|
412
|
+
volumeids_to_objects[obj.entity_id].append(obj)
|
|
413
|
+
|
|
414
|
+
obj_id_to_class_id = {
|
|
415
|
+
obj.id: obj.class_id for obj in volumeids_to_objects[volume.id]
|
|
416
|
+
}
|
|
417
|
+
if not obj_id_to_class_id:
|
|
418
|
+
logger.warning(
|
|
419
|
+
f"No objects found for volume {volume.id}. Skipping figure updates."
|
|
420
|
+
)
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
volume_figure_dict = api.volume.figure.download(
|
|
424
|
+
volume.dataset_id, [volume.id], skip_geometry=True
|
|
425
|
+
)
|
|
426
|
+
figures_list = volume_figure_dict.get(volume.id, [])
|
|
427
|
+
for figure in figures_list:
|
|
428
|
+
class_id = obj_id_to_class_id.get(figure.object_id, None)
|
|
429
|
+
if class_id is None:
|
|
430
|
+
logger.warning(
|
|
431
|
+
f"Class ID for figure (id: {figure.id}) not found in volume objects. Skipping figure update.",
|
|
432
|
+
extra={
|
|
433
|
+
"obj_id_to_class_id": obj_id_to_class_id,
|
|
434
|
+
"object_id": figure.object_id,
|
|
435
|
+
},
|
|
436
|
+
)
|
|
437
|
+
continue
|
|
438
|
+
pixel_value = class_id_to_pixel_value.get(class_id, None)
|
|
439
|
+
if pixel_value is None:
|
|
440
|
+
logger.warning(
|
|
441
|
+
f"Pixel value for class ID {class_id} not found in meta. Skipping figure update."
|
|
442
|
+
)
|
|
443
|
+
continue
|
|
444
|
+
figure_custom_data = scores.get(pixel_value, {})
|
|
445
|
+
if figure_custom_data:
|
|
446
|
+
api.volume.figure.update_custom_data(figure.id, figure_custom_data)
|
|
447
|
+
logger.debug(
|
|
448
|
+
f"Updated figure {figure.id} with custom data: {figure_custom_data}"
|
|
449
|
+
)
|
|
379
450
|
progress_cb(1) if log_progress else None
|
|
380
451
|
|
|
381
|
-
res_ds_info = api.dataset.get_info_by_id(dataset_id)
|
|
382
|
-
if res_ds_info.items_count == 0:
|
|
383
|
-
logger.info("Resulting dataset is empty. Removing it.")
|
|
384
|
-
api.dataset.remove(dataset_id)
|
|
385
|
-
|
|
386
452
|
if log_progress:
|
|
387
453
|
if is_development():
|
|
388
454
|
progress.close()
|
|
@@ -10,10 +10,12 @@ from supervisely import Api
|
|
|
10
10
|
from supervisely.collection.str_enum import StrEnum
|
|
11
11
|
from supervisely.geometry.mask_3d import Mask3D
|
|
12
12
|
from supervisely.io.fs import ensure_base_path, get_file_ext, get_file_name
|
|
13
|
+
from supervisely.project.project_meta import ProjectMeta
|
|
13
14
|
from supervisely.sly_logger import logger
|
|
14
15
|
from supervisely.volume.volume import convert_3d_nifti_to_nrrd
|
|
15
16
|
|
|
16
17
|
VOLUME_NAME = "anatomic"
|
|
18
|
+
SCORE_NAME = "score"
|
|
17
19
|
LABEL_NAME = ["inference", "label", "annotation", "mask", "segmentation"]
|
|
18
20
|
MASK_PIXEL_VALUE = "Mask pixel value: "
|
|
19
21
|
|
|
@@ -172,20 +174,62 @@ def get_scores_from_table(csv_file_path: str, plane: str) -> dict:
|
|
|
172
174
|
return result
|
|
173
175
|
|
|
174
176
|
|
|
177
|
+
def _find_pixel_values(descr: str) -> int:
|
|
178
|
+
"""
|
|
179
|
+
Find the pixel value in the description string.
|
|
180
|
+
"""
|
|
181
|
+
lines = descr.split("\n")
|
|
182
|
+
for line in lines:
|
|
183
|
+
if line.strip().startswith(MASK_PIXEL_VALUE):
|
|
184
|
+
try:
|
|
185
|
+
value_part = line.strip().split(MASK_PIXEL_VALUE)[1]
|
|
186
|
+
return int(value_part.strip())
|
|
187
|
+
except (IndexError, ValueError):
|
|
188
|
+
continue
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_class_id_to_pixel_value_map(meta: ProjectMeta) -> dict:
|
|
193
|
+
class_id_to_pixel_value = {}
|
|
194
|
+
for obj_class in meta.obj_classes.items():
|
|
195
|
+
pixel_value = _find_pixel_values(obj_class.description)
|
|
196
|
+
if pixel_value is not None:
|
|
197
|
+
class_id_to_pixel_value[obj_class.sly_id] = pixel_value
|
|
198
|
+
elif "Segment_" in obj_class.name:
|
|
199
|
+
try:
|
|
200
|
+
pixel_value = int(obj_class.name.split("_")[-1])
|
|
201
|
+
class_id_to_pixel_value[obj_class.sly_id] = pixel_value
|
|
202
|
+
except (ValueError, IndexError):
|
|
203
|
+
logger.warning(
|
|
204
|
+
f"Failed to parse pixel value from class name: {obj_class.name}. "
|
|
205
|
+
"Please ensure the class name ends with a valid integer."
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
logger.warning(
|
|
209
|
+
f"Class {obj_class.name} does not have a pixel value defined in its description. "
|
|
210
|
+
"Please update the class description to include 'Mask pixel value: <value>'."
|
|
211
|
+
)
|
|
212
|
+
return class_id_to_pixel_value
|
|
213
|
+
|
|
214
|
+
|
|
175
215
|
class AnnotationMatcher:
|
|
176
216
|
def __init__(self, items, dataset_id):
|
|
177
|
-
self._items = items
|
|
178
|
-
self._ds_id = dataset_id
|
|
179
217
|
self._ann_paths = defaultdict(list)
|
|
180
218
|
self._item_by_filename = {}
|
|
181
219
|
self._item_by_path = {}
|
|
182
220
|
|
|
183
|
-
self.
|
|
221
|
+
self.items = items
|
|
222
|
+
self._ds_id = dataset_id
|
|
184
223
|
|
|
185
224
|
self._project_wide = False
|
|
186
225
|
self._volumes = None
|
|
187
226
|
|
|
188
|
-
|
|
227
|
+
@property
|
|
228
|
+
def items(self):
|
|
229
|
+
return self._items
|
|
230
|
+
|
|
231
|
+
@items.setter
|
|
232
|
+
def items(self, items):
|
|
189
233
|
self._items = items
|
|
190
234
|
self._ann_paths.clear()
|
|
191
235
|
self._item_by_filename.clear()
|
|
@@ -202,13 +246,13 @@ class AnnotationMatcher:
|
|
|
202
246
|
|
|
203
247
|
def get_volumes(self, api: Api):
|
|
204
248
|
dataset_info = api.dataset.get_info_by_id(self._ds_id)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if dataset_info.items_count > 0 and len(self._ann_paths.keys()) == 1:
|
|
249
|
+
if dataset_info.items_count > 0:
|
|
250
|
+
datasets = {dataset_info.name: dataset_info}
|
|
208
251
|
self._project_wide = False
|
|
209
252
|
else:
|
|
210
253
|
datasets = {
|
|
211
|
-
dsinfo.name: dsinfo
|
|
254
|
+
dsinfo.name: dsinfo
|
|
255
|
+
for dsinfo in api.dataset.get_list(dataset_info.project_id, recursive=True)
|
|
212
256
|
}
|
|
213
257
|
self._project_wide = True
|
|
214
258
|
|
|
@@ -270,6 +314,8 @@ class AnnotationMatcher:
|
|
|
270
314
|
# items_to_remove = []
|
|
271
315
|
for item, volume in item_to_volume.items():
|
|
272
316
|
volume_shape = tuple(volume.file_meta["sizes"])
|
|
317
|
+
if item.shape is None:
|
|
318
|
+
continue
|
|
273
319
|
if item.shape != volume_shape:
|
|
274
320
|
logger.warning(f"Volume shape mismatch: {item.shape} != {volume_shape}")
|
|
275
321
|
# items_to_remove.append(item)
|
|
@@ -363,27 +409,26 @@ def parse_name_parts(full_name: str) -> NameParts:
|
|
|
363
409
|
type = None
|
|
364
410
|
is_ann = False
|
|
365
411
|
if VOLUME_NAME in full_name:
|
|
366
|
-
type =
|
|
367
|
-
elif
|
|
412
|
+
type = VOLUME_NAME
|
|
413
|
+
elif SCORE_NAME in full_name and full_name.endswith(".csv"):
|
|
414
|
+
type = SCORE_NAME
|
|
415
|
+
else:
|
|
368
416
|
type = next((part for part in LABEL_NAME if part in full_name), None)
|
|
369
417
|
is_ann = type is not None
|
|
370
|
-
elif "score" in name_no_ext or get_file_ext(full_name) == ".csv":
|
|
371
|
-
type = "score"
|
|
372
418
|
|
|
373
419
|
if type is None:
|
|
374
420
|
return
|
|
375
421
|
|
|
376
422
|
plane = None
|
|
423
|
+
tokens = name_no_ext.lower().split("_")
|
|
377
424
|
for part in PlanePrefix.values():
|
|
378
|
-
if part in
|
|
425
|
+
if part in tokens:
|
|
379
426
|
plane = part
|
|
380
427
|
break
|
|
381
428
|
|
|
382
429
|
if plane is None:
|
|
383
430
|
return
|
|
384
431
|
|
|
385
|
-
is_ann = any(part in name.lower() for part in LABEL_NAME)
|
|
386
|
-
|
|
387
432
|
patient_uuid = None
|
|
388
433
|
case_uuid = None
|
|
389
434
|
|
|
@@ -9,7 +9,7 @@ from typing import Callable, Dict, List, Optional, Union
|
|
|
9
9
|
from requests.exceptions import HTTPError, RetryError
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
|
-
from supervisely import
|
|
12
|
+
from supervisely import is_community, is_development
|
|
13
13
|
from supervisely.app import DialogWindowError
|
|
14
14
|
from supervisely.sly_logger import EventType, logger
|
|
15
15
|
|
|
@@ -250,7 +250,9 @@ class ErrorHandler:
|
|
|
250
250
|
def __init__(self, exception: Exception, stack: List[traceback.FrameSummary] = None):
|
|
251
251
|
self.code = 2004
|
|
252
252
|
self.title = "Image files size limit exceeded"
|
|
253
|
-
self.message =
|
|
253
|
+
self.message = (
|
|
254
|
+
"The given image file size is too large (more than 1 GB) for Community Edition."
|
|
255
|
+
)
|
|
254
256
|
|
|
255
257
|
super().__init__(
|
|
256
258
|
exception,
|
|
@@ -290,12 +292,14 @@ class ErrorHandler:
|
|
|
290
292
|
|
|
291
293
|
class OutOfMemory(HandleException):
|
|
292
294
|
def __init__(self, exception: Exception, stack: List[traceback.FrameSummary] = None):
|
|
293
|
-
device_prefix =
|
|
295
|
+
device_prefix = (
|
|
296
|
+
"GPU memory"
|
|
297
|
+
if any([s in exception.args[0] for s in ["CUDA", "cuda", "GPU"]])
|
|
298
|
+
else "memory"
|
|
299
|
+
)
|
|
294
300
|
self.code = 2007
|
|
295
301
|
self.title = f"Out of {device_prefix} on the computer where the agent is deployed"
|
|
296
|
-
self.message =
|
|
297
|
-
"Please, check your agent's memory usage, reduce batch size or use a device with more memory capacity."
|
|
298
|
-
)
|
|
302
|
+
self.message = "Please, check your agent's memory usage, reduce batch size or use a device with more memory capacity."
|
|
299
303
|
|
|
300
304
|
super().__init__(
|
|
301
305
|
exception,
|
|
@@ -471,7 +475,9 @@ class ErrorHandler:
|
|
|
471
475
|
def __init__(self, exception: Exception, stack: List[traceback.FrameSummary] = None):
|
|
472
476
|
self.code = 2019
|
|
473
477
|
self.title = "Upload remote images is only available for PRO teams"
|
|
474
|
-
self.message =
|
|
478
|
+
self.message = (
|
|
479
|
+
"Please, upgrade plan to upload remote images: https://supervisely.com/pricing/"
|
|
480
|
+
)
|
|
475
481
|
|
|
476
482
|
super().__init__(
|
|
477
483
|
exception,
|
|
@@ -485,7 +491,9 @@ class ErrorHandler:
|
|
|
485
491
|
def __init__(self, exception: Exception, stack: List[traceback.FrameSummary] = None):
|
|
486
492
|
self.code = 2020
|
|
487
493
|
self.title = "Upload remote videos is only available for PRO teams"
|
|
488
|
-
self.message =
|
|
494
|
+
self.message = (
|
|
495
|
+
"Please, upgrade plan to upload remote videos: https://supervisely.com/pricing/"
|
|
496
|
+
)
|
|
489
497
|
|
|
490
498
|
super().__init__(
|
|
491
499
|
exception,
|
|
@@ -681,6 +689,7 @@ ERROR_PATTERNS = {
|
|
|
681
689
|
r".*images\.bulk\.upload.*FileSize.*\"sizeLimit\":1073741824.*": ErrorHandler.API.ImageFilesSizeTooLarge,
|
|
682
690
|
r".*videos\.bulk\.upload.*FileSize.*sizeLimit\":314572800.*": ErrorHandler.API.VideoFilesSizeTooLarge,
|
|
683
691
|
r".*images\.bulk\.upload.*FileSize.*\"sizeLimit\":157286400.*": ErrorHandler.API.VolumeFilesSizeTooLarge,
|
|
692
|
+
r".*images\.bulk\.upload.*FileSize.*\"sizeLimit\":\"25mb\".*": ErrorHandler.API.VolumeFilesSizeTooLarge,
|
|
684
693
|
r".*Dataset with datasetId.*is either archived, doesn't exist or you don't have enough permissions to access.*": ErrorHandler.API.DatasetNotFound,
|
|
685
694
|
r".*Project with projectId.*is either archived, doesn't exist or you don't have enough permissions to access.*": ErrorHandler.API.ProjectNotFound,
|
|
686
695
|
r".*api\.task\.set_field.*": ErrorHandler.API.AppSetFieldError,
|
|
@@ -477,8 +477,9 @@ def download_volume_project(
|
|
|
477
477
|
mesh_ids.append(figure_id)
|
|
478
478
|
figure_path = dataset_fs.get_interpolation_path(volume_name, sf)
|
|
479
479
|
mesh_paths.append(figure_path)
|
|
480
|
-
|
|
481
|
-
figs = api.volume.figure.download(dataset.id, [volume_id], skip_geometry=True)
|
|
480
|
+
|
|
481
|
+
figs = api.volume.figure.download(dataset.id, [volume_id], skip_geometry=True)
|
|
482
|
+
figs = figs.get(volume_id, {})
|
|
482
483
|
figs_ids_map = {fig.id: fig for fig in figs}
|
|
483
484
|
for ann_fig in ann.figures + ann.spatial_figures:
|
|
484
485
|
fig = figs_ids_map.get(ann_fig.geometry.sly_id)
|
|
@@ -610,8 +610,8 @@ supervisely/convert/image/multi_view/multi_view.py,sha256=V-6oFN6oDre7UhejfyDkGK
|
|
|
610
610
|
supervisely/convert/image/multispectral/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
611
611
|
supervisely/convert/image/multispectral/multispectral_converter.py,sha256=T3etYVNI0AUUrQsQhxw_r85NthXrqhqmdZQfz8kUY0g,5194
|
|
612
612
|
supervisely/convert/image/pascal_voc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
613
|
-
supervisely/convert/image/pascal_voc/pascal_voc_converter.py,sha256=
|
|
614
|
-
supervisely/convert/image/pascal_voc/pascal_voc_helper.py,sha256=
|
|
613
|
+
supervisely/convert/image/pascal_voc/pascal_voc_converter.py,sha256=JwbkAT3W7aNHuxr9fas9PGrfDy4f5beyx6DWItIdVTw,9247
|
|
614
|
+
supervisely/convert/image/pascal_voc/pascal_voc_helper.py,sha256=Ek2KeQjKhSDup8PBE53Pg82x7HHNjDkWNilmz7BHYj4,30199
|
|
615
615
|
supervisely/convert/image/pdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
616
616
|
supervisely/convert/image/pdf/pdf_converter.py,sha256=LKvVng9jPp0cSIjYEjKLOb48wtdOdB7LXS2gjmOdZhE,2442
|
|
617
617
|
supervisely/convert/image/pdf/pdf_helper.py,sha256=IDwLEvsVy8lu-KC1lXvSRkZZ9BCC6ylebnNEtLQU5L4,1288
|
|
@@ -623,7 +623,7 @@ supervisely/convert/image/yolo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
623
623
|
supervisely/convert/image/yolo/yolo_converter.py,sha256=Wn5dR05y4SEPONcaxWr9ofnbvbf-SbRZN0fkksk5Dps,11391
|
|
624
624
|
supervisely/convert/image/yolo/yolo_helper.py,sha256=5b0ShsVlqikA071VT8AiRW_079_WD6pdB5Bx3OU12Bw,25989
|
|
625
625
|
supervisely/convert/pointcloud/__init__.py,sha256=WPeIpPoTWDIKAa0lF6t2SMUhFNZ0l-vKujf6yD6w7SA,589
|
|
626
|
-
supervisely/convert/pointcloud/pointcloud_converter.py,sha256
|
|
626
|
+
supervisely/convert/pointcloud/pointcloud_converter.py,sha256=-FQ-YTXu-ywrJ0yBfg0tBV_t8SB6kKD4MlqkdAZMWEA,12132
|
|
627
627
|
supervisely/convert/pointcloud/bag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
628
628
|
supervisely/convert/pointcloud/bag/bag_converter.py,sha256=WWd6A2hS7H4MRgtLdJ-yYgmNU-Wk2eycl6LTTJM2GKQ,11391
|
|
629
629
|
supervisely/convert/pointcloud/bag/bag_helper.py,sha256=2TFe49isZTxMhya-PApqLPxrvGnvRFMBc_--BwyCpWU,4284
|
|
@@ -674,9 +674,9 @@ supervisely/convert/volume/dicom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
674
674
|
supervisely/convert/volume/dicom/dicom_converter.py,sha256=Hw4RxU_qvllk6M26udZE6G-m1RWR8-VVPcEPwFlqrVg,3354
|
|
675
675
|
supervisely/convert/volume/dicom/dicom_helper.py,sha256=OrKlyt1hA5BOXKhE1LF1WxBIv3b6t96xRras4OSAuNM,2891
|
|
676
676
|
supervisely/convert/volume/nii/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
677
|
-
supervisely/convert/volume/nii/nii_planes_volume_converter.py,sha256=
|
|
677
|
+
supervisely/convert/volume/nii/nii_planes_volume_converter.py,sha256=XQ5o7mg3OJ9UO-wM14O718TIkVXeXDAz-lJDV--0UE8,20043
|
|
678
678
|
supervisely/convert/volume/nii/nii_volume_converter.py,sha256=gR1_dfUf0L-K49B8qVHF7DiiDKDmxUQKKSPLdlqoYC4,8691
|
|
679
|
-
supervisely/convert/volume/nii/nii_volume_helper.py,sha256=
|
|
679
|
+
supervisely/convert/volume/nii/nii_volume_helper.py,sha256=oBALqwQDBuUwN9UHQP7PM-NyoH41d2FDickMSkVtksI,17597
|
|
680
680
|
supervisely/convert/volume/sly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
681
681
|
supervisely/convert/volume/sly/sly_volume_converter.py,sha256=TI1i_aVYFFoqLHqVzCXnFeR6xobhGcgN_xWFZcpRqbE,6730
|
|
682
682
|
supervisely/convert/volume/sly/sly_volume_helper.py,sha256=gUY0GW3zDMlO2y-zQQG36uoXMrKkKz4-ErM1CDxFCxE,5620
|
|
@@ -724,7 +724,7 @@ supervisely/imaging/image.py,sha256=1KNc4qRbP9OlI4Yta07Kc2ohAgSBJ_9alF9Jag74w30,
|
|
|
724
724
|
supervisely/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
725
725
|
supervisely/io/docker_utils.py,sha256=hb_HXGM8IYB0PF-nD7NxMwaHgzaxIFxofsUzQ_RCUZI,7935
|
|
726
726
|
supervisely/io/env.py,sha256=l-Xkil6FVxMP8FmnjF4H4p3RJQ3AoBd0OUjZHA2QVTc,21193
|
|
727
|
-
supervisely/io/exception_handlers.py,sha256=
|
|
727
|
+
supervisely/io/exception_handlers.py,sha256=22LPlLgyq59DnrhpaFrbGBYJE7uxO64VTZjsPJC0PLE,36757
|
|
728
728
|
supervisely/io/fs.py,sha256=pzNAK5fbT3G-7PKRY5oYcOPjgXeZ9x5Dyry7fzzZsr8,63604
|
|
729
729
|
supervisely/io/fs_cache.py,sha256=985gvBGzveLcDudgz10E4EWVjP9jxdU1Pa0GFfCBoCA,6520
|
|
730
730
|
supervisely/io/github_utils.py,sha256=jGmvQJ5bjtACuSFABzrxL0jJdh14SezovrHp8T-9y8g,1779
|
|
@@ -1047,7 +1047,7 @@ supervisely/project/project_type.py,sha256=7mQ7zg6r7Bm2oFn5aR8n_PeLqMmOaPZd6ph7Z
|
|
|
1047
1047
|
supervisely/project/readme_template.md,sha256=SFAfNF_uxSBJJ45A8qZ0MRuHnwSE4Gu_Z7UJqPMgRzg,9254
|
|
1048
1048
|
supervisely/project/upload.py,sha256=ys95MXFh-rtq-EAsNsiRi3wgbFUCEsY2un3_bd5hJkE,3753
|
|
1049
1049
|
supervisely/project/video_project.py,sha256=7i8__1zoU2Uryicjfa2_7p3JLnSPTv14ctLJPQGgnPY,66315
|
|
1050
|
-
supervisely/project/volume_project.py,sha256=
|
|
1050
|
+
supervisely/project/volume_project.py,sha256=yHJ2KTPhkqxlSHkafXQqyJqDgl8KHkx2fKkkkgw86qg,23600
|
|
1051
1051
|
supervisely/pyscripts_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1052
1052
|
supervisely/pyscripts_utils/utils.py,sha256=scEwHJvHRQa8NHIOn2eTwH6-Zc8CGdLoxM-WzH9jcRo,314
|
|
1053
1053
|
supervisely/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -1114,9 +1114,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1114
1114
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1115
1115
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1116
1116
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1117
|
-
supervisely-6.73.
|
|
1118
|
-
supervisely-6.73.
|
|
1119
|
-
supervisely-6.73.
|
|
1120
|
-
supervisely-6.73.
|
|
1121
|
-
supervisely-6.73.
|
|
1122
|
-
supervisely-6.73.
|
|
1117
|
+
supervisely-6.73.399.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1118
|
+
supervisely-6.73.399.dist-info/METADATA,sha256=ZxlcTeH4-lJvPtaN7rpoQXr5o-qVhvyYrcfbFUiqm_M,35254
|
|
1119
|
+
supervisely-6.73.399.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
1120
|
+
supervisely-6.73.399.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1121
|
+
supervisely-6.73.399.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1122
|
+
supervisely-6.73.399.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|