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.
@@ -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, _ in helper.get_annotation_from_nii(ann_path):
100
- class_name = f"Segment_{idx}"
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
- 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
- # nii_path = item.path
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
- volume_meta.update(item.custom_data)
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
 
@@ -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
- import nibabel as nib # pylint: disable=import-error
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
- "space": orientation_map.get(orientation, "unknown"),
837
- "space directions": space_directions,
838
- "space origin": space_origin,
839
- "sizes": data.shape,
840
- "type": str(data.dtype),
841
- "dimension": len(data.shape),
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: supervisely
3
- Version: 6.73.325
3
+ Version: 6.73.326
4
4
  Summary: Supervisely Python SDK.
5
5
  Home-page: https://github.com/supervisely/supervisely
6
6
  Author: Supervisely
@@ -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=SV1OAoel92qOvWTfcVqzee9LQypAQGQ-hJEMJ9j7nLg,4995
668
- supervisely/convert/volume/nii/nii_volume_converter.py,sha256=nVHKK7wVhMvBiX1awMoXOwt2f5MHujqVmof0v_zW5E4,8502
669
- supervisely/convert/volume/nii/nii_volume_helper.py,sha256=wy8GqjgnFJaEgKKSOgKw_QPETdiYYY5c9BBfyJE3Hqo,1417
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=7ebCIICqZMwRP3ruRy3PtSeiUpBpSlyRH20VJybBQbI,25828
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.325.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1086
- supervisely-6.73.325.dist-info/METADATA,sha256=wRj0QjkNc2OGdL0AtTslp2JAlgDO8-1f3XeeUfoZfOY,33596
1087
- supervisely-6.73.325.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
1088
- supervisely-6.73.325.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1089
- supervisely-6.73.325.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1090
- supervisely-6.73.325.dist-info/RECORD,,
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,,