supervisely 6.73.325__py3-none-any.whl → 6.73.327__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/annotation/annotation.py +1 -1
- supervisely/app/widgets/pretrained_models_selector/pretrained_models_selector.py +17 -14
- supervisely/app/widgets/pretrained_models_selector/template.html +2 -1
- supervisely/convert/image/yolo/yolo_helper.py +95 -25
- supervisely/convert/volume/nii/nii_planes_volume_converter.py +54 -6
- supervisely/convert/volume/nii/nii_volume_converter.py +7 -7
- supervisely/convert/volume/nii/nii_volume_helper.py +49 -0
- supervisely/nn/inference/gui/serving_gui_template.py +2 -3
- supervisely/nn/inference/inference.py +33 -25
- supervisely/nn/training/gui/classes_selector.py +24 -19
- supervisely/nn/training/gui/gui.py +90 -37
- supervisely/nn/training/gui/hyperparameters_selector.py +32 -15
- supervisely/nn/training/gui/input_selector.py +13 -2
- supervisely/nn/training/gui/model_selector.py +16 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +10 -1
- supervisely/nn/training/gui/training_artifacts.py +23 -4
- supervisely/nn/training/gui/training_logs.py +15 -3
- supervisely/nn/training/gui/training_process.py +14 -13
- supervisely/nn/training/train_app.py +59 -24
- supervisely/nn/utils.py +9 -0
- supervisely/project/project.py +16 -3
- supervisely/volume/volume.py +19 -21
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/METADATA +1 -1
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/RECORD +28 -28
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/LICENSE +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/WHEEL +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.327.dist-info}/top_level.txt +0 -0
|
@@ -3088,7 +3088,7 @@ class Annotation:
|
|
|
3088
3088
|
def to_yolo(
|
|
3089
3089
|
self,
|
|
3090
3090
|
class_names: List[str],
|
|
3091
|
-
task_type: Literal["
|
|
3091
|
+
task_type: Literal["detect", "segment", "pose"] = "detect",
|
|
3092
3092
|
) -> List[str]:
|
|
3093
3093
|
"""
|
|
3094
3094
|
Convert Supervisely annotation to YOLO annotation format.
|
|
@@ -4,7 +4,7 @@ from supervisely.api.api import Api
|
|
|
4
4
|
from supervisely.app.content import DataJson, StateJson
|
|
5
5
|
from supervisely.app.widgets import Widget
|
|
6
6
|
from supervisely.io.fs import get_file_ext
|
|
7
|
-
from supervisely.nn.utils import ModelSource
|
|
7
|
+
from supervisely.nn.utils import ModelSource, _get_model_name
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class PretrainedModelsSelector(Widget):
|
|
@@ -147,9 +147,11 @@ class PretrainedModelsSelector(Widget):
|
|
|
147
147
|
if train_version == "v1":
|
|
148
148
|
model_name = selected_model.get(model_name_column)
|
|
149
149
|
if model_name is None:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
model_name = _get_model_name(selected_model)
|
|
151
|
+
if model_name is None:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"Could not find model name. Make sure you have column 'Model' in your models list."
|
|
154
|
+
)
|
|
153
155
|
|
|
154
156
|
model_meta = selected_model.get("meta")
|
|
155
157
|
if model_meta is None:
|
|
@@ -230,24 +232,25 @@ class PretrainedModelsSelector(Widget):
|
|
|
230
232
|
for task_type in self._table_data:
|
|
231
233
|
for arch_type in self._table_data[task_type]:
|
|
232
234
|
for idx, model in enumerate(self._table_data[task_type][arch_type]):
|
|
233
|
-
|
|
234
|
-
if
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
name_from_info = _get_model_name(model)
|
|
236
|
+
if name_from_info is not None:
|
|
237
|
+
if name_from_info.lower() == model_name.lower():
|
|
238
|
+
self.set_active_task_type(task_type)
|
|
239
|
+
self.set_active_arch_type(arch_type)
|
|
240
|
+
self.set_active_row(idx)
|
|
241
|
+
return
|
|
239
242
|
|
|
240
243
|
def get_by_model_name(self, model_name: str) -> Union[Dict, None]:
|
|
241
244
|
for task_type in self._table_data:
|
|
242
245
|
for arch_type in self._table_data[task_type]:
|
|
243
246
|
for idx, model in enumerate(self._table_data[task_type][arch_type]):
|
|
244
|
-
|
|
245
|
-
if
|
|
246
|
-
|
|
247
|
+
name_from_info = _get_model_name(model)
|
|
248
|
+
if name_from_info is not None:
|
|
249
|
+
if name_from_info.lower() == model_name.lower():
|
|
250
|
+
return model
|
|
247
251
|
|
|
248
252
|
def _filter_and_sort_models(self, models: List[Dict], sort_models: bool = True) -> Dict:
|
|
249
253
|
filtered_models = {}
|
|
250
|
-
|
|
251
254
|
for model in models:
|
|
252
255
|
for key in model:
|
|
253
256
|
if isinstance(model[key], (int, float)):
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
<tr>
|
|
63
63
|
<template v-for="(col, colKey) in data.{{{widget.widget_id}}}.tableData[state.{{{widget.widget_id}}}.selectedTaskType][state.{{{widget.widget_id}}}.selectedArchType][0]">
|
|
64
64
|
<th v-if="colKey != 'meta'" :key="colKey">
|
|
65
|
-
<div
|
|
65
|
+
<div v-if="colKey==='model_name'">Model</div>
|
|
66
|
+
<div v-else>{{ colKey }}</div>
|
|
66
67
|
</th>
|
|
67
68
|
</template>
|
|
68
69
|
</tr>
|
|
@@ -17,7 +17,8 @@ from supervisely.geometry.polygon import Polygon
|
|
|
17
17
|
from supervisely.geometry.polyline import Polyline
|
|
18
18
|
from supervisely.geometry.rectangle import Rectangle
|
|
19
19
|
from supervisely.imaging.color import generate_rgb
|
|
20
|
-
from supervisely.io.fs import get_file_name_with_ext, touch
|
|
20
|
+
from supervisely.io.fs import get_file_name, get_file_name_with_ext, touch
|
|
21
|
+
from supervisely.nn.task_type import TaskType
|
|
21
22
|
from supervisely.project.project import Dataset, OpenMode, Project
|
|
22
23
|
from supervisely.project.project_meta import ProjectMeta
|
|
23
24
|
from supervisely.sly_logger import logger
|
|
@@ -27,6 +28,20 @@ YOLO_DETECTION_COORDS_NUM = 4
|
|
|
27
28
|
YOLO_SEGM_MIN_COORDS_NUM = 6
|
|
28
29
|
YOLO_KEYPOINTS_MIN_COORDS_NUM = 6
|
|
29
30
|
|
|
31
|
+
|
|
32
|
+
class YOLOTaskType:
|
|
33
|
+
DETECT = "detect"
|
|
34
|
+
SEGMENT = "segment"
|
|
35
|
+
POSE = "pose"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
SLY_YOLO_TASK_TYPE_MAP = {
|
|
39
|
+
TaskType.OBJECT_DETECTION: YOLOTaskType.DETECT,
|
|
40
|
+
TaskType.INSTANCE_SEGMENTATION: YOLOTaskType.SEGMENT,
|
|
41
|
+
TaskType.POSE_ESTIMATION: YOLOTaskType.POSE,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
30
45
|
coco_classes = [
|
|
31
46
|
"person",
|
|
32
47
|
"bicycle",
|
|
@@ -398,22 +413,22 @@ def keypoints_to_yolo_line(
|
|
|
398
413
|
|
|
399
414
|
def convert_label_geometry_if_needed(
|
|
400
415
|
label: Label,
|
|
401
|
-
task_type: Literal["
|
|
416
|
+
task_type: Literal["detect", "segment", "pose"],
|
|
402
417
|
verbose: bool = False,
|
|
403
418
|
) -> List[Label]:
|
|
404
|
-
if task_type ==
|
|
419
|
+
if task_type == YOLOTaskType.DETECT:
|
|
405
420
|
available_geometry_type = Rectangle
|
|
406
421
|
convertable_geometry_types = [Polygon, GraphNodes, Bitmap, Polyline, AlphaMask, AnyGeometry]
|
|
407
|
-
elif task_type ==
|
|
422
|
+
elif task_type == YOLOTaskType.SEGMENT:
|
|
408
423
|
available_geometry_type = Polygon
|
|
409
424
|
convertable_geometry_types = [Bitmap, AlphaMask, AnyGeometry]
|
|
410
|
-
elif task_type ==
|
|
425
|
+
elif task_type == YOLOTaskType.POSE:
|
|
411
426
|
available_geometry_type = GraphNodes
|
|
412
427
|
convertable_geometry_types = []
|
|
413
428
|
else:
|
|
414
429
|
raise ValueError(
|
|
415
430
|
f"Unsupported task type: {task_type}. "
|
|
416
|
-
"Supported types: '
|
|
431
|
+
f"Supported types: '{YOLOTaskType.DETECT}', '{YOLOTaskType.SEGMENT}', '{YOLOTaskType.POSE}'"
|
|
417
432
|
)
|
|
418
433
|
|
|
419
434
|
if label.obj_class.geometry_type == available_geometry_type:
|
|
@@ -438,7 +453,7 @@ def label_to_yolo_lines(
|
|
|
438
453
|
img_height: int,
|
|
439
454
|
img_width: int,
|
|
440
455
|
class_names: List[str],
|
|
441
|
-
task_type: Literal["
|
|
456
|
+
task_type: Literal["detect", "segment", "pose"],
|
|
442
457
|
) -> List[str]:
|
|
443
458
|
"""
|
|
444
459
|
Convert the Supervisely Label to a line in the YOLO format.
|
|
@@ -449,21 +464,21 @@ def label_to_yolo_lines(
|
|
|
449
464
|
|
|
450
465
|
lines = []
|
|
451
466
|
for label in labels:
|
|
452
|
-
if task_type ==
|
|
467
|
+
if task_type == YOLOTaskType.DETECT:
|
|
453
468
|
yolo_line = rectangle_to_yolo_line(
|
|
454
469
|
class_idx=class_idx,
|
|
455
470
|
geometry=label.geometry,
|
|
456
471
|
img_height=img_height,
|
|
457
472
|
img_width=img_width,
|
|
458
473
|
)
|
|
459
|
-
elif task_type ==
|
|
474
|
+
elif task_type == YOLOTaskType.SEGMENT:
|
|
460
475
|
yolo_line = polygon_to_yolo_line(
|
|
461
476
|
class_idx=class_idx,
|
|
462
477
|
geometry=label.geometry,
|
|
463
478
|
img_height=img_height,
|
|
464
479
|
img_width=img_width,
|
|
465
480
|
)
|
|
466
|
-
elif task_type ==
|
|
481
|
+
elif task_type == YOLOTaskType.POSE:
|
|
467
482
|
nodes_field = label.obj_class.geometry_type.items_json_field
|
|
468
483
|
max_kpts_count = len(label.obj_class.geometry_config[nodes_field])
|
|
469
484
|
yolo_line = keypoints_to_yolo_line(
|
|
@@ -474,7 +489,10 @@ def label_to_yolo_lines(
|
|
|
474
489
|
max_kpts_count=max_kpts_count,
|
|
475
490
|
)
|
|
476
491
|
else:
|
|
477
|
-
raise ValueError(
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Unsupported task type: {task_type}. "
|
|
494
|
+
f"Supported types: '{YOLOTaskType.DETECT}', '{YOLOTaskType.SEGMENT}', '{YOLOTaskType.POSE}'"
|
|
495
|
+
)
|
|
478
496
|
|
|
479
497
|
if yolo_line is not None:
|
|
480
498
|
lines.append(yolo_line)
|
|
@@ -485,12 +503,11 @@ def label_to_yolo_lines(
|
|
|
485
503
|
def sly_ann_to_yolo(
|
|
486
504
|
ann: Annotation,
|
|
487
505
|
class_names: List[str],
|
|
488
|
-
task_type: Literal["
|
|
506
|
+
task_type: Literal["detect", "segment", "pose"] = "detect",
|
|
489
507
|
) -> List[str]:
|
|
490
508
|
"""
|
|
491
509
|
Convert the Supervisely annotation to the YOLO format.
|
|
492
510
|
"""
|
|
493
|
-
|
|
494
511
|
h, w = ann.img_size
|
|
495
512
|
yolo_lines = []
|
|
496
513
|
for label in ann.labels:
|
|
@@ -509,11 +526,12 @@ def sly_ds_to_yolo(
|
|
|
509
526
|
dataset: Dataset,
|
|
510
527
|
meta: ProjectMeta,
|
|
511
528
|
dest_dir: Optional[str] = None,
|
|
512
|
-
task_type: Literal["
|
|
529
|
+
task_type: Literal["detect", "segment", "pose"] = "detect",
|
|
513
530
|
log_progress: bool = False,
|
|
514
531
|
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
532
|
+
is_val: Optional[bool] = None,
|
|
515
533
|
) -> str:
|
|
516
|
-
|
|
534
|
+
task_type = validate_task_type(task_type)
|
|
517
535
|
if progress_cb is not None:
|
|
518
536
|
log_progress = False
|
|
519
537
|
|
|
@@ -543,15 +561,19 @@ def sly_ds_to_yolo(
|
|
|
543
561
|
ann_path = dataset.get_ann_path(name)
|
|
544
562
|
ann = Annotation.load_json_file(ann_path, meta)
|
|
545
563
|
|
|
546
|
-
|
|
547
|
-
|
|
564
|
+
if is_val is not None:
|
|
565
|
+
images_dir = val_images_dir if is_val else train_images_dir
|
|
566
|
+
labels_dir = val_labels_dir if is_val else train_labels_dir
|
|
567
|
+
else:
|
|
568
|
+
images_dir = val_images_dir if ann.img_tags.get("val") else train_images_dir
|
|
569
|
+
labels_dir = val_labels_dir if ann.img_tags.get("val") else train_labels_dir
|
|
548
570
|
|
|
549
571
|
img_path = Path(dataset.get_img_path(name))
|
|
550
572
|
img_name = f"{dataset.short_name}_{get_file_name_with_ext(img_path)}"
|
|
551
573
|
img_name = generate_free_name(used_names, img_name, with_ext=True, extend_used_names=True)
|
|
552
574
|
shutil.copy2(img_path, images_dir / img_name)
|
|
553
575
|
|
|
554
|
-
label_path = str(labels_dir / f"{img_name}.txt")
|
|
576
|
+
label_path = str(labels_dir / f"{get_file_name(img_name)}.txt")
|
|
555
577
|
yolo_lines = ann.to_yolo(class_names, task_type)
|
|
556
578
|
if len(yolo_lines) > 0:
|
|
557
579
|
with open(label_path, "w") as f:
|
|
@@ -565,7 +587,8 @@ def sly_ds_to_yolo(
|
|
|
565
587
|
# * save data config file if it does not exist
|
|
566
588
|
config_path = dest_dir / "data_config.yaml"
|
|
567
589
|
if not config_path.exists():
|
|
568
|
-
|
|
590
|
+
with_keypoint = task_type is YOLOTaskType.POSE
|
|
591
|
+
save_yolo_config(meta, dest_dir, with_keypoint=with_keypoint)
|
|
569
592
|
|
|
570
593
|
return str(dest_dir)
|
|
571
594
|
|
|
@@ -578,6 +601,8 @@ def save_yolo_config(meta: ProjectMeta, dest_dir: str, with_keypoint: bool = Fal
|
|
|
578
601
|
data_yaml = {
|
|
579
602
|
"train": f"../{str(dest_dir.name)}/images/train",
|
|
580
603
|
"val": f"../{str(dest_dir.name)}/images/val",
|
|
604
|
+
"train_labels": f"../{str(dest_dir.name)}/labels/train",
|
|
605
|
+
"val_labels": f"../{str(dest_dir.name)}/labels/val",
|
|
581
606
|
"nc": len(class_names),
|
|
582
607
|
"names": class_names,
|
|
583
608
|
"colors": class_colors,
|
|
@@ -590,6 +615,7 @@ def save_yolo_config(meta: ProjectMeta, dest_dir: str, with_keypoint: bool = Fal
|
|
|
590
615
|
field_name = obj_class.geometry_type.items_json_field
|
|
591
616
|
max_kpts_count = max(max_kpts_count, len(obj_class.geometry_config[field_name]))
|
|
592
617
|
data_yaml["kpt_shape"] = [max_kpts_count, 3]
|
|
618
|
+
data_yaml["flip_idx"] = [i for i in range(max_kpts_count)]
|
|
593
619
|
with open(save_path, "w") as f:
|
|
594
620
|
yaml.dump(data_yaml, f, default_flow_style=None)
|
|
595
621
|
|
|
@@ -599,21 +625,31 @@ def save_yolo_config(meta: ProjectMeta, dest_dir: str, with_keypoint: bool = Fal
|
|
|
599
625
|
def sly_project_to_yolo(
|
|
600
626
|
project: Union[Project, str],
|
|
601
627
|
dest_dir: Optional[str] = None,
|
|
602
|
-
task_type: Literal["
|
|
628
|
+
task_type: Literal["detect", "segment", "pose"] = "detect",
|
|
603
629
|
log_progress: bool = False,
|
|
604
630
|
progress_cb: Optional[Callable] = None,
|
|
631
|
+
val_datasets: Optional[List[str]] = None,
|
|
605
632
|
):
|
|
606
633
|
"""
|
|
607
634
|
Convert Supervisely project to YOLO format.
|
|
608
635
|
|
|
636
|
+
:param project: Supervisely project or path to the directory with the project.
|
|
637
|
+
:type project: :class:`supervisely.project.project.Project` or :class:`str`
|
|
609
638
|
:param dest_dir: Destination directory.
|
|
610
639
|
:type dest_dir: :class:`str`, optional
|
|
640
|
+
:param task_type: Task type.
|
|
641
|
+
:type task_type: :class:`str`, optional
|
|
611
642
|
:param log_progress: Show uploading progress bar.
|
|
612
643
|
:type log_progress: :class:`bool`
|
|
613
644
|
:param progress_cb: Function for tracking conversion progress (for all items in the project).
|
|
614
645
|
:type progress_cb: callable, optional
|
|
615
|
-
:
|
|
616
|
-
|
|
646
|
+
:param val_datasets: List of dataset names for validation.
|
|
647
|
+
Full dataset names are required (e.g., 'ds0/nested_ds1/ds3').
|
|
648
|
+
If specified, datasets from the list will be marked as val, others as train.
|
|
649
|
+
If not specified, the function will determine the validation datasets automatically.
|
|
650
|
+
:type val_datasets: :class:`list`, optional
|
|
651
|
+
:return: Path to the destination directory.
|
|
652
|
+
:rtype: :class:`str`
|
|
617
653
|
|
|
618
654
|
:Usage example:
|
|
619
655
|
|
|
@@ -627,6 +663,7 @@ def sly_project_to_yolo(
|
|
|
627
663
|
# Convert Project to YOLO format
|
|
628
664
|
sly.Project(project_directory).to_yolo(log_progress=True)
|
|
629
665
|
"""
|
|
666
|
+
task_type = validate_task_type(task_type)
|
|
630
667
|
if isinstance(project, str):
|
|
631
668
|
project = Project(project, mode=OpenMode.READ)
|
|
632
669
|
|
|
@@ -644,9 +681,15 @@ def sly_project_to_yolo(
|
|
|
644
681
|
desc="Converting Supervisely project to YOLO format", total=project.total_items
|
|
645
682
|
).update
|
|
646
683
|
|
|
647
|
-
|
|
684
|
+
with_keypoint = task_type is YOLOTaskType.POSE
|
|
685
|
+
save_yolo_config(project.meta, dest_dir, with_keypoint=with_keypoint)
|
|
648
686
|
|
|
649
687
|
for dataset in project.datasets:
|
|
688
|
+
if val_datasets is not None:
|
|
689
|
+
is_val = dataset.name in val_datasets
|
|
690
|
+
else:
|
|
691
|
+
is_val = None
|
|
692
|
+
|
|
650
693
|
dataset: Dataset
|
|
651
694
|
dataset.to_yolo(
|
|
652
695
|
meta=project.meta,
|
|
@@ -654,18 +697,23 @@ def sly_project_to_yolo(
|
|
|
654
697
|
task_type=task_type,
|
|
655
698
|
log_progress=log_progress,
|
|
656
699
|
progress_cb=progress_cb,
|
|
700
|
+
is_val=is_val,
|
|
657
701
|
)
|
|
658
702
|
logger.info(f"Dataset '{dataset.short_name}' has been converted to YOLO format.")
|
|
659
703
|
logger.info(f"Project '{project.name}' has been converted to YOLO format.")
|
|
660
704
|
|
|
705
|
+
return str(dest_dir)
|
|
706
|
+
|
|
661
707
|
|
|
662
708
|
def to_yolo(
|
|
663
709
|
input_data: Union[Project, Dataset, str],
|
|
664
710
|
dest_dir: Optional[str] = None,
|
|
665
|
-
task_type: Literal["
|
|
711
|
+
task_type: Literal["detect", "segment", "pose"] = "detect",
|
|
666
712
|
meta: Optional[ProjectMeta] = None,
|
|
667
713
|
log_progress: bool = True,
|
|
668
714
|
progress_cb: Optional[Callable] = None,
|
|
715
|
+
val_datasets: Optional[List[str]] = None,
|
|
716
|
+
is_val: Optional[bool] = None,
|
|
669
717
|
) -> Union[None, str]:
|
|
670
718
|
"""
|
|
671
719
|
Universal function to convert Supervisely project or dataset to YOLO format.
|
|
@@ -691,6 +739,13 @@ def to_yolo(
|
|
|
691
739
|
:type log_progress: :class:`bool`
|
|
692
740
|
:param progress_cb: Function for tracking conversion progress (for all items in the project).
|
|
693
741
|
:type progress_cb: callable, optional
|
|
742
|
+
:param val_datasets: List of dataset names for validation.
|
|
743
|
+
Full dataset names are required (e.g., 'ds0/nested_ds1/ds3').
|
|
744
|
+
If specified, datasets from the list will be marked as val, others as train.
|
|
745
|
+
If not specified, the function will determine the validation datasets automatically.
|
|
746
|
+
:type val_datasets: :class:`list`, optional
|
|
747
|
+
:param is_val: Whether the dataset is for validation.
|
|
748
|
+
:type is_val: :class:`bool`, optional
|
|
694
749
|
:return: None, list of YOLO lines, or path to the destination directory.
|
|
695
750
|
:rtype: NoneType, list, str
|
|
696
751
|
|
|
@@ -711,7 +766,7 @@ def to_yolo(
|
|
|
711
766
|
|
|
712
767
|
# Convert Dataset to YOLO format
|
|
713
768
|
dataset: sly.Dataset = project_fs.datasets.get("dataset_name")
|
|
714
|
-
sly.convert.to_yolo(dataset, dest_dir="./yolo", meta=project_fs.meta)
|
|
769
|
+
sly.convert.to_yolo(dataset, dest_dir="./yolo", meta=project_fs.meta, is_val=True)
|
|
715
770
|
"""
|
|
716
771
|
if isinstance(input_data, str):
|
|
717
772
|
try:
|
|
@@ -728,6 +783,7 @@ def to_yolo(
|
|
|
728
783
|
task_type=task_type,
|
|
729
784
|
log_progress=log_progress,
|
|
730
785
|
progress_cb=progress_cb,
|
|
786
|
+
val_datasets=val_datasets,
|
|
731
787
|
)
|
|
732
788
|
elif isinstance(input_data, Dataset):
|
|
733
789
|
return sly_ds_to_yolo(
|
|
@@ -737,6 +793,20 @@ def to_yolo(
|
|
|
737
793
|
task_type=task_type,
|
|
738
794
|
log_progress=log_progress,
|
|
739
795
|
progress_cb=progress_cb,
|
|
796
|
+
is_val=is_val,
|
|
740
797
|
)
|
|
741
798
|
else:
|
|
742
799
|
raise ValueError("Unsupported input type. Only Project or Dataset are supported.")
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def validate_task_type(task_type: Literal["detect", "segment", "pose"]) -> str:
|
|
803
|
+
if task_type not in [YOLOTaskType.DETECT, YOLOTaskType.SEGMENT, YOLOTaskType.POSE]:
|
|
804
|
+
task_type = SLY_YOLO_TASK_TYPE_MAP.get(task_type)
|
|
805
|
+
if task_type is None:
|
|
806
|
+
raise ValueError(
|
|
807
|
+
f"Unsupported task type: {task_type}. "
|
|
808
|
+
f"Supported types: '{YOLOTaskType.DETECT}', '{SLY_YOLO_TASK_TYPE_MAP[TaskType.OBJECT_DETECTION]}', "
|
|
809
|
+
f"'{YOLOTaskType.SEGMENT}', '{SLY_YOLO_TASK_TYPE_MAP[TaskType.INSTANCE_SEGMENTATION]}', "
|
|
810
|
+
f"'{YOLOTaskType.POSE}', '{SLY_YOLO_TASK_TYPE_MAP[TaskType.POSE_ESTIMATION]}'"
|
|
811
|
+
)
|
|
812
|
+
return task_type
|
|
@@ -8,7 +8,7 @@ from supervisely.convert.volume.nii import nii_volume_helper as helper
|
|
|
8
8
|
from supervisely.convert.volume.nii.nii_volume_converter import NiiConverter
|
|
9
9
|
from supervisely.convert.volume.volume_converter import VolumeConverter
|
|
10
10
|
from supervisely.geometry.mask_3d import Mask3D
|
|
11
|
-
from supervisely.io.fs import get_file_name
|
|
11
|
+
from supervisely.io.fs import get_file_ext, get_file_name
|
|
12
12
|
from supervisely.volume.volume import is_nifti_file
|
|
13
13
|
from supervisely.volume_annotation.volume_annotation import VolumeAnnotation
|
|
14
14
|
from supervisely.volume_annotation.volume_object import VolumeObject
|
|
@@ -34,14 +34,46 @@ class NiiPlaneStructuredConverter(NiiConverter, VolumeConverter):
|
|
|
34
34
|
├── 🩻 sag_inference_1.nii class 1
|
|
35
35
|
├── 🩻 sag_inference_2.nii class 2
|
|
36
36
|
└── 🩻 sag_inference_3.nii class 3
|
|
37
|
+
|
|
38
|
+
Additionally, if a TXT file with class color map is present, it will be used to
|
|
39
|
+
create the classes with names and colors corresponding to the pixel values in the NIfTI files.
|
|
40
|
+
The TXT file should be structured as follows:
|
|
41
|
+
|
|
42
|
+
```txt
|
|
43
|
+
1 Femur 255 0 0
|
|
44
|
+
2 Femoral cartilage 0 255 0
|
|
45
|
+
3 Tibia 0 0 255
|
|
46
|
+
4 Tibia cartilage 255 255 0
|
|
47
|
+
5 Patella 0 255 255
|
|
48
|
+
6 Patellar cartilage 255 0 255
|
|
49
|
+
7 Miniscus 175 175 175
|
|
50
|
+
```
|
|
51
|
+
where 1, 2, ... are the pixel values in the NIfTI files
|
|
52
|
+
Femur, Femoral cartilage, ... are the names of the classes
|
|
53
|
+
255, 0, 0, ... are the RGB colors of the classes
|
|
54
|
+
The class name will be used to create the corresponding ObjClass in Supervisely.
|
|
37
55
|
"""
|
|
38
56
|
|
|
57
|
+
class Item(VolumeConverter.BaseItem):
|
|
58
|
+
def __init__(self, *args, **kwargs):
|
|
59
|
+
super().__init__(*args, **kwargs)
|
|
60
|
+
self._is_semantic = False
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def is_semantic(self) -> bool:
|
|
64
|
+
return self._is_semantic
|
|
65
|
+
|
|
66
|
+
@is_semantic.setter
|
|
67
|
+
def is_semantic(self, value: bool):
|
|
68
|
+
self._is_semantic = value
|
|
69
|
+
|
|
39
70
|
def validate_format(self) -> bool:
|
|
40
71
|
# create Items
|
|
41
72
|
converted_dir_name = "converted"
|
|
42
73
|
|
|
43
74
|
volumes_dict = defaultdict(list)
|
|
44
75
|
ann_dict = defaultdict(list)
|
|
76
|
+
cls_color_map = None
|
|
45
77
|
|
|
46
78
|
for root, _, files in os.walk(self._input_data):
|
|
47
79
|
if converted_dir_name in root:
|
|
@@ -56,17 +88,24 @@ class NiiPlaneStructuredConverter(NiiConverter, VolumeConverter):
|
|
|
56
88
|
if prefix not in helper.PlanePrefix.values():
|
|
57
89
|
continue
|
|
58
90
|
name = full_name.split("_")[1]
|
|
59
|
-
idx = 1 if len(name.split("_")) < 3 else int(name.split("_")[2])
|
|
60
91
|
if name in helper.LABEL_NAME or name[:-1] in helper.LABEL_NAME:
|
|
61
92
|
ann_dict[prefix].append(path)
|
|
62
93
|
else:
|
|
63
94
|
volumes_dict[prefix].append(path)
|
|
95
|
+
ext = get_file_ext(path)
|
|
96
|
+
if ext == ".txt":
|
|
97
|
+
cls_color_map = helper.read_cls_color_map(path)
|
|
98
|
+
if cls_color_map is None:
|
|
99
|
+
logger.warning(f"Failed to read class color map from {path}.")
|
|
64
100
|
|
|
65
101
|
self._items = []
|
|
66
102
|
for prefix, paths in volumes_dict.items():
|
|
67
103
|
if len(paths) == 1:
|
|
68
104
|
item = self.Item(item_path=paths[0])
|
|
69
|
-
item.ann_data = ann_dict.get(prefix)
|
|
105
|
+
item.ann_data = ann_dict.get(prefix, [])
|
|
106
|
+
item.is_semantic = len(item.ann_data) == 1
|
|
107
|
+
if cls_color_map is not None:
|
|
108
|
+
item.custom_data["cls_color_map"] = cls_color_map
|
|
70
109
|
self._items.append(item)
|
|
71
110
|
elif len(paths) > 1:
|
|
72
111
|
logger.info(
|
|
@@ -79,6 +118,9 @@ class NiiPlaneStructuredConverter(NiiConverter, VolumeConverter):
|
|
|
79
118
|
if Path(ann_path).parent == Path(path).parent:
|
|
80
119
|
possible_ann_paths.append(ann_path)
|
|
81
120
|
item.ann_data = possible_ann_paths
|
|
121
|
+
item.is_semantic = len(possible_ann_paths) == 1
|
|
122
|
+
if cls_color_map is not None:
|
|
123
|
+
item.custom_data["cls_color_map"] = cls_color_map
|
|
82
124
|
self._items.append(item)
|
|
83
125
|
self._meta = ProjectMeta()
|
|
84
126
|
return self.items_count > 0
|
|
@@ -96,12 +138,18 @@ class NiiPlaneStructuredConverter(NiiConverter, VolumeConverter):
|
|
|
96
138
|
objs = []
|
|
97
139
|
spatial_figures = []
|
|
98
140
|
for idx, ann_path in enumerate(item.ann_data, start=1):
|
|
99
|
-
for mask,
|
|
100
|
-
|
|
141
|
+
for mask, pixel_id in helper.get_annotation_from_nii(ann_path):
|
|
142
|
+
class_id = pixel_id if item.is_semantic else idx
|
|
143
|
+
class_name = f"Segment_{class_id}"
|
|
144
|
+
color = None
|
|
145
|
+
if item.custom_data.get("cls_color_map") is not None:
|
|
146
|
+
class_info = item.custom_data["cls_color_map"].get(class_id)
|
|
147
|
+
if class_info is not None:
|
|
148
|
+
class_name, color = class_info
|
|
101
149
|
class_name = renamed_classes.get(class_name, class_name)
|
|
102
150
|
obj_class = meta.get_obj_class(class_name)
|
|
103
151
|
if obj_class is None:
|
|
104
|
-
obj_class = ObjClass(class_name, Mask3D)
|
|
152
|
+
obj_class = ObjClass(class_name, Mask3D, color)
|
|
105
153
|
meta = meta.add_obj_class(obj_class)
|
|
106
154
|
self._meta_changed = True
|
|
107
155
|
self._meta = meta
|
|
@@ -169,11 +169,11 @@ class NiiConverter(VolumeConverter):
|
|
|
169
169
|
item_paths = []
|
|
170
170
|
|
|
171
171
|
for item in batch:
|
|
172
|
-
if self._upload_as_links:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
# if self._upload_as_links:
|
|
173
|
+
# remote_path = self.remote_files_map.get(item.path)
|
|
174
|
+
# if remote_path is not None:
|
|
175
|
+
# item.custom_data = {"remote_path": remote_path}
|
|
176
|
+
|
|
177
177
|
item.path = helper.nifti_to_nrrd(item.path, converted_dir)
|
|
178
178
|
ext = get_file_ext(item.path)
|
|
179
179
|
if ext.lower() != ext:
|
|
@@ -195,8 +195,8 @@ class NiiConverter(VolumeConverter):
|
|
|
195
195
|
leave=True if progress_cb is None else False,
|
|
196
196
|
position=1,
|
|
197
197
|
)
|
|
198
|
-
if item.custom_data is not None:
|
|
199
|
-
|
|
198
|
+
# if item.custom_data is not None:
|
|
199
|
+
# volume_meta.update(item.custom_data)
|
|
200
200
|
api.volume.upload_np(dataset_id, item.name, volume_np, volume_meta, progress_nrrd)
|
|
201
201
|
info = api.volume.get_info_by_name(dataset_id, item.name)
|
|
202
202
|
item.volume_meta = info.meta
|
|
@@ -7,6 +7,7 @@ import numpy as np
|
|
|
7
7
|
from supervisely.collection.str_enum import StrEnum
|
|
8
8
|
from supervisely.geometry.mask_3d import Mask3D
|
|
9
9
|
from supervisely.io.fs import ensure_base_path, get_file_ext, get_file_name
|
|
10
|
+
from supervisely.sly_logger import logger
|
|
10
11
|
from supervisely.volume.volume import convert_3d_nifti_to_nrrd
|
|
11
12
|
|
|
12
13
|
VOLUME_NAME = "anatomic"
|
|
@@ -21,6 +22,54 @@ class PlanePrefix(str, StrEnum):
|
|
|
21
22
|
AXIAL = "axl"
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def read_cls_color_map(path: str) -> dict:
|
|
26
|
+
"""Read class color map from TXT file.
|
|
27
|
+
|
|
28
|
+
```txt
|
|
29
|
+
1 Femur 255 0 0
|
|
30
|
+
2 Femoral cartilage 0 255 0
|
|
31
|
+
3 Tibia 0 0 255
|
|
32
|
+
4 Tibia cartilage 255 255 0
|
|
33
|
+
5 Patella 0 255 255
|
|
34
|
+
6 Patellar cartilage 255 0 255
|
|
35
|
+
7 Miniscus 175 175 175
|
|
36
|
+
```
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
cls_color_map = {}
|
|
40
|
+
if not os.path.exists(path):
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
with open(path, "r") as file:
|
|
44
|
+
for line in file:
|
|
45
|
+
parts = line.strip().split()
|
|
46
|
+
color = None
|
|
47
|
+
try:
|
|
48
|
+
color = list(map(int, parts[-3:]))
|
|
49
|
+
except:
|
|
50
|
+
pass
|
|
51
|
+
cls_id = int(parts[0])
|
|
52
|
+
if not color:
|
|
53
|
+
cls_name = " ".join(parts[1:])
|
|
54
|
+
else:
|
|
55
|
+
cls_name = " ".join(parts[1:-3])
|
|
56
|
+
if cls_id in cls_color_map:
|
|
57
|
+
logger.warning(f"Duplicate class ID {cls_id} found in color map.")
|
|
58
|
+
if cls_name in cls_color_map:
|
|
59
|
+
logger.warning(f"Duplicate class name {cls_name} found in color map.")
|
|
60
|
+
if len(color) != 3:
|
|
61
|
+
logger.warning(f"Invalid color format for class {cls_name}. Expected 3 values.")
|
|
62
|
+
if any(c < 0 or c > 255 for c in color):
|
|
63
|
+
logger.warning(
|
|
64
|
+
f"Invalid color value for class {cls_name}. Expected values between 0 and 255."
|
|
65
|
+
)
|
|
66
|
+
cls_color_map[cls_id] = (cls_name, color)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.warning(f"Failed to read class color map from {path}: {e}")
|
|
69
|
+
return None
|
|
70
|
+
return cls_color_map
|
|
71
|
+
|
|
72
|
+
|
|
24
73
|
def nifti_to_nrrd(nii_file_path: str, converted_dir: str) -> str:
|
|
25
74
|
"""Convert NIfTI 3D volume file to NRRD 3D volume file."""
|
|
26
75
|
|
|
@@ -23,7 +23,7 @@ from supervisely.app.widgets.pretrained_models_selector.pretrained_models_select
|
|
|
23
23
|
)
|
|
24
24
|
from supervisely.nn.experiments import get_experiment_infos
|
|
25
25
|
from supervisely.nn.inference.gui.serving_gui import ServingGUI
|
|
26
|
-
from supervisely.nn.utils import ModelSource, RuntimeType
|
|
26
|
+
from supervisely.nn.utils import ModelSource, RuntimeType, _get_model_name
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class ServingGUITemplate(ServingGUI):
|
|
@@ -149,8 +149,7 @@ class ServingGUITemplate(ServingGUI):
|
|
|
149
149
|
@property
|
|
150
150
|
def model_name(self) -> Optional[str]:
|
|
151
151
|
if self.model_source == ModelSource.PRETRAINED:
|
|
152
|
-
|
|
153
|
-
return model_meta.get("model_name")
|
|
152
|
+
return _get_model_name(self.model_info)
|
|
154
153
|
else:
|
|
155
154
|
return self.model_info.get("model_name")
|
|
156
155
|
|