monai-weekly 1.5.dev2503__py3-none-any.whl → 1.5.dev2505__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.
- monai/__init__.py +1 -1
- monai/_version.py +3 -3
- monai/bundle/scripts.py +20 -5
- monai/data/image_reader.py +170 -49
- monai/inferers/merger.py +19 -4
- monai/transforms/intensity/array.py +4 -0
- {monai_weekly-1.5.dev2503.dist-info → monai_weekly-1.5.dev2505.dist-info}/METADATA +1 -1
- {monai_weekly-1.5.dev2503.dist-info → monai_weekly-1.5.dev2505.dist-info}/RECORD +11 -11
- {monai_weekly-1.5.dev2503.dist-info → monai_weekly-1.5.dev2505.dist-info}/LICENSE +0 -0
- {monai_weekly-1.5.dev2503.dist-info → monai_weekly-1.5.dev2505.dist-info}/WHEEL +0 -0
- {monai_weekly-1.5.dev2503.dist-info → monai_weekly-1.5.dev2505.dist-info}/top_level.txt +0 -0
monai/__init__.py
CHANGED
monai/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2025-
|
11
|
+
"date": "2025-02-02T02:25:03+0000",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "1.5.
|
14
|
+
"full-revisionid": "14c70f52e658772747b5703b83008afc2a9c26a7",
|
15
|
+
"version": "1.5.dev2505"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
monai/bundle/scripts.py
CHANGED
@@ -174,7 +174,7 @@ def _get_git_release_url(repo_owner: str, repo_name: str, tag_name: str, filenam
|
|
174
174
|
|
175
175
|
|
176
176
|
def _get_ngc_bundle_url(model_name: str, version: str) -> str:
|
177
|
-
return f"{NGC_BASE_URL}/{model_name.lower()}/versions/{version}/
|
177
|
+
return f"{NGC_BASE_URL}/{model_name.lower()}/versions/{version}/files"
|
178
178
|
|
179
179
|
|
180
180
|
def _get_ngc_private_base_url(repo: str) -> str:
|
@@ -218,6 +218,21 @@ def _remove_ngc_prefix(name: str, prefix: str = "monai_") -> str:
|
|
218
218
|
return name
|
219
219
|
|
220
220
|
|
221
|
+
def _get_all_download_files(request_url: str, headers: dict | None = None) -> list[str]:
|
222
|
+
if not has_requests:
|
223
|
+
raise ValueError("requests package is required, please install it.")
|
224
|
+
headers = {} if headers is None else headers
|
225
|
+
response = requests_get(request_url, headers=headers)
|
226
|
+
response.raise_for_status()
|
227
|
+
model_info = json.loads(response.text)
|
228
|
+
|
229
|
+
if not isinstance(model_info, dict) or "modelFiles" not in model_info:
|
230
|
+
raise ValueError("The data is not a dictionary or it does not have the key 'modelFiles'.")
|
231
|
+
|
232
|
+
model_files = model_info["modelFiles"]
|
233
|
+
return [f["path"] for f in model_files]
|
234
|
+
|
235
|
+
|
221
236
|
def _download_from_ngc(
|
222
237
|
download_path: Path,
|
223
238
|
filename: str,
|
@@ -229,12 +244,12 @@ def _download_from_ngc(
|
|
229
244
|
# ensure prefix is contained
|
230
245
|
filename = _add_ngc_prefix(filename, prefix=prefix)
|
231
246
|
url = _get_ngc_bundle_url(model_name=filename, version=version)
|
232
|
-
filepath = download_path / f"{filename}_v{version}.zip"
|
233
247
|
if remove_prefix:
|
234
248
|
filename = _remove_ngc_prefix(filename, prefix=remove_prefix)
|
235
|
-
|
236
|
-
|
237
|
-
|
249
|
+
filepath = download_path / filename
|
250
|
+
filepath.mkdir(parents=True, exist_ok=True)
|
251
|
+
for file in _get_all_download_files(url):
|
252
|
+
download_url(url=f"{url}/{file}", filepath=f"{filepath}/{file}", hash_val=None, progress=progress)
|
238
253
|
|
239
254
|
|
240
255
|
def _download_from_ngc_private(
|
monai/data/image_reader.py
CHANGED
@@ -418,6 +418,10 @@ class PydicomReader(ImageReader):
|
|
418
418
|
If provided, only the matched files will be included. For example, to include the file name
|
419
419
|
"image_0001.dcm", the regular expression could be `".*image_(\\d+).dcm"`. Default to `""`.
|
420
420
|
Set it to `None` to use `pydicom.misc.is_dicom` to match valid files.
|
421
|
+
to_gpu: If True, load the image into GPU memory using CuPy and Kvikio. This can accelerate data loading.
|
422
|
+
Default is False. CuPy and Kvikio are required for this option.
|
423
|
+
In practical use, it's recommended to add a warm up call before the actual loading.
|
424
|
+
A related tutorial will be prepared in the future, and the document will be updated accordingly.
|
421
425
|
kwargs: additional args for `pydicom.dcmread` API. more details about available args:
|
422
426
|
https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.filereader.dcmread.html
|
423
427
|
If the `get_data` function will be called
|
@@ -434,6 +438,7 @@ class PydicomReader(ImageReader):
|
|
434
438
|
prune_metadata: bool = True,
|
435
439
|
label_dict: dict | None = None,
|
436
440
|
fname_regex: str = "",
|
441
|
+
to_gpu: bool = False,
|
437
442
|
**kwargs,
|
438
443
|
):
|
439
444
|
super().__init__()
|
@@ -444,6 +449,33 @@ class PydicomReader(ImageReader):
|
|
444
449
|
self.prune_metadata = prune_metadata
|
445
450
|
self.label_dict = label_dict
|
446
451
|
self.fname_regex = fname_regex
|
452
|
+
if to_gpu and (not has_cp or not has_kvikio):
|
453
|
+
warnings.warn(
|
454
|
+
"PydicomReader: CuPy and/or Kvikio not installed for GPU loading, falling back to CPU loading."
|
455
|
+
)
|
456
|
+
to_gpu = False
|
457
|
+
|
458
|
+
if to_gpu:
|
459
|
+
self.warmup_kvikio()
|
460
|
+
|
461
|
+
self.to_gpu = to_gpu
|
462
|
+
|
463
|
+
def warmup_kvikio(self):
|
464
|
+
"""
|
465
|
+
Warm up the Kvikio library to initialize the internal buffers, cuFile, GDS, etc.
|
466
|
+
This can accelerate the data loading process when `to_gpu` is set to True.
|
467
|
+
"""
|
468
|
+
if has_cp and has_kvikio:
|
469
|
+
a = cp.arange(100)
|
470
|
+
with tempfile.NamedTemporaryFile() as tmp_file:
|
471
|
+
tmp_file_name = tmp_file.name
|
472
|
+
f = kvikio.CuFile(tmp_file_name, "w")
|
473
|
+
f.write(a)
|
474
|
+
f.close()
|
475
|
+
|
476
|
+
b = cp.empty_like(a)
|
477
|
+
f = kvikio.CuFile(tmp_file_name, "r")
|
478
|
+
f.read(b)
|
447
479
|
|
448
480
|
def verify_suffix(self, filename: Sequence[PathLike] | PathLike) -> bool:
|
449
481
|
"""
|
@@ -475,12 +507,15 @@ class PydicomReader(ImageReader):
|
|
475
507
|
img_ = []
|
476
508
|
|
477
509
|
filenames: Sequence[PathLike] = ensure_tuple(data)
|
510
|
+
self.filenames = list(filenames)
|
478
511
|
kwargs_ = self.kwargs.copy()
|
512
|
+
if self.to_gpu:
|
513
|
+
kwargs["defer_size"] = "100 KB"
|
479
514
|
kwargs_.update(kwargs)
|
480
515
|
|
481
516
|
self.has_series = False
|
482
517
|
|
483
|
-
for name in filenames:
|
518
|
+
for i, name in enumerate(filenames):
|
484
519
|
name = f"{name}"
|
485
520
|
if Path(name).is_dir():
|
486
521
|
# read DICOM series
|
@@ -489,20 +524,28 @@ class PydicomReader(ImageReader):
|
|
489
524
|
else:
|
490
525
|
series_slcs = [slc for slc in glob.glob(os.path.join(name, "*")) if pydicom.misc.is_dicom(slc)]
|
491
526
|
slices = []
|
527
|
+
loaded_slc_names = []
|
492
528
|
for slc in series_slcs:
|
493
529
|
try:
|
494
530
|
slices.append(pydicom.dcmread(fp=slc, **kwargs_))
|
531
|
+
loaded_slc_names.append(slc)
|
495
532
|
except pydicom.errors.InvalidDicomError as e:
|
496
533
|
warnings.warn(f"Failed to read {slc} with exception: \n{e}.", stacklevel=2)
|
497
|
-
img_.append(slices if len(slices) > 1 else slices[0])
|
498
534
|
if len(slices) > 1:
|
499
535
|
self.has_series = True
|
536
|
+
img_.append(slices)
|
537
|
+
self.filenames[i] = loaded_slc_names # type: ignore
|
538
|
+
else:
|
539
|
+
img_.append(slices[0]) # type: ignore
|
540
|
+
self.filenames[i] = loaded_slc_names[0] # type: ignore
|
500
541
|
else:
|
501
542
|
ds = pydicom.dcmread(fp=name, **kwargs_)
|
502
|
-
img_.append(ds)
|
503
|
-
|
543
|
+
img_.append(ds) # type: ignore
|
544
|
+
if len(filenames) == 1:
|
545
|
+
return img_[0]
|
546
|
+
return img_
|
504
547
|
|
505
|
-
def _combine_dicom_series(self, data: Iterable):
|
548
|
+
def _combine_dicom_series(self, data: Iterable, filenames: Sequence[PathLike]):
|
506
549
|
"""
|
507
550
|
Combine dicom series (a list of pydicom dataset objects). Their data arrays will be stacked together at a new
|
508
551
|
dimension as the last dimension.
|
@@ -522,28 +565,27 @@ class PydicomReader(ImageReader):
|
|
522
565
|
"""
|
523
566
|
slices: list = []
|
524
567
|
# for a dicom series
|
525
|
-
for slc_ds in data:
|
568
|
+
for slc_ds, filename in zip(data, filenames):
|
526
569
|
if hasattr(slc_ds, "InstanceNumber"):
|
527
|
-
slices.append(slc_ds)
|
570
|
+
slices.append((slc_ds, filename))
|
528
571
|
else:
|
529
|
-
warnings.warn(f"slice: {
|
530
|
-
slices = sorted(slices, key=lambda s: s.InstanceNumber)
|
531
|
-
|
572
|
+
warnings.warn(f"slice: {filename} does not have InstanceNumber tag, skip it.")
|
573
|
+
slices = sorted(slices, key=lambda s: s[0].InstanceNumber)
|
532
574
|
if len(slices) == 0:
|
533
575
|
raise ValueError("the input does not have valid slices.")
|
534
576
|
|
535
|
-
first_slice = slices[0]
|
577
|
+
first_slice, first_filename = slices[0]
|
536
578
|
average_distance = 0.0
|
537
|
-
first_array = self._get_array_data(first_slice)
|
579
|
+
first_array = self._get_array_data(first_slice, first_filename)
|
538
580
|
shape = first_array.shape
|
539
|
-
spacing = getattr(first_slice, "PixelSpacing", [1.0
|
581
|
+
spacing = getattr(first_slice, "PixelSpacing", [1.0] * len(shape))
|
540
582
|
prev_pos = getattr(first_slice, "ImagePositionPatient", (0.0, 0.0, 0.0))[2]
|
541
583
|
stack_array = [first_array]
|
542
584
|
for idx in range(1, len(slices)):
|
543
|
-
slc_array = self._get_array_data(slices[idx])
|
585
|
+
slc_array = self._get_array_data(slices[idx][0], slices[idx][1])
|
544
586
|
slc_shape = slc_array.shape
|
545
|
-
slc_spacing = getattr(slices[idx], "PixelSpacing",
|
546
|
-
slc_pos = getattr(slices[idx], "ImagePositionPatient", (0.0, 0.0, float(idx)))[2]
|
587
|
+
slc_spacing = getattr(slices[idx][0], "PixelSpacing", [1.0] * len(shape))
|
588
|
+
slc_pos = getattr(slices[idx][0], "ImagePositionPatient", (0.0, 0.0, float(idx)))[2]
|
547
589
|
if not np.allclose(slc_spacing, spacing):
|
548
590
|
warnings.warn(f"the list contains slices that have different spacings {spacing} and {slc_spacing}.")
|
549
591
|
if shape != slc_shape:
|
@@ -555,11 +597,14 @@ class PydicomReader(ImageReader):
|
|
555
597
|
if len(slices) > 1:
|
556
598
|
average_distance /= len(slices) - 1
|
557
599
|
spacing.append(average_distance)
|
558
|
-
|
600
|
+
if self.to_gpu:
|
601
|
+
stack_array = cp.stack(stack_array, axis=-1)
|
602
|
+
else:
|
603
|
+
stack_array = np.stack(stack_array, axis=-1)
|
559
604
|
stack_metadata = self._get_meta_dict(first_slice)
|
560
605
|
stack_metadata["spacing"] = np.asarray(spacing)
|
561
|
-
if hasattr(slices[-1], "ImagePositionPatient"):
|
562
|
-
stack_metadata["lastImagePositionPatient"] = np.asarray(slices[-1].ImagePositionPatient)
|
606
|
+
if hasattr(slices[-1][0], "ImagePositionPatient"):
|
607
|
+
stack_metadata["lastImagePositionPatient"] = np.asarray(slices[-1][0].ImagePositionPatient)
|
563
608
|
stack_metadata[MetaKeys.SPATIAL_SHAPE] = shape + (len(slices),)
|
564
609
|
else:
|
565
610
|
stack_array = stack_array[0]
|
@@ -597,29 +642,35 @@ class PydicomReader(ImageReader):
|
|
597
642
|
if self.has_series is True:
|
598
643
|
# a list, all objects within a list belong to one dicom series
|
599
644
|
if not isinstance(data[0], list):
|
600
|
-
|
645
|
+
# input is a dir, self.filenames is a list of list of filenames
|
646
|
+
dicom_data.append(self._combine_dicom_series(data, self.filenames[0])) # type: ignore
|
601
647
|
# a list of list, each inner list represents a dicom series
|
602
648
|
else:
|
603
|
-
for series in data:
|
604
|
-
dicom_data.append(self._combine_dicom_series(series))
|
649
|
+
for i, series in enumerate(data):
|
650
|
+
dicom_data.append(self._combine_dicom_series(series, self.filenames[i])) # type: ignore
|
605
651
|
else:
|
606
652
|
# a single pydicom dataset object
|
607
653
|
if not isinstance(data, list):
|
608
654
|
data = [data]
|
609
|
-
for d in data:
|
655
|
+
for i, d in enumerate(data):
|
610
656
|
if hasattr(d, "SegmentSequence"):
|
611
|
-
data_array, metadata = self._get_seg_data(d)
|
657
|
+
data_array, metadata = self._get_seg_data(d, self.filenames[i])
|
612
658
|
else:
|
613
|
-
data_array = self._get_array_data(d)
|
659
|
+
data_array = self._get_array_data(d, self.filenames[i])
|
614
660
|
metadata = self._get_meta_dict(d)
|
615
661
|
metadata[MetaKeys.SPATIAL_SHAPE] = data_array.shape
|
616
662
|
dicom_data.append((data_array, metadata))
|
617
663
|
|
664
|
+
# TODO: the actual type is list[np.ndarray | cp.ndarray]
|
665
|
+
# should figure out how to define correct types without having cupy not found error
|
666
|
+
# https://github.com/Project-MONAI/MONAI/pull/8188#discussion_r1886645918
|
618
667
|
img_array: list[np.ndarray] = []
|
619
668
|
compatible_meta: dict = {}
|
620
669
|
|
621
670
|
for data_array, metadata in ensure_tuple(dicom_data):
|
622
|
-
|
671
|
+
if self.swap_ij:
|
672
|
+
data_array = cp.swapaxes(data_array, 0, 1) if self.to_gpu else np.swapaxes(data_array, 0, 1)
|
673
|
+
img_array.append(cp.ascontiguousarray(data_array) if self.to_gpu else np.ascontiguousarray(data_array))
|
623
674
|
affine = self._get_affine(metadata, self.affine_lps_to_ras)
|
624
675
|
metadata[MetaKeys.SPACE] = SpaceKeys.RAS if self.affine_lps_to_ras else SpaceKeys.LPS
|
625
676
|
if self.swap_ij:
|
@@ -641,7 +692,7 @@ class PydicomReader(ImageReader):
|
|
641
692
|
|
642
693
|
_copy_compatible_dict(metadata, compatible_meta)
|
643
694
|
|
644
|
-
return _stack_images(img_array, compatible_meta), compatible_meta
|
695
|
+
return _stack_images(img_array, compatible_meta, to_cupy=self.to_gpu), compatible_meta
|
645
696
|
|
646
697
|
def _get_meta_dict(self, img) -> dict:
|
647
698
|
"""
|
@@ -713,7 +764,7 @@ class PydicomReader(ImageReader):
|
|
713
764
|
affine = orientation_ras_lps(affine)
|
714
765
|
return affine
|
715
766
|
|
716
|
-
def _get_frame_data(self, img) -> Iterator:
|
767
|
+
def _get_frame_data(self, img, filename, array_data) -> Iterator:
|
717
768
|
"""
|
718
769
|
yield frames and description from the segmentation image.
|
719
770
|
This function is adapted from Highdicom:
|
@@ -751,48 +802,54 @@ class PydicomReader(ImageReader):
|
|
751
802
|
"""
|
752
803
|
|
753
804
|
if not hasattr(img, "PerFrameFunctionalGroupsSequence"):
|
754
|
-
raise NotImplementedError(
|
755
|
-
f"To read dicom seg: {img.filename}, 'PerFrameFunctionalGroupsSequence' is required."
|
756
|
-
)
|
805
|
+
raise NotImplementedError(f"To read dicom seg: {filename}, 'PerFrameFunctionalGroupsSequence' is required.")
|
757
806
|
|
758
807
|
frame_seg_nums = []
|
759
808
|
for f in img.PerFrameFunctionalGroupsSequence:
|
760
809
|
if not hasattr(f, "SegmentIdentificationSequence"):
|
761
810
|
raise NotImplementedError(
|
762
|
-
f"To read dicom seg: {
|
811
|
+
f"To read dicom seg: {filename}, 'SegmentIdentificationSequence' is required for each frame."
|
763
812
|
)
|
764
813
|
frame_seg_nums.append(int(f.SegmentIdentificationSequence[0].ReferencedSegmentNumber))
|
765
814
|
|
766
|
-
frame_seg_nums_arr = np.array(frame_seg_nums)
|
815
|
+
frame_seg_nums_arr = cp.array(frame_seg_nums) if self.to_gpu else np.array(frame_seg_nums)
|
767
816
|
|
768
817
|
seg_descriptions = {int(f.SegmentNumber): f for f in img.SegmentSequence}
|
769
818
|
|
770
|
-
for i in np.unique(frame_seg_nums_arr):
|
771
|
-
indices = np.where(frame_seg_nums_arr == i)[0]
|
772
|
-
yield (
|
819
|
+
for i in np.unique(frame_seg_nums_arr) if not self.to_gpu else cp.unique(frame_seg_nums_arr):
|
820
|
+
indices = np.where(frame_seg_nums_arr == i)[0] if not self.to_gpu else cp.where(frame_seg_nums_arr == i)[0]
|
821
|
+
yield (array_data[indices, ...], seg_descriptions[i])
|
773
822
|
|
774
|
-
def _get_seg_data(self, img):
|
823
|
+
def _get_seg_data(self, img, filename):
|
775
824
|
"""
|
776
825
|
Get the array data and metadata of the segmentation image.
|
777
826
|
|
778
827
|
Aegs:
|
779
828
|
img: a Pydicom dataset object that has attribute "SegmentSequence".
|
829
|
+
filename: the file path of the image.
|
780
830
|
|
781
831
|
"""
|
782
832
|
|
783
833
|
metadata = self._get_meta_dict(img)
|
784
834
|
n_classes = len(img.SegmentSequence)
|
785
|
-
|
835
|
+
array_data = self._get_array_data(img, filename)
|
836
|
+
spatial_shape = list(array_data.shape)
|
786
837
|
spatial_shape[0] = spatial_shape[0] // n_classes
|
787
838
|
|
788
839
|
if self.label_dict is not None:
|
789
840
|
metadata["labels"] = self.label_dict
|
790
|
-
|
841
|
+
if self.to_gpu:
|
842
|
+
all_segs = cp.zeros([*spatial_shape, len(self.label_dict)], dtype=array_data.dtype)
|
843
|
+
else:
|
844
|
+
all_segs = np.zeros([*spatial_shape, len(self.label_dict)], dtype=array_data.dtype)
|
791
845
|
else:
|
792
846
|
metadata["labels"] = {}
|
793
|
-
|
847
|
+
if self.to_gpu:
|
848
|
+
all_segs = cp.zeros([*spatial_shape, n_classes], dtype=array_data.dtype)
|
849
|
+
else:
|
850
|
+
all_segs = np.zeros([*spatial_shape, n_classes], dtype=array_data.dtype)
|
794
851
|
|
795
|
-
for i, (frames, description) in enumerate(self._get_frame_data(img)):
|
852
|
+
for i, (frames, description) in enumerate(self._get_frame_data(img, filename, array_data)):
|
796
853
|
segment_label = getattr(description, "SegmentLabel", f"label_{i}")
|
797
854
|
class_name = getattr(description, "SegmentDescription", segment_label)
|
798
855
|
if class_name not in metadata["labels"].keys():
|
@@ -840,19 +897,79 @@ class PydicomReader(ImageReader):
|
|
840
897
|
|
841
898
|
return all_segs, metadata
|
842
899
|
|
843
|
-
def
|
900
|
+
def _get_array_data_from_gpu(self, img, filename):
|
901
|
+
"""
|
902
|
+
Get the raw array data of the image. This function is used when `to_gpu` is set to True.
|
903
|
+
|
904
|
+
Args:
|
905
|
+
img: a Pydicom dataset object.
|
906
|
+
filename: the file path of the image.
|
907
|
+
|
908
|
+
"""
|
909
|
+
rows = getattr(img, "Rows", None)
|
910
|
+
columns = getattr(img, "Columns", None)
|
911
|
+
bits_allocated = getattr(img, "BitsAllocated", None)
|
912
|
+
samples_per_pixel = getattr(img, "SamplesPerPixel", 1)
|
913
|
+
number_of_frames = getattr(img, "NumberOfFrames", 1)
|
914
|
+
pixel_representation = getattr(img, "PixelRepresentation", 1)
|
915
|
+
|
916
|
+
if rows is None or columns is None or bits_allocated is None:
|
917
|
+
warnings.warn(
|
918
|
+
f"dicom data: {filename} does not have Rows, Columns or BitsAllocated, falling back to CPU loading."
|
919
|
+
)
|
920
|
+
|
921
|
+
if not hasattr(img, "pixel_array"):
|
922
|
+
raise ValueError(f"dicom data: {filename} does not have pixel_array.")
|
923
|
+
data = img.pixel_array
|
924
|
+
|
925
|
+
return data
|
926
|
+
|
927
|
+
if bits_allocated == 8:
|
928
|
+
dtype = cp.int8 if pixel_representation == 1 else cp.uint8
|
929
|
+
elif bits_allocated == 16:
|
930
|
+
dtype = cp.int16 if pixel_representation == 1 else cp.uint16
|
931
|
+
elif bits_allocated == 32:
|
932
|
+
dtype = cp.int32 if pixel_representation == 1 else cp.uint32
|
933
|
+
else:
|
934
|
+
raise ValueError("Unsupported BitsAllocated value")
|
935
|
+
|
936
|
+
bytes_per_pixel = bits_allocated // 8
|
937
|
+
total_pixels = rows * columns * samples_per_pixel * number_of_frames
|
938
|
+
expected_pixel_data_length = total_pixels * bytes_per_pixel
|
939
|
+
|
940
|
+
pixel_data_tag = pydicom.tag.Tag(0x7FE0, 0x0010)
|
941
|
+
if pixel_data_tag not in img:
|
942
|
+
raise ValueError(f"dicom data: {filename} does not have pixel data.")
|
943
|
+
|
944
|
+
offset = img.get_item(pixel_data_tag, keep_deferred=True).value_tell
|
945
|
+
|
946
|
+
with kvikio.CuFile(filename, "r") as f:
|
947
|
+
buffer = cp.empty(expected_pixel_data_length, dtype=cp.int8)
|
948
|
+
f.read(buffer, expected_pixel_data_length, offset)
|
949
|
+
|
950
|
+
new_shape = (number_of_frames, rows, columns) if number_of_frames > 1 else (rows, columns)
|
951
|
+
data = buffer.view(dtype).reshape(new_shape)
|
952
|
+
|
953
|
+
return data
|
954
|
+
|
955
|
+
def _get_array_data(self, img, filename):
|
844
956
|
"""
|
845
957
|
Get the array data of the image. If `RescaleSlope` and `RescaleIntercept` are available, the raw array data
|
846
|
-
will be rescaled. The output data has the dtype
|
958
|
+
will be rescaled. The output data has the dtype float32 if the rescaling is applied.
|
847
959
|
|
848
960
|
Args:
|
849
961
|
img: a Pydicom dataset object.
|
962
|
+
filename: the file path of the image.
|
850
963
|
|
851
964
|
"""
|
852
965
|
# process Dicom series
|
853
|
-
|
854
|
-
|
855
|
-
|
966
|
+
|
967
|
+
if self.to_gpu:
|
968
|
+
data = self._get_array_data_from_gpu(img, filename)
|
969
|
+
else:
|
970
|
+
if not hasattr(img, "pixel_array"):
|
971
|
+
raise ValueError(f"dicom data: {filename} does not have pixel_array.")
|
972
|
+
data = img.pixel_array
|
856
973
|
|
857
974
|
slope, offset = 1.0, 0.0
|
858
975
|
rescale_flag = False
|
@@ -862,8 +979,14 @@ class PydicomReader(ImageReader):
|
|
862
979
|
if hasattr(img, "RescaleIntercept"):
|
863
980
|
offset = img.RescaleIntercept
|
864
981
|
rescale_flag = True
|
982
|
+
|
865
983
|
if rescale_flag:
|
866
|
-
|
984
|
+
if self.to_gpu:
|
985
|
+
slope = cp.asarray(slope, dtype=cp.float32)
|
986
|
+
offset = cp.asarray(offset, dtype=cp.float32)
|
987
|
+
data = data.astype(cp.float32) * slope + offset
|
988
|
+
else:
|
989
|
+
data = data.astype(np.float32) * slope + offset
|
867
990
|
|
868
991
|
return data
|
869
992
|
|
@@ -884,8 +1007,6 @@ class NibabelReader(ImageReader):
|
|
884
1007
|
Default is False. CuPy and Kvikio are required for this option.
|
885
1008
|
Note: For compressed NIfTI files, some operations may still be performed on CPU memory,
|
886
1009
|
and the acceleration may not be significant. In some cases, it may be slower than loading on CPU.
|
887
|
-
In practical use, it's recommended to add a warm up call before the actual loading.
|
888
|
-
A related tutorial will be prepared in the future, and the document will be updated accordingly.
|
889
1010
|
kwargs: additional args for `nibabel.load` API. more details about available args:
|
890
1011
|
https://github.com/nipy/nibabel/blob/master/nibabel/loadsave.py
|
891
1012
|
|
monai/inferers/merger.py
CHANGED
@@ -15,12 +15,13 @@ import threading
|
|
15
15
|
from abc import ABC, abstractmethod
|
16
16
|
from collections.abc import Sequence
|
17
17
|
from contextlib import nullcontext
|
18
|
+
from tempfile import TemporaryDirectory
|
18
19
|
from typing import TYPE_CHECKING, Any
|
19
20
|
|
20
21
|
import numpy as np
|
21
22
|
import torch
|
22
23
|
|
23
|
-
from monai.utils import ensure_tuple_size, optional_import, require_pkg
|
24
|
+
from monai.utils import ensure_tuple_size, get_package_version, optional_import, require_pkg, version_geq
|
24
25
|
|
25
26
|
if TYPE_CHECKING:
|
26
27
|
import zarr
|
@@ -233,7 +234,7 @@ class ZarrAvgMerger(Merger):
|
|
233
234
|
store: zarr.storage.Store | str = "merged.zarr",
|
234
235
|
value_store: zarr.storage.Store | str | None = None,
|
235
236
|
count_store: zarr.storage.Store | str | None = None,
|
236
|
-
compressor: str =
|
237
|
+
compressor: str | None = None,
|
237
238
|
value_compressor: str | None = None,
|
238
239
|
count_compressor: str | None = None,
|
239
240
|
chunks: Sequence[int] | bool = True,
|
@@ -246,8 +247,22 @@ class ZarrAvgMerger(Merger):
|
|
246
247
|
self.value_dtype = value_dtype
|
247
248
|
self.count_dtype = count_dtype
|
248
249
|
self.store = store
|
249
|
-
self.
|
250
|
-
|
250
|
+
self.tmpdir: TemporaryDirectory | None
|
251
|
+
if version_geq(get_package_version("zarr"), "3.0.0"):
|
252
|
+
if value_store is None:
|
253
|
+
self.tmpdir = TemporaryDirectory()
|
254
|
+
self.value_store = zarr.storage.LocalStore(self.tmpdir.name)
|
255
|
+
else:
|
256
|
+
self.value_store = value_store
|
257
|
+
if count_store is None:
|
258
|
+
self.tmpdir = TemporaryDirectory()
|
259
|
+
self.count_store = zarr.storage.LocalStore(self.tmpdir.name)
|
260
|
+
else:
|
261
|
+
self.count_store = count_store
|
262
|
+
else:
|
263
|
+
self.tmpdir = None
|
264
|
+
self.value_store = zarr.storage.TempStore() if value_store is None else value_store
|
265
|
+
self.count_store = zarr.storage.TempStore() if count_store is None else count_store
|
251
266
|
self.chunks = chunks
|
252
267
|
self.compressor = compressor
|
253
268
|
self.value_compressor = value_compressor
|
@@ -821,6 +821,7 @@ class NormalizeIntensity(Transform):
|
|
821
821
|
mean and std on each channel separately.
|
822
822
|
When `channel_wise` is True, the first dimension of `subtrahend` and `divisor` should
|
823
823
|
be the number of image channels if they are not None.
|
824
|
+
If the input is not of floating point type, it will be converted to float32
|
824
825
|
|
825
826
|
Args:
|
826
827
|
subtrahend: the amount to subtract by (usually the mean).
|
@@ -907,6 +908,9 @@ class NormalizeIntensity(Transform):
|
|
907
908
|
if self.divisor is not None and len(self.divisor) != len(img):
|
908
909
|
raise ValueError(f"img has {len(img)} channels, but divisor has {len(self.divisor)} components.")
|
909
910
|
|
911
|
+
if not img.dtype.is_floating_point:
|
912
|
+
img, *_ = convert_data_type(img, dtype=torch.float32)
|
913
|
+
|
910
914
|
for i, d in enumerate(img):
|
911
915
|
img[i] = self._normalize( # type: ignore
|
912
916
|
d,
|
@@ -1,5 +1,5 @@
|
|
1
|
-
monai/__init__.py,sha256=
|
2
|
-
monai/_version.py,sha256=
|
1
|
+
monai/__init__.py,sha256=_9NAD3U4LFjwCPYSJK0ecDZIGRPgvsEz5RWMEeRwTe4,4095
|
2
|
+
monai/_version.py,sha256=RBfwvnikigD3UW6Md_MFJYW7ZAmuKk3hwFD8Y8LPUjU,503
|
3
3
|
monai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
monai/_extensions/__init__.py,sha256=NEBPreRhQ8H9gVvgrLr_y52_TmqB96u_u4VQmeNT93I,642
|
5
5
|
monai/_extensions/loader.py,sha256=7SiKw36q-nOzH8CRbBurFrz7GM40GCu7rc93Tm8XpnI,3643
|
@@ -114,7 +114,7 @@ monai/bundle/config_item.py,sha256=rMjXSGkjJZdi04BwSHwCcIwzIb_TflmC3xDhC3SVJRs,1
|
|
114
114
|
monai/bundle/config_parser.py,sha256=cGyEn-cqNk0rEEZ1Qiv6UydmIDvtWZcMVljyfVm5i50,23025
|
115
115
|
monai/bundle/properties.py,sha256=iN3K4FVmN9ny1Hw9p5j7_ULcCdSD8PmrR7qXxbNz49k,11582
|
116
116
|
monai/bundle/reference_resolver.py,sha256=GXCMK4iogxxE6VocsmAbUrcXosmC5arnjeG9zYhHKpg,16748
|
117
|
-
monai/bundle/scripts.py,sha256=
|
117
|
+
monai/bundle/scripts.py,sha256=D0GnyZF0FCQyHZtqjoX9jen3IiAvnUeM1mtSWa2fu4E,89935
|
118
118
|
monai/bundle/utils.py,sha256=t-22uFvLn7Yy-dr1v1U33peNOxgAmU4TJiGAbsBrUKs,10108
|
119
119
|
monai/bundle/workflows.py,sha256=CuhmFq1AWsN3ATiYJCSakPOxrOdGutl6vkpo9sxe8gU,34369
|
120
120
|
monai/config/__init__.py,sha256=CN28CfTdsp301gv8YXfVvkbztCfbAqrLKrJi_C8oP9s,1048
|
@@ -131,7 +131,7 @@ monai/data/fft_utils.py,sha256=in9Zu8hC4oSVzuA-Zl236X6EkvgFka0RXdOxgvdGkv0,4448
|
|
131
131
|
monai/data/folder_layout.py,sha256=IsHW1-Bkupn_T8r6MgFTIJQh5HwCg0xQwOKmgBtl0gE,6344
|
132
132
|
monai/data/grid_dataset.py,sha256=O0gHf3BgrisH1erOMZNSpoIut92mydnNpzGYWnBKg4U,19483
|
133
133
|
monai/data/image_dataset.py,sha256=OhNJ3awauWtqsDhefIGDw3UYGF3RoleeNUPdJOKg3kI,7008
|
134
|
-
monai/data/image_reader.py,sha256=
|
134
|
+
monai/data/image_reader.py,sha256=cjwLac3TqWpQVBjIioK2V2VNxeP5Zhzdk0BmOfD-V5Q,70862
|
135
135
|
monai/data/image_writer.py,sha256=rH6vboPFkX4ziN3lnrmK6AzAOQYI9tEiOJb7Al2tj-8,39856
|
136
136
|
monai/data/iterable_dataset.py,sha256=A0L5jaxwnfgProBj96tlT160esI21yutnTf3a4c29Ms,13100
|
137
137
|
monai/data/itk_torch_bridge.py,sha256=3th-B3tJuJE22JFfOUgGeTMOPh1czJEiSccFyn_Ob0w,14461
|
@@ -195,7 +195,7 @@ monai/handlers/utils.py,sha256=Ib1u-PLrtIkiLqTfREnrCWpN4af1btdNzkyMZuuuYyU,10239
|
|
195
195
|
monai/handlers/validation_handler.py,sha256=NZO21c6zzXbmAgJZHkkdoZQSQIHwuxh94QD3PLUldGU,3674
|
196
196
|
monai/inferers/__init__.py,sha256=K74t_RCeUPdEZvHzIPzVAwZ9DtmouLqhb3qDEmFBWs4,1107
|
197
197
|
monai/inferers/inferer.py,sha256=aZwCmM6WGj49SHi_jIkQeGDstMz45frvM1Lomoeqzm4,92669
|
198
|
-
monai/inferers/merger.py,sha256=
|
198
|
+
monai/inferers/merger.py,sha256=dZm-FVyXPlFb59q4DG52mbtPm8Iy4cNFWv3un0Z8k0M,16262
|
199
199
|
monai/inferers/splitter.py,sha256=_hTnFdvDNRckkA7ZGQehVsNZw83oXoGFWyk5VXNqgJg,21149
|
200
200
|
monai/inferers/utils.py,sha256=BWVg6j6FGX5tFgrf6QvxJgFkr4bJWTpFgVBQRjfAq5A,20432
|
201
201
|
monai/losses/__init__.py,sha256=igy7BjoQzM3McmJPD2tmeiW2ljSXfB2HBdc4YiDzYEg,1778
|
@@ -367,7 +367,7 @@ monai/transforms/croppad/batch.py,sha256=5ukcYk3VCDpk62AL5Q_jTqpXmSNTlw0UCUhDeAB
|
|
367
367
|
monai/transforms/croppad/dictionary.py,sha256=WOzj_PjmoB3zLEmtQlafb9-PWgXd-s5K7Z5Doc8Adns,60746
|
368
368
|
monai/transforms/croppad/functional.py,sha256=iroD0XBaMG1Mox6-EotIh2nAUxJPrpIyUrHopc83Sug,12640
|
369
369
|
monai/transforms/intensity/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
370
|
-
monai/transforms/intensity/array.py,sha256=
|
370
|
+
monai/transforms/intensity/array.py,sha256=CF3lVbcI4D-YmhifxGj1Mf32TwW4alK1WYBzFiejWbo,121791
|
371
371
|
monai/transforms/intensity/dictionary.py,sha256=MEeMKQckn6X-cEk51Z2YTGjt89RohBzFfO_jU3D06wk,85086
|
372
372
|
monai/transforms/io/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
373
373
|
monai/transforms/io/array.py,sha256=QxB19z8aBhaSNlSkrubyoM0swHqTcDatWT0GrzmOOwM,27481
|
@@ -420,8 +420,8 @@ monai/visualize/img2tensorboard.py,sha256=NnMcyfIFqX-jD7TBO3Rn02zt5uug79d_7pIIaV
|
|
420
420
|
monai/visualize/occlusion_sensitivity.py,sha256=OQHEJLyIhB8zWqQsfKaX-1kvCjWFVYtLfS4dFC0nKFI,18160
|
421
421
|
monai/visualize/utils.py,sha256=B-MhTVs7sQbIqYS3yPnpBwPw2K82rE2PBtGIfpwZtWM,9894
|
422
422
|
monai/visualize/visualizer.py,sha256=qckyaMZCbezYUwE20k5yc-Pb7UozVavMDbrmyQwfYHY,1377
|
423
|
-
monai_weekly-1.5.
|
424
|
-
monai_weekly-1.5.
|
425
|
-
monai_weekly-1.5.
|
426
|
-
monai_weekly-1.5.
|
427
|
-
monai_weekly-1.5.
|
423
|
+
monai_weekly-1.5.dev2505.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
424
|
+
monai_weekly-1.5.dev2505.dist-info/METADATA,sha256=IAD15IBYLPNobEtmzADUM5Zl66cKa1jZ5F5QR4vlIT4,11876
|
425
|
+
monai_weekly-1.5.dev2505.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
426
|
+
monai_weekly-1.5.dev2505.dist-info/top_level.txt,sha256=UaNwRzLGORdus41Ip446s3bBfViLkdkDsXDo34J2P44,6
|
427
|
+
monai_weekly-1.5.dev2505.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|