dbdicom 0.2.0__py3-none-any.whl → 0.3.16__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.
Files changed (72) hide show
  1. dbdicom/__init__.py +3 -25
  2. dbdicom/api.py +496 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/database.py +133 -0
  5. dbdicom/dataset.py +471 -0
  6. dbdicom/dbd.py +1290 -0
  7. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  10. dbdicom/external/dcm4che/bin/emf2sf +57 -57
  11. dbdicom/register.py +402 -0
  12. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  13. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
  14. dbdicom/sop_classes/mr_image.py +338 -0
  15. dbdicom/sop_classes/parametric_map.py +381 -0
  16. dbdicom/sop_classes/secondary_capture.py +140 -0
  17. dbdicom/sop_classes/segmentation.py +311 -0
  18. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  19. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  20. dbdicom/utils/arrays.py +142 -0
  21. dbdicom/utils/files.py +0 -20
  22. dbdicom/utils/image.py +43 -466
  23. dbdicom/utils/pydicom_dataset.py +386 -0
  24. dbdicom-0.3.16.dist-info/METADATA +26 -0
  25. dbdicom-0.3.16.dist-info/RECORD +54 -0
  26. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
  27. dbdicom/create.py +0 -450
  28. dbdicom/ds/__init__.py +0 -10
  29. dbdicom/ds/create.py +0 -63
  30. dbdicom/ds/dataset.py +0 -841
  31. dbdicom/ds/dictionaries.py +0 -620
  32. dbdicom/ds/types/mr_image.py +0 -267
  33. dbdicom/ds/types/parametric_map.py +0 -226
  34. dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
  35. dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
  36. dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
  37. dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
  38. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
  39. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
  40. dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
  41. dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
  42. dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
  43. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
  44. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
  45. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
  46. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
  47. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
  48. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
  49. dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
  50. dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
  51. dbdicom/manager.py +0 -2077
  52. dbdicom/message.py +0 -119
  53. dbdicom/record.py +0 -1526
  54. dbdicom/types/database.py +0 -107
  55. dbdicom/types/instance.py +0 -184
  56. dbdicom/types/patient.py +0 -40
  57. dbdicom/types/series.py +0 -816
  58. dbdicom/types/study.py +0 -58
  59. dbdicom/utils/variables.py +0 -155
  60. dbdicom/utils/vreg.py +0 -2626
  61. dbdicom/wrappers/__init__.py +0 -7
  62. dbdicom/wrappers/dipy.py +0 -462
  63. dbdicom/wrappers/elastix.py +0 -855
  64. dbdicom/wrappers/numpy.py +0 -119
  65. dbdicom/wrappers/scipy.py +0 -1413
  66. dbdicom/wrappers/skimage.py +0 -1030
  67. dbdicom/wrappers/sklearn.py +0 -151
  68. dbdicom/wrappers/vreg.py +0 -273
  69. dbdicom-0.2.0.dist-info/METADATA +0 -276
  70. dbdicom-0.2.0.dist-info/RECORD +0 -81
  71. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
  72. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/top_level.txt +0 -0
@@ -1,46 +1,228 @@
1
1
  # Coded version of DICOM file 'C:\Users\steve\Dropbox\Software\QIB-Sheffield\dbdicom\tests\data\MULTIFRAME\IM_0010'
2
2
  # Produced by pydicom codify utility script
3
+
4
+ import datetime
5
+
3
6
  import numpy as np
7
+ import vreg
4
8
 
9
+ import pydicom
5
10
  from pydicom.dataset import Dataset, FileMetaDataset
6
11
  from pydicom.sequence import Sequence
12
+ from pydicom.uid import ExplicitVRLittleEndian, generate_uid
7
13
 
8
- from dbdicom.ds.dataset import DbDataset
9
- import dbdicom.utils.image as image
14
+ import dbdicom.utils.image as image_utils
15
+ from dbdicom.utils.pydicom_dataset import set_values, get_values
10
16
 
11
- class EnhancedMRImage(DbDataset):
12
17
 
