supervisely 6.73.325__py3-none-any.whl → 6.73.326__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/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/volume/volume.py +19 -21
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/METADATA +1 -1
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/RECORD +10 -10
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/LICENSE +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/WHEEL +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.325.dist-info → supervisely-6.73.326.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
supervisely/volume/volume.py
CHANGED
|
@@ -816,30 +816,28 @@ def convert_3d_nifti_to_nrrd(path: str) -> Tuple[np.ndarray, dict]:
|
|
|
816
816
|
path = "/home/admin/work/volumes/vol_01.nii"
|
|
817
817
|
data, header = sly.volume.convert_nifti_to_nrrd(path)
|
|
818
818
|
"""
|
|
819
|
+
import SimpleITK as sitk
|
|
819
820
|
|
|
820
|
-
|
|
821
|
+
nifti_image = sitk.ReadImage(path)
|
|
822
|
+
nifti_image = _sitk_image_orient_ras(nifti_image)
|
|
823
|
+
data = sitk.GetArrayFromImage(nifti_image)
|
|
824
|
+
data = np.transpose(data, (2, 1, 0))
|
|
825
|
+
|
|
826
|
+
direction = np.array(nifti_image.GetDirection()).reshape(3, 3)
|
|
827
|
+
spacing = np.array(nifti_image.GetSpacing())
|
|
828
|
+
origin = np.array(nifti_image.GetOrigin())
|
|
829
|
+
|
|
830
|
+
space_directions = (direction.T * spacing[:, None]).tolist()
|
|
821
831
|
|
|
822
|
-
orientation_map = {
|
|
823
|
-
('R', 'A', 'S'): "right-anterior-superior",
|
|
824
|
-
('L', 'P', 'S'): "left-posterior-superior",
|
|
825
|
-
('R', 'P', 'I'): "right-posterior-inferior",
|
|
826
|
-
('L', 'A', 'I'): "left-anterior-inferior"
|
|
827
|
-
}
|
|
828
|
-
nifti = nib.load(path)
|
|
829
|
-
reordered_to_ras_nifti = nib.as_closest_canonical(nifti)
|
|
830
|
-
data = reordered_to_ras_nifti.get_fdata()
|
|
831
|
-
affine = reordered_to_ras_nifti.affine
|
|
832
|
-
orientation = nib.aff2axcodes(affine)
|
|
833
|
-
space_directions = affine[:3, :3].tolist()
|
|
834
|
-
space_origin = affine[:3, 3].tolist()
|
|
835
832
|
header = {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
833
|
+
"dimension": 3,
|
|
834
|
+
"space": "right-anterior-superior",
|
|
835
|
+
"sizes": list(data.shape),
|
|
836
|
+
"space directions": space_directions,
|
|
837
|
+
"endian": "little",
|
|
838
|
+
"encoding": "gzip",
|
|
839
|
+
"space origin": origin
|
|
840
|
+
}
|
|
843
841
|
return data, header
|
|
844
842
|
|
|
845
843
|
|
|
@@ -664,9 +664,9 @@ supervisely/convert/volume/dicom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
664
664
|
supervisely/convert/volume/dicom/dicom_converter.py,sha256=__QP8fMAaq_BdWFYh1_nAYT2gpY1WwZzdlDj39YwHhw,3195
|
|
665
665
|
supervisely/convert/volume/dicom/dicom_helper.py,sha256=1EXmxl5Z8Xi3ZkZnfJ4EbiPCVyITSXUc0Cn_oo02pPE,1284
|
|
666
666
|
supervisely/convert/volume/nii/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
667
|
-
supervisely/convert/volume/nii/nii_planes_volume_converter.py,sha256=
|
|
668
|
-
supervisely/convert/volume/nii/nii_volume_converter.py,sha256=
|
|
669
|
-
supervisely/convert/volume/nii/nii_volume_helper.py,sha256=
|
|
667
|
+
supervisely/convert/volume/nii/nii_planes_volume_converter.py,sha256=9TtN_AgCQgv16Olip6inFanCA5JlEEJ7JQf-0XjIw_Q,7091
|
|
668
|
+
supervisely/convert/volume/nii/nii_volume_converter.py,sha256=IZ6DJeLLbLAW-kifOJ_9ddV3h7gL3AswM2TTbXB9Os0,8476
|
|
669
|
+
supervisely/convert/volume/nii/nii_volume_helper.py,sha256=RvYab6Z530Qw-qTAsZ3WM8WZKqhijia9OC-g4_zOSEs,3142
|
|
670
670
|
supervisely/convert/volume/sly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
671
671
|
supervisely/convert/volume/sly/sly_volume_converter.py,sha256=XmSuxnRqxchG87b244f3h0UHvOt6IkajMquL1drWlCM,5595
|
|
672
672
|
supervisely/convert/volume/sly/sly_volume_helper.py,sha256=gUY0GW3zDMlO2y-zQQG36uoXMrKkKz4-ErM1CDxFCxE,5620
|
|
@@ -1060,7 +1060,7 @@ supervisely/volume/__init__.py,sha256=EBZBY_5mzabXzMUQh5akusIGd16XnX9n8J0jIi_JmW
|
|
|
1060
1060
|
supervisely/volume/nrrd_encoder.py,sha256=1lqwwyqxEvctw1ysQ70x4xPSV1uy1g5YcH5CURwL7-c,4084
|
|
1061
1061
|
supervisely/volume/nrrd_loader.py,sha256=_yqahKcqSRxunHZ5LtnUWIRA7UvIhPKOhAUwYijSGY4,9065
|
|
1062
1062
|
supervisely/volume/stl_converter.py,sha256=WIMQgHO_u4JT58QdcMXcb_euF1BFhM7D52IVX_0QTxE,6285
|
|
1063
|
-
supervisely/volume/volume.py,sha256=
|
|
1063
|
+
supervisely/volume/volume.py,sha256=bUPrDQAr4ZIkSQMzpSWXjsHRqcXUq2Z2H6Fe1uLdYmw,25687
|
|
1064
1064
|
supervisely/volume_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1065
1065
|
supervisely/volume_annotation/constants.py,sha256=BdFIh56fy7vzLIjt0gH8xP01EIU-qgQIwbSHVUcABCU,569
|
|
1066
1066
|
supervisely/volume_annotation/plane.py,sha256=wyezAcc8tLp38O44CwWY0wjdQxf3VjRdFLWooCrk-Nw,16301
|
|
@@ -1082,9 +1082,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1082
1082
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1083
1083
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1084
1084
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1085
|
-
supervisely-6.73.
|
|
1086
|
-
supervisely-6.73.
|
|
1087
|
-
supervisely-6.73.
|
|
1088
|
-
supervisely-6.73.
|
|
1089
|
-
supervisely-6.73.
|
|
1090
|
-
supervisely-6.73.
|
|
1085
|
+
supervisely-6.73.326.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1086
|
+
supervisely-6.73.326.dist-info/METADATA,sha256=0hQAKuBU9KX8mYNuop6LSvLzz0RQmCGZSdFIqqgmJUE,33596
|
|
1087
|
+
supervisely-6.73.326.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
1088
|
+
supervisely-6.73.326.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1089
|
+
supervisely-6.73.326.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1090
|
+
supervisely-6.73.326.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|