supervisely 6.73.322__py3-none-any.whl → 6.73.323__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.
@@ -404,7 +404,7 @@ class Annotation:
404
404
  f"Failed to deserialize one of the label from JSON format annotation: \n{repr(e)}"
405
405
  )
406
406
 
407
- custom_data = data.get(AnnotationJsonFields.CUSTOM_DATA, {})
407
+ custom_data = data.get(AnnotationJsonFields.CUSTOM_DATA, {}) or {}
408
408
  prob_labels = None
409
409
  if (
410
410
  AnnotationJsonFields.PROBABILITY_LABELS in custom_data
@@ -71,6 +71,7 @@ class AvailablePointcloudEpisodesConverters:
71
71
  class AvailableVolumeConverters:
72
72
  SLY = "supervisely"
73
73
  DICOM = "dicom"
74
+ NII = "nii"
74
75
 
75
76
 
76
77
  class BaseConverter:
@@ -1,3 +1,4 @@
1
1
  # Volume
2
2
  from supervisely.convert.volume.sly.sly_volume_converter import SLYVolumeConverter
3
3
  from supervisely.convert.volume.dicom.dicom_converter import DICOMConverter
4
+ from supervisely.convert.volume.nii.nii_volume_converter import NiiConverter
File without changes
@@ -0,0 +1,151 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ import magic
5
+
6
+ from supervisely import ProjectMeta, generate_free_name, logger
7
+ from supervisely._utils import batched, is_development
8
+ from supervisely.annotation.obj_class import ObjClass
9
+ from supervisely.annotation.obj_class_collection import ObjClassCollection
10
+ from supervisely.api.api import Api
11
+ from supervisely.convert.base_converter import AvailableVolumeConverters
12
+ from supervisely.convert.volume.nii import nii_volume_helper as helper
13
+ from supervisely.convert.volume.volume_converter import VolumeConverter
14
+ from supervisely.geometry.mask_3d import Mask3D
15
+ from supervisely.io.fs import (
16
+ get_file_ext,
17
+ get_file_name,
18
+ get_file_name_with_ext,
19
+ list_files,
20
+ )
21
+ from supervisely.volume.volume import is_nifti_file
22
+ from supervisely.volume_annotation.volume_annotation import VolumeAnnotation
23
+ from supervisely.volume_annotation.volume_object import VolumeObject
24
+
25
+
26
+ class NiiConverter(VolumeConverter):
27
+
28
+ def __str__(self) -> str:
29
+ return AvailableVolumeConverters.NII
30
+
31
+ def validate_format(self) -> bool:
32
+ # create Items
33
+ converted_dir_name = "converted"
34
+ # nrrds_dict = {}
35
+ nifti_dict = {}
36
+ nifti_dirs = {}
37
+ for root, _, files in os.walk(self._input_data):
38
+ dir_name = os.path.basename(root)
39
+ nifti_dirs[dir_name] = root
40
+ if converted_dir_name in root:
41
+ continue
42
+ for file in files:
43
+ path = os.path.join(root, file)
44
+ mime = magic.from_file(path, mime=True)
45
+ if mime == "application/gzip" or mime == "application/octet-stream":
46
+ if is_nifti_file(path): # is nifti
47
+ name = get_file_name(path)
48
+ if name.endswith(".nii"):
49
+ name = get_file_name(name)
50
+ nifti_dict[name] = path
51
+
52
+ self._items = []
53
+ skip_files = []
54
+ for name, nrrd_path in nifti_dict.items():
55
+ if name in nifti_dirs:
56
+ item = self.Item(item_path=nrrd_path)
57
+ ann_dir = nifti_dirs[name]
58
+ item.ann_data = list_files(ann_dir, [".nii", ".nii.gz", ".gz"], None, True)
59
+ self._items.append(item)
60
+ skip_files.extend(item.ann_data)
61
+ skip_files.append(nrrd_path)
62
+
63
+ for name, nrrd_path in nifti_dict.items():
64
+ if nrrd_path in skip_files:
65
+ continue
66
+ item = self.Item(item_path=nrrd_path)
67
+ self._items.append(item)
68
+
69
+ self._meta = ProjectMeta()
70
+ return self.items_count > 0
71
+
72
+ def upload_dataset(
73
+ self,
74
+ api: Api,
75
+ dataset_id: int,
76
+ batch_size: int = 1,
77
+ log_progress=True,
78
+ ):
79
+ """Upload converted data to Supervisely"""
80
+
81
+ meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
82
+
83
+ existing_names = set([vol.name for vol in api.volume.get_list(dataset_id)])
84
+
85
+ if log_progress:
86
+ progress, progress_cb = self.get_progress(
87
+ self.items_count, "Converting and uploading volumes..."
88
+ )
89
+ else:
90
+ progress_cb = None
91
+
92
+ converted_dir_name = "converted"
93
+ converted_dir = os.path.join(self._input_data, converted_dir_name)
94
+ meta_changed = False
95
+
96
+ for batch in batched(self._items, batch_size=batch_size):
97
+ item_names = []
98
+ item_paths = []
99
+
100
+ for item in batch:
101
+ # nii_path = item.path
102
+ item.path = helper.nifti_to_nrrd(item.path, converted_dir)
103
+ ext = get_file_ext(item.path)
104
+ if ext.lower() != ext:
105
+ new_volume_path = Path(item.path).with_suffix(ext.lower()).as_posix()
106
+ os.rename(item.path, new_volume_path)
107
+ item.path = new_volume_path
108
+ item.name = get_file_name_with_ext(item.path)
109
+ item.name = generate_free_name(
110
+ existing_names, item.name, with_ext=True, extend_used_names=True
111
+ )
112
+ item_names.append(item.name)
113
+ item_paths.append(item.path)
114
+
115
+ volume_info = api.volume.upload_nrrd_serie_path(
116
+ dataset_id, name=item.name, path=item.path
117
+ )
118
+
119
+ if isinstance(item.ann_data, list) and len(item.ann_data) > 0:
120
+ objs = []
121
+ spatial_figures = []
122
+ for ann_path in item.ann_data:
123
+ ann_name = get_file_name(ann_path)
124
+ if ann_name.endswith(".nii"):
125
+ ann_name = get_file_name(ann_name)
126
+ for mask, _ in helper.get_annotation_from_nii(ann_path):
127
+ obj_class = meta.get_obj_class(ann_name)
128
+ if obj_class is None:
129
+ obj_class = ObjClass(ann_name, Mask3D)
130
+ meta = meta.add_obj_class(obj_class)
131
+ meta_changed = True
132
+ obj = VolumeObject(obj_class, mask_3d=mask)
133
+ spatial_figures.append(obj.figure)
134
+ objs.append(obj)
135
+ ann = VolumeAnnotation(
136
+ volume_info.meta, objects=objs, spatial_figures=spatial_figures
137
+ )
138
+
139
+ if meta_changed:
140
+ self._meta = meta
141
+ _, _, _ = self.merge_metas_with_conflicts(api, dataset_id)
142
+
143
+ api.volume.annotation.append(volume_info.id, ann)
144
+
145
+ if log_progress:
146
+ progress_cb(len(batch))
147
+
148
+ if log_progress:
149
+ if is_development():
150
+ progress.close()
151
+ logger.info(f"Dataset ID:{dataset_id} has been successfully uploaded.")
@@ -0,0 +1,38 @@
1
+ import os
2
+ from typing import Generator
3
+
4
+ import nrrd
5
+ import numpy as np
6
+
7
+ from supervisely.geometry.mask_3d import Mask3D
8
+ from supervisely.io.fs import ensure_base_path, get_file_ext, get_file_name
9
+ from supervisely.volume.volume import convert_3d_nifti_to_nrrd
10
+
11
+
12
+ def nifti_to_nrrd(nii_file_path: str, converted_dir: str) -> str:
13
+ """Convert NIfTI 3D volume file to NRRD 3D volume file."""
14
+
15
+ output_name = get_file_name(nii_file_path)
16
+ if get_file_ext(output_name) == ".nii":
17
+ output_name = get_file_name(output_name)
18
+
19
+ data, header = convert_3d_nifti_to_nrrd(nii_file_path)
20
+
21
+ nrrd_file_path = os.path.join(converted_dir, f"{output_name}.nrrd")
22
+ ensure_base_path(nrrd_file_path)
23
+
24
+ nrrd.write(nrrd_file_path, data, header)
25
+ return nrrd_file_path
26
+
27
+
28
+ def get_annotation_from_nii(path: str) -> Generator[Mask3D, None, None]:
29
+ """Get annotation from NIfTI 3D volume file."""
30
+
31
+ data, _ = convert_3d_nifti_to_nrrd(path)
32
+ unique_classes = np.unique(data)
33
+
34
+ for class_id in unique_classes:
35
+ if class_id == 0:
36
+ continue
37
+ mask = Mask3D(data == class_id)
38
+ yield mask, class_id
@@ -799,6 +799,49 @@ def convert_nifti_to_nrrd(path: str) -> Tuple[np.ndarray, dict]:
799
799
  }