13
- def __init__(self, dataset=None, template=None):
14
- super().__init__()
18
+ def from_volume(vol:vreg.Volume3D):
19
+ """
20
+ Build an Enhanced MR Image DICOM dataset from N+3D array.
15
21
 
16
- if (dataset is None) and (template is None):
17
- template = 'UKRIN'
22
+ Parameters
23
+ ----------
24
+ vol: vreg Volume3D
18
25
 
19
- if dataset is not None:
20
- self.__dict__ = dataset.__dict__
26
+ Returns
27
+ -------
28
+ pydicom dataset
29
+ """
30
+
31
+ # Flatten frames
32
+ frames = vol.values.reshape(vol.shape[:2] + (-1,))
33
+ geom = image_utils.dismantle_affine_matrix(vol.affine)
34
+
35
+ # --- FileDataset ---
36
+ ds = Dataset()
21
37
 
22
- if template == 'UKRIN':
23
- ukrin_maps(self)
38
+ # File Meta
39
+ ds.file_meta = FileMetaDataset()
40
+ ds.file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
41
+ ds.file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.4.1" # Enhanced MR
42
+
43
+ ds.is_little_endian = True
44
+ ds.is_implicit_VR = False
45
+ ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID
46
+ ds.SOPInstanceUID = generate_uid()
47
+
48
+ # Study/Series
49
+ ds.SeriesInstanceUID = generate_uid()
50
+ ds.StudyInstanceUID = generate_uid()
51
+ ds.FrameOfReferenceUID = generate_uid()
52
+ ds.Modality = "MR"
53
+ ds.PatientName = "Test^Patient"
54
+ ds.PatientID = "123456"
55
+ ds.StudyDate = datetime.date.today().strftime("%Y%m%d")
56
+ ds.StudyTime = datetime.datetime.now().strftime("%H%M%S")
57
+
58
+ # Image attributes
59
+ ds.Columns = vol.shape[0]
60
+ ds.Rows = vol.shape[1]
61
+ ds.NumberOfFrames = np.prod(vol.shape[2:])
62
+ ds.SamplesPerPixel = 1
63
+ ds.PhotometricInterpretation = "MONOCHROME2"
64
+ ds.BitsAllocated = 16
65
+ ds.BitsStored = 16
66
+ ds.HighBit = 15
67
+ ds.PixelRepresentation = 1
68
+ ds.PixelSpacing = list(vol.spacing[:2])
69
+ ds.SliceThickness = vol.spacing[2]
24
70
 
25
- @property
26
- def pixel_array(self):
27
- """Reimplements pydicom property pixel_array"""
28
- return get_pixel_array(self)
71
+ # Dimensions
72
+ ds.DimensionOrganizationSequence = Sequence([Dataset()])
73
+ ds.DimensionOrganizationSequence[0].DimensionOrganizationUID = generate_uid()
74
+ ds.DimensionIndexSequence = Sequence()
75
+ for axis in ['SliceLocation'] + vol.dims:
76
+ axis_dimension_item = Dataset()
77
+ axis_dimension_item.DimensionIndexPointer = pydicom.tag.Tag(axis)
78
+ axis_dimension_item.DimensionDescriptionLabel = axis
79
+ ds.DimensionIndexSequence.append(axis_dimension_item)
80
+
81
+ # Shared Functional Groups
82
+ ds.SharedFunctionalGroupsSequence = [Dataset()]
83
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence = [Dataset()]
84
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].PixelSpacing = ds.PixelSpacing
85
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].SliceThickness = ds.SliceThickness
86
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].SpacingBetweenSlices = ds.SliceThickness
87
+ ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence = [Dataset()]
88
+ ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence[0].ImageOrientationPatient = geom['ImageOrientationPatient']
89
+
90
+ # Per-frame Functional Groups
91
+ PerFrameFunctionalGroupsSequence = []
92
+
93
+ for flat_index in range(frames.shape[-1]):
94
+ frame_ds = Dataset()
95
+ vol_idx, slice_idx = divmod(flat_index, vol.shape[2])
96
+ indices = np.unravel_index(vol_idx, vol.shape[3:])
97
+ dim_values = [i + 1 for i in indices]
98
+
99
+ # Frame content
100
+ frame_ds.FrameContentSequence = [Dataset()]
101
+ frame_ds.FrameContentSequence[0].DimensionIndexValues = dim_values
102
+
103
+ # Plane position
104
+ frame_ds.PlanePositionSequence = [Dataset()]
105
+ frame_ds.PlanePositionSequence[0].ImagePositionPatient = list(np.array(geom['ImagePositionPatient']) + slice_idx * vol.spacing[2] * np.array(geom['slice_cosine']))
106
+
107
+ # Plane orientation
108
+ frame_ds.PlaneOrientationSequence = [Dataset()]
109
+ frame_ds.PlaneOrientationSequence[0].ImageOrientationPatient = geom['ImageOrientationPatient']
110
+
111
+ # Assign parameters using dims as DICOM keywords
112
+ for ax_i, axis in enumerate(vol.dims):
113
+ val = vol.coords[ax_i][indices]
114
+
115
+ sequence, attr = axis.split("/")
116
+ if not hasattr(frame_ds, sequence):
117
+ setattr(frame_ds, sequence, [Dataset()])
118
+ sequence_ds = getattr(frame_ds, sequence)[0]
119
+ set_values(sequence_ds, attr, val)
120
+
121
+ # Frame anatomy & type
122
+ frame_ds.FrameAnatomySequence = [Dataset()]
123
+ frame_ds.FrameAnatomySequence[0].AnatomicRegionSequence = [Dataset()]
124
+ frame_ds.FrameAnatomySequence[0].AnatomicRegionSequence[0].CodeValue = "12738006"
125
+ frame_ds.FrameAnatomySequence[0].AnatomicRegionSequence[0].CodingSchemeDesignator = "SCT"
126
+ frame_ds.FrameAnatomySequence[0].AnatomicRegionSequence[0].CodeMeaning = "Brain"
127
+
128
+ frame_ds.MRImageFrameTypeSequence = [Dataset()]
129
+ frame_ds.MRImageFrameTypeSequence[0].FrameType = ["ORIGINAL", "PRIMARY", "M", "NONE"]
130
+
131
+ # Acquisition datetime
132
+ frame_ds.FrameAcquisitionDateTime = (
133
+ datetime.datetime.now() + datetime.timedelta(seconds=flat_index)
134
+ ).strftime("%Y%m%d%H%M%S.%f")
135
+
136
+ PerFrameFunctionalGroupsSequence.append(frame_ds)
137
+
138
+ ds.PerFrameFunctionalGroupsSequence = PerFrameFunctionalGroupsSequence
139
+
140
+ # Pixel Data
141
+ ds.PixelData = b"".join([f.tobytes() for f in frames])
142
+
143
+ return ds
144
+
145
+
146
+
147
+ # THIS NEEDS DEBUGGING
148
+ def to_volume(ds):
149
+ """
150
+ Write an Enhanced MR Image DICOM from N+3D array.
151
+
152
+ Parameters
153
+ ----------
154
+ ds: pydicom Dataset
155
+
156
+ Returns
157
+ -------
158
+ vreg Volume3D
159
+ """
160
+ values = pixel_data(ds).T # need reshape
161
+ dims = [item.DimensionDescriptionLabel
162
+ for item in ds.DimensionIndexSequence[1:]] # handle slice location
163
+ affine = image_utils.affine_matrix(
164
+ get_values(ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence[0], 'ImageOrientationPatient'),
165
+ get_values(ds.SharedFunctionalGroupsSequence[0].PlanePositionSequence[0], 'ImagePositionPatient'),
166
+ get_values(ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0], 'PixelSpacing'),
167
+ get_values(ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0], 'SliceThickness'), # derive from slice_loc in per-frame
168
+ )
169
+ coords = np.zeros((len(dims), ds.NumberOfFrames))
170
+ for d, dim in enumerate(dims):
171
+ for flat_index in range(ds.NumberOfFrames):
172
+ found_val = False
173
+ frame_ds = ds.PerFrameFunctionalGroupsSequence[flat_index]
174
+ for sequence in frame_ds:
175
+ if hasattr(sequence[0], dim):
176
+ coords[d, flat_index] = get_values(sequence[0], dim)
177
+ found_val=True
178
+ break
179
+ if not found_val:
180
+ raise ValueError(f"Dimension {dim} not found in frame {flat_index}")
181
+ shape = [len(np.unique(coords[d,:])) for d in range(len(dims))]
182
+ if np.prod(shape) == ds.NumberOfFrames:
183
+ values = values.reshape(values.shape[:2] + tuple(shape))
184
+ else:
185
+ values = values.reshape(values.shape[:2] + (1, ds.NumberOfFrames) )
186
+
187
+ return vreg.volume(values, affine, coords, dims)
188
+
189
+
190
+
191
+
192
+ def pixel_data(ds):
193
+ """Read the pixel array from an MR image"""
194
+
195
+ array = ds.pixel_array
196
+ array = array.astype(np.float32)
197
+ if [0x2005, 0x100E] in ds: # 'Philips Rescale Slope'
198
+ slope = ds[(0x2005, 0x100E)].value
199
+ intercept = ds[(0x2005, 0x100D)].value
200
+ if (intercept == 0) and (slope == 1):
201
+ array = array.astype(np.int16)
202
+ else:
203
+ array = array.astype(np.float32)
204
+ array -= intercept
205
+ array /= slope
206
+ else:
207
+ slope = float(getattr(ds, 'RescaleSlope', 1))
208
+ intercept = float(getattr(ds, 'RescaleIntercept', 0))
209
+ if (intercept == 0) and (slope == 1):
210
+ array = array.astype(np.int16)
211
+ else:
212
+ array = array.astype(np.float32)
213
+ array *= slope
214
+ array += intercept
215
+ return np.transpose(array)
29
216
 