800
800
  return data, header
801
801
 
802
+ def convert_3d_nifti_to_nrrd(path: str) -> Tuple[np.ndarray, dict]:
803
+ """Convert 3D NIFTI volume to NRRD format.
804
+ Volume automatically reordered to RAS orientation as closest to canonical.
805
+
806
+ :param path: Path to NIFTI volume file.
807
+ :type path: str
808
+ :return: Volume data in NumPy array format and dictionary with metadata (NRRD header).
809
+ :rtype: Tuple[np.ndarray, dict]
810
+ :Usage example:
811
+
812
+ .. code-block:: python
813
+
814
+ import supervisely as sly
815
+
816
+ path = "/home/admin/work/volumes/vol_01.nii"
817
+ data, header = sly.volume.convert_nifti_to_nrrd(path)
818
+ """
819
+
820
+ import nibabel as nib # pylint: disable=import-error
821
+
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
+ 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
+ }
843
+ return data, header
844
+
802
845
 
803
846
  def is_nifti_file(path: str) -> bool:
804
847
  """Check if the file is a NIFTI file.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: supervisely
3
- Version: 6.73.322
3
+ Version: 6.73.323
4
4
  Summary: Supervisely Python SDK.
5
5
  Home-page: https://github.com/supervisely/supervisely
6
6
  Author: Supervisely
@@ -5,7 +5,7 @@ supervisely/function_wrapper.py,sha256=R5YajTQ0GnRp2vtjwfC9hINkzQc0JiyGsu8TER373
5
5
  supervisely/sly_logger.py,sha256=z92Vu5hmC0GgTIJO1n6kPDayRW9__8ix8hL6poDZj-Y,6274
6
6
  supervisely/tiny_timer.py,sha256=hkpe_7FE6bsKL79blSs7WBaktuPavEVu67IpEPrfmjE,183
7
7
  supervisely/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- supervisely/annotation/annotation.py,sha256=5AG1AhebkmiYy2r7nKbz6TjdmCF4tuf9FtqUjLLs7aU,114659
8
+ supervisely/annotation/annotation.py,sha256=x1RizD9DPiwk14Mf8xGvuwPdzx_zI5Zx1CVvmCy_sII,114665
9
9
  supervisely/annotation/annotation_transforms.py,sha256=TlVy_gUbM-XH6GbLpZPrAi6pMIGTr7Ow02iSKOSTa-I,9582
10
10
  supervisely/annotation/json_geometries_map.py,sha256=nL6AmMhFy02fw9ryBm75plKyOkDh61QdOToSuLAcz_Q,1659
11
11
  supervisely/annotation/label.py,sha256=NpHZ5o2H6dI4KiII22o2HpiLXG1yekh-bEy8WvI2Ljg,37498
@@ -565,7 +565,7 @@ supervisely/collection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
565
565
  supervisely/collection/key_indexed_collection.py,sha256=x2UVlkprspWhhae9oLUzjTWBoIouiWY9UQSS_MozfH0,37643
566
566
  supervisely/collection/str_enum.py,sha256=Zp29yFGvnxC6oJRYNNlXhO2lTSdsriU1wiGHj6ahEJE,1250
567
567
  supervisely/convert/__init__.py,sha256=ropgB1eebG2bfLoJyf2jp8Vv9UkFujaW3jVX-71ho1g,1353
568
- supervisely/convert/base_converter.py,sha256=rRMIxY3h7cX5WAu_qn7w9vzRBcDB_jLZm5u_XQh7QG4,18563
568
+ supervisely/convert/base_converter.py,sha256=O2SP4I_Hd0aSn8kbOUocy8orkc_-iD-TQ-z4ieUqabA,18579
569
569
  supervisely/convert/converter.py,sha256=tWxTDfFv7hwzQhUQrBxzfr6WP8FUGFX_ewg5T2HbUYo,8959
570
570
  supervisely/convert/image/__init__.py,sha256=JEuyaBiiyiYmEUYqdn8Mog5FVXpz0H1zFubKkOOm73I,1395
571
571
  supervisely/convert/image/image_converter.py,sha256=8vak8ZoKTN1ye2ZmCTvCZ605-Rw1AFLIEo7bJMfnR68,10426
@@ -658,11 +658,14 @@ supervisely/convert/video/mot/mot_converter.py,sha256=wXbv-9Psc2uVnhzHuOt5VnRIvS
658
658
  supervisely/convert/video/sly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
659
659
  supervisely/convert/video/sly/sly_video_converter.py,sha256=S2qif7JFxqIi9VN_ez_iBtoJXpG9W6Ky2k5Er3-DtUo,4418
660
660
  supervisely/convert/video/sly/sly_video_helper.py,sha256=D8PgoXpi0y3z-VEqvBLDf_gSUQ2hTL3irrfJyGhaV0Y,6758
661
- supervisely/convert/volume/__init__.py,sha256=ZXV1GwQlvKY7sZroUU-jiMTkgngk1aimh51nw1onzuc,168
661
+ supervisely/convert/volume/__init__.py,sha256=NjVfOa9uH1BdYvB-RynW6L28x0f_tqL9p7tHSIQ6Sso,245
662
662
  supervisely/convert/volume/volume_converter.py,sha256=3jpt2Yn_G4FSP_vHFsJHQfYNQpT7q6ar_sRyr_xrPnA,5335
663
663
  supervisely/convert/volume/dicom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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
+ supervisely/convert/volume/nii/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
667
+ supervisely/convert/volume/nii/nii_volume_converter.py,sha256=kI2JmeFuLfLWgYGCEozoaka1QH4TocnfgyN0em6maa0,5946
668
+ supervisely/convert/volume/nii/nii_volume_helper.py,sha256=kzh20fsdeI8efA0vawW0M6Wh48nMlCLzHBQFuSNVFmc,1136
666
669
  supervisely/convert/volume/sly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
667
670
  supervisely/convert/volume/sly/sly_volume_converter.py,sha256=XmSuxnRqxchG87b244f3h0UHvOt6IkajMquL1drWlCM,5595
668
671
  supervisely/convert/volume/sly/sly_volume_helper.py,sha256=gUY0GW3zDMlO2y-zQQG36uoXMrKkKz4-ErM1CDxFCxE,5620
@@ -1056,7 +1059,7 @@ supervisely/volume/__init__.py,sha256=EBZBY_5mzabXzMUQh5akusIGd16XnX9n8J0jIi_JmW
1056
1059
  supervisely/volume/nrrd_encoder.py,sha256=1lqwwyqxEvctw1ysQ70x4xPSV1uy1g5YcH5CURwL7-c,4084
1057
1060
  supervisely/volume/nrrd_loader.py,sha256=_yqahKcqSRxunHZ5LtnUWIRA7UvIhPKOhAUwYijSGY4,9065
1058
1061
  supervisely/volume/stl_converter.py,sha256=WIMQgHO_u4JT58QdcMXcb_euF1BFhM7D52IVX_0QTxE,6285
1059
- supervisely/volume/volume.py,sha256=-AAZIducizejhXyZWmaytCPLCcF8_vdnXDSTpJb5jIs,24337
1062
+ supervisely/volume/volume.py,sha256=7ebCIICqZMwRP3ruRy3PtSeiUpBpSlyRH20VJybBQbI,25828
1060
1063
  supervisely/volume_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1061
1064
  supervisely/volume_annotation/constants.py,sha256=BdFIh56fy7vzLIjt0gH8xP01EIU-qgQIwbSHVUcABCU,569
1062
1065
  supervisely/volume_annotation/plane.py,sha256=wyezAcc8tLp38O44CwWY0wjdQxf3VjRdFLWooCrk-Nw,16301
@@ -1078,9 +1081,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
1078
1081
  supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
1079
1082
  supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
1080
1083
  supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
1081
- supervisely-6.73.322.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1082
- supervisely-6.73.322.dist-info/METADATA,sha256=tXlMoMRbbXrc18yQVTx6Ti09xSaTCC4TyKgLUoNIC_U,33596
1083
- supervisely-6.73.322.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
1084
- supervisely-6.73.322.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1085
- supervisely-6.73.322.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1086
- supervisely-6.73.322.dist-info/RECORD,,
1084
+ supervisely-6.73.323.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1085
+ supervisely-6.73.323.dist-info/METADATA,sha256=uIqQoH6i-OiLhSZSLt6SqL7O1ZWPV0D6ZRJICli80eE,33596
1086
+ supervisely-6.73.323.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
1087
+ supervisely-6.73.323.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1088
+ supervisely-6.73.323.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1089
+ supervisely-6.73.323.dist-info/RECORD,,