30
- def get_pixel_array(self):
31
- return get_pixel_array(self)
32
217
 
33
- def set_pixel_array(self, array, value_range=None):
34
- set_pixel_array(self, array, value_range=value_range)
35
218
 
36
- def image_type(self):
37
- return image_type(self)
38
219
 
39
- def signal_type(self):
40
- return signal_type(self)
41
220
 
42
221
 
43
- def ukrin_maps(ds):
222
+ def default(): # UKRIN-MAPS
223
+
224
+
225
+ ds = Dataset()
44
226
 
45
227
  # File meta info data elements
46
228
  ds.file_meta = FileMetaDataset()
@@ -497,139 +679,3 @@ def ukrin_maps_per_frame_functional_group():
497
679
 
498
680
  return ds
499
681
 
500
- def get_window(ds):
501
- """Centre and width of the pixel data after applying rescale slope and intercept.
502
-
503
- In this case retrieve the centre and width values of the first frame
504
- NOT In USE
505
- """
506
-
507
- centre = ds.PerFrameFunctionalGroupsSequence[0].FrameVOILUTSequence[0].WindowCenter
508
- width = ds.PerFrameFunctionalGroupsSequence[0].FrameVOILUTSequence[0].WindowWidth
509
- if centre is None or width is None:
510
- array = ds.get_pixel_array()
511
- if centre is None:
512
- centre = np.median(array)
513
- if width is None:
514
- p = np.percentile(array, [25, 75])
515
- width = p[1] - p[0]
516
-
517
- return centre, width
518
-
519
- def get_pixel_array(ds):
520
-
521
- array = ds.pixel_array.astype(np.float32)
522
- frames = ds.PerFrameFunctionalGroupsSequence
523
- for index, frame in enumerate(frames):
524
- slice = np.squeeze(array[index, ...])
525
- if [0x2005, 0x100E] in ds: # 'Philips Rescale Slope'
526
- slope = ds[(0x2005, 0x100E)].value
527
- intercept = ds[(0x2005, 0x100D)].value
528
- slice = (slice - intercept) / slope
529
- else:
530
- transform = frame.PixelValueTransformationSequence[0]
531
- slope = float(getattr(transform, 'RescaleSlope', 1))
532
- intercept = float(getattr(transform, 'RescaleIntercept', 0))
533
- slice = slice * slope + intercept
534
- array[index, ...] = np.transpose(slice)
535
-
536
- return array
537
-
538
-
539
- def set_pixel_array(ds, array, value_range=None):
540
-
541
- if (0x2005, 0x100E) in ds:
542
- del ds[0x2005, 0x100E] # Delete 'Philips Rescale Slope'
543
- if (0x2005, 0x100D) in ds:
544
- del ds[0x2005, 0x100D]
545
-
546
- array = image.clip(array, value_range=value_range)
547
- array, slope, intercept = image.scale_to_range(array, ds.BitsAllocated)
548
- array = np.transpose(array, (0, 2, 1))
549
-
550
- maximum = np.amax(array)
551
- minimum = np.amin(array)
552
- shape = np.shape(array)
553
-
554
- ds.NumberOfFrames = np.shape(array)[0]
555
- del ds.PerFrameFunctionalGroupsSequence[ds.NumberOfFrames:]
556
-
557
- ds.PixelRepresentation = 0
558
- ds.SmallestImagePixelValue = int(maximum)
559
- ds.LargestImagePixelValue = int(minimum)
560
- ds.RescaleSlope = 1 / slope
561
- ds.RescaleIntercept = - intercept / slope
562
- ds.WindowCenter = (maximum + minimum) / 2
563
- ds.WindowWidth = maximum - minimum
564
- ds.Rows = shape[0]
565
- ds.Columns = shape[1]
566
- ds.PixelData = array.tobytes()
567
-
568
-
569
- def image_type(ds):
570
- """Determine if a dataset is Magnitude, Phase, Real or Imaginary"""
571
-
572
- image_type = []
573
- for slice in ds.PerFrameFunctionalGroupsSequence:
574
- sequence = slice.MRImageFrameTypeSequence[0]
575
-
576
- if hasattr(sequence, 'FrameType'):
577
- type = set(sequence.FrameType)
578
- if set(['M', 'MAGNITUDE']).intersection(type):
579
- image_type.append('MAGNITUDE')
580
- elif set(['P', 'PHASE']).intersection(type):
581
- image_type.append('PHASE')
582
- elif set(['R', 'REAL']).intersection(type):
583
- image_type.append('REAL')
584
- elif set(['I', 'IMAGINARY']).intersection(type):
585
- image_type.append('IMAGINARY')
586
- elif hasattr(sequence, 'ComplexImageComponent'):
587
- type = set(sequence.ComplexImageComponent)
588
- if set(['M', 'MAGNITUDE']).intersection(type):
589
- image_type.append('MAGNITUDE')
590
- elif set(['P', 'PHASE']).intersection(type):
591
- image_type.append('PHASE')
592
- elif set(['R', 'REAL']).intersection(type):
593
- image_type.append('REAL')
594
- elif set(['I', 'IMAGINARY']).intersection(type):
595
- image_type.append('IMAGINARY')
596
- else:
597
- image_type.append('UNKNOWN')
598
-
599
- return image_type
600
-
601
-
602
- def signal_type(ds):
603
- """Determine if an image is Water, Fat, In-Phase, Out-phase image or None"""
604
-
605
- signal_type = []
606
- for slice in ds.PerFrameFunctionalGroupsSequence:
607
- sequence = slice.MRImageFrameTypeSequence[0]
608
-
609
- if hasattr(sequence, 'FrameType'):
610
- type = set(sequence.FrameType)
611
- if set(['W', 'WATER']).intersection(type):
612
- signal_type.append('WATER')
613
- elif set(['F', 'FAT']).intersection(type):
614
- signal_type.append('FAT')
615
- elif set(['IP', 'IN_PHASE']).intersection(type):
616
- signal_type.append('IN-PHASE')
617
- elif set(['OP', 'OUT_PHASE']).intersection(type):
618
- signal_type.append('OP-PHASE')
619
- else:
620
- signal_type.append('UNKNOWN')
621
-
622
- return signal_type
623
-
624
- def get_affine_matrix(ds):
625
- """Affine transformation matrix for all images in a multiframe image"""
626
-
627
- affineList = []
628
- for frame in ds.PerFrameFunctionalGroupsSequence:
629
- affine = image.affine_matrix(
630
- frame.PlaneOrientationSequence[0].ImageOrientationPatient,
631
- frame.PlanePositionSequence[0].ImagePositionPatient,
632
- frame.PixelMeasuresSequence[0].PixelSpacing,
633
- frame.PixelMeasuresSequence[0].SliceThickness)
634
- affineList.append(affine)
635
- return np.squeeze(np.array(affineList))