dbdicom 0.2.6__py3-none-any.whl → 0.3.1__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.

Potentially problematic release.


This version of dbdicom might be problematic. Click here for more details.

Files changed (52) hide show
  1. dbdicom/__init__.py +1 -28
  2. dbdicom/api.py +287 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/dataset.py +721 -0
  5. dbdicom/dbd.py +736 -0
  6. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  7. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/register.py +527 -0
  10. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  11. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +153 -26
  12. dbdicom/{ds/types → sop_classes}/mr_image.py +185 -140
  13. dbdicom/sop_classes/parametric_map.py +310 -0
  14. dbdicom/sop_classes/secondary_capture.py +140 -0
  15. dbdicom/sop_classes/segmentation.py +311 -0
  16. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  17. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  18. dbdicom/utils/arrays.py +36 -0
  19. dbdicom/utils/files.py +0 -20
  20. dbdicom/utils/image.py +10 -629
  21. dbdicom-0.3.1.dist-info/METADATA +28 -0
  22. dbdicom-0.3.1.dist-info/RECORD +53 -0
  23. dbdicom/create.py +0 -457
  24. dbdicom/dro.py +0 -174
  25. dbdicom/ds/__init__.py +0 -10
  26. dbdicom/ds/create.py +0 -63
  27. dbdicom/ds/dataset.py +0 -869
  28. dbdicom/ds/dictionaries.py +0 -620
  29. dbdicom/ds/types/parametric_map.py +0 -226
  30. dbdicom/extensions/__init__.py +0 -9
  31. dbdicom/extensions/dipy.py +0 -448
  32. dbdicom/extensions/elastix.py +0 -503
  33. dbdicom/extensions/matplotlib.py +0 -107
  34. dbdicom/extensions/numpy.py +0 -271
  35. dbdicom/extensions/scipy.py +0 -1512
  36. dbdicom/extensions/skimage.py +0 -1030
  37. dbdicom/extensions/sklearn.py +0 -243
  38. dbdicom/extensions/vreg.py +0 -1390
  39. dbdicom/manager.py +0 -2132
  40. dbdicom/message.py +0 -119
  41. dbdicom/pipelines.py +0 -66
  42. dbdicom/record.py +0 -1893
  43. dbdicom/types/database.py +0 -107
  44. dbdicom/types/instance.py +0 -231
  45. dbdicom/types/patient.py +0 -40
  46. dbdicom/types/series.py +0 -2874
  47. dbdicom/types/study.py +0 -58
  48. dbdicom-0.2.6.dist-info/METADATA +0 -72
  49. dbdicom-0.2.6.dist-info/RECORD +0 -66
  50. {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/WHEEL +0 -0
  51. {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/licenses/LICENSE +0 -0
  52. {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,311 @@
1
+ import numpy as np
2
+ import pydicom
3
+ from pydicom.dataset import Dataset, FileDataset
4
+ from pydicom.sequence import Sequence
5
+ import datetime
6
+
7
+
8
+ def create_binary_segmentation_dicom(rows=128, cols=128):
9
+ # Create file metadata
10
+ meta = Dataset()
11
+ meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.66.4' # Segmentation Storage
12
+ meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
13
+ meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
14
+ meta.ImplementationClassUID = pydicom.uid.generate_uid()
15
+
16
+ # Create the FileDataset (in memory)
17
+ ds = FileDataset(None, {}, file_meta=meta, preamble=b"\0" * 128)
18
+ ds.is_little_endian = True
19
+ ds.is_implicit_VR = False
20
+
21
+ # Required general attributes
22
+ ds.SOPClassUID = meta.MediaStorageSOPClassUID
23
+ ds.SOPInstanceUID = meta.MediaStorageSOPInstanceUID
24
+ ds.Modality = 'SEG'
25
+ ds.SeriesInstanceUID = pydicom.uid.generate_uid()
26
+ ds.StudyInstanceUID = pydicom.uid.generate_uid()
27
+ ds.FrameOfReferenceUID = pydicom.uid.generate_uid()
28
+ ds.PatientName = 'Seg^Test'
29
+ ds.PatientID = 'SEG001'
30
+
31
+ # Set content date/time
32
+ dt = datetime.datetime.now()
33
+ ds.ContentDate = dt.strftime('%Y%m%d')
34
+ ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
35
+
36
+ # Segmentation-specific
37
+ ds.SegmentationType = 'BINARY'
38
+ ds.ContentLabel = 'MASK'
39
+ ds.ContentDescription = 'Binary segmentation mask'
40
+ ds.ContentCreatorName = 'AutoGen'
41
+
42
+ ds.Rows = rows
43
+ ds.Columns = cols
44
+ ds.SamplesPerPixel = 1
45
+ ds.BitsAllocated = 1
46
+ ds.BitsStored = 1
47
+ ds.HighBit = 0
48
+ ds.PixelRepresentation = 0
49
+ ds.PhotometricInterpretation = 'MONOCHROME2'
50
+ ds.NumberOfFrames = 1
51
+ ds.BurnedInAnnotation = 'NO'
52
+ ds.ImageType = ['DERIVED', 'PRIMARY']
53
+
54
+ # Create a dummy mask (circle in center)
55
+ Y, X = np.ogrid[:rows, :cols]
56
+ mask = ((X - cols // 2)**2 + (Y - rows // 2)**2) < (min(rows, cols) // 4)**2
57
+ binary_frame = np.packbits(mask.astype(np.uint8).flatten())
58
+
59
+ # Assign Pixel Data
60
+ ds.PixelData = binary_frame.tobytes()
61
+
62
+ # SegmentSequence: define what the mask means
63
+ segment = Dataset()
64
+ segment.SegmentNumber = 1
65
+ segment.SegmentLabel = 'Kidney'
66
+ segment.SegmentAlgorithmType = 'MANUAL'
67
+ segment.SegmentAlgorithmName = 'ManualDraw'
68
+
69
+ segment.SegmentedPropertyCategoryCodeSequence = Sequence([Dataset()])
70
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeValue = 'T-D0050'
71
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodingSchemeDesignator = 'SRT'
72
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeMeaning = 'Tissue'
73
+
74
+ segment.SegmentedPropertyTypeCodeSequence = Sequence([Dataset()])
75
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeValue = 'T-71000'
76
+ segment.SegmentedPropertyTypeCodeSequence[0].CodingSchemeDesignator = 'SRT'
77
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeMeaning = 'Kidney'
78
+
79
+ ds.SegmentSequence = Sequence([segment])
80
+
81
+ # Functional groups (Plane Position)
82
+ fg = Dataset()
83
+ pp = Dataset()
84
+ pp.ImagePositionPatient = [0.0, 0.0, 0.0]
85
+ fg.PlanePositionSequence = Sequence([pp])
86
+ ds.PerFrameFunctionalGroupsSequence = Sequence([fg])
87
+
88
+ return ds
89
+
90
+
91
+
92
+
93
+ def create_multi_segment_segmentation_dicom(masks_dict, spacing=(1.0, 1.0), origin=(0.0, 0.0, 0.0)):
94
+ """
95
+ Create a multi-segment binary DICOM Segmentation object.
96
+
97
+ Parameters:
98
+ - masks_dict: dict of {label: binary 2D NumPy array}
99
+ - spacing: (row_spacing, col_spacing)
100
+ - origin: (x, y, z) ImagePositionPatient
101
+
102
+ Returns:
103
+ - pydicom FileDataset object
104
+ """
105
+
106
+ labels = list(masks_dict.keys())
107
+ first_mask = next(iter(masks_dict.values()))
108
+ rows, cols = first_mask.shape
109
+
110
+ # Create metadata
111
+ meta = Dataset()
112
+ meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.66.4'
113
+ meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
114
+ meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
115
+ meta.ImplementationClassUID = pydicom.uid.generate_uid()
116
+
117
+ ds = FileDataset(None, {}, file_meta=meta, preamble=b"\0" * 128)
118
+ ds.is_little_endian = True
119
+ ds.is_implicit_VR = False
120
+
121
+ # Required general attributes
122
+ ds.SOPClassUID = meta.MediaStorageSOPClassUID
123
+ ds.SOPInstanceUID = meta.MediaStorageSOPInstanceUID
124
+ ds.Modality = 'SEG'
125
+ ds.SeriesInstanceUID = pydicom.uid.generate_uid()
126
+ ds.StudyInstanceUID = pydicom.uid.generate_uid()
127
+ ds.FrameOfReferenceUID = pydicom.uid.generate_uid()
128
+ ds.PatientName = 'Seg^Multi'
129
+ ds.PatientID = 'MULTISEG001'
130
+ ds.ContentDate = datetime.datetime.now().strftime('%Y%m%d')
131
+ ds.ContentTime = datetime.datetime.now().strftime('%H%M%S.%f')[:13]
132
+
133
+ ds.Rows = rows
134
+ ds.Columns = cols
135
+ ds.SamplesPerPixel = 1
136
+ ds.BitsAllocated = 1
137
+ ds.BitsStored = 1
138
+ ds.HighBit = 0
139
+ ds.PixelRepresentation = 0
140
+ ds.PhotometricInterpretation = 'MONOCHROME2'
141
+ ds.SegmentationType = 'BINARY'
142
+ ds.BurnedInAnnotation = 'NO'
143
+ ds.ImageType = ['DERIVED', 'PRIMARY']
144
+ ds.ContentLabel = 'MULTI_SEG'
145
+ ds.ContentCreatorName = 'AutoGen'
146
+
147
+ # SegmentSequence
148
+ segment_sequence = []
149
+ pixel_data_bytes = b''
150
+ fg_sequence = []
151
+
152
+ for i, label in enumerate(labels):
153
+ segment = Dataset()
154
+ segment.SegmentNumber = i + 1
155
+ segment.SegmentLabel = label
156
+ segment.SegmentAlgorithmType = 'MANUAL'
157
+ segment.SegmentAlgorithmName = 'ManualDraw'
158
+
159
+ # Use generic SRT codes for tissue/organ
160
+ segment.SegmentedPropertyCategoryCodeSequence = Sequence([Dataset()])
161
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeValue = 'T-D0050'
162
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodingSchemeDesignator = 'SRT'
163
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeMeaning = 'Tissue'
164
+
165
+ segment.SegmentedPropertyTypeCodeSequence = Sequence([Dataset()])
166
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeValue = 'T-00000'
167
+ segment.SegmentedPropertyTypeCodeSequence[0].CodingSchemeDesignator = '99LOCAL'
168
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeMeaning = label
169
+
170
+ segment_sequence.append(segment)
171
+
172
+ # Mask -> 1-bit packed frame
173
+ mask = masks_dict[label]
174
+ assert mask.shape == (rows, cols), f"Shape mismatch for label '{label}'"
175
+ packed = np.packbits(mask.astype(np.uint8).flatten())
176
+ pixel_data_bytes += packed.tobytes()
177
+
178
+ # Per-frame functional group (Plane Position and Segment Identification)
179
+ fg = Dataset()
180
+
181
+ # Plane Position
182
+ pp = Dataset()
183
+ pp.ImagePositionPatient = [float(origin[0]), float(origin[1]), float(origin[2] + i)]
184
+ fg.PlanePositionSequence = Sequence([pp])
185
+
186
+ # Segment Identification
187
+ si = Dataset()
188
+ si.ReferencedSegmentNumber = i + 1
189
+ fg.SegmentIdentificationSequence = Sequence([si])
190
+
191
+ fg_sequence.append(fg)
192
+
193
+
194
+
195
+ def create_multiframe_segmentation(masks_dict, pixel_spacing=(1.0, 1.0), slice_thickness=1.0, origin=(0.0, 0.0, 0.0)):
196
+ """
197
+ Create a DICOM Segmentation object with multiple frames per segment (e.g., 3D masks).
198
+
199
+ Parameters:
200
+ masks_dict: dict {label: 3D numpy array (Z, Y, X)}
201
+ pixel_spacing: tuple of (row_spacing, col_spacing)
202
+ slice_thickness: float
203
+ origin: tuple of (x, y, z)
204
+ """
205
+ labels = list(masks_dict.keys())
206
+ first_volume = next(iter(masks_dict.values()))
207
+ num_slices, rows, cols = first_volume.shape
208
+
209
+ meta = Dataset()
210
+ meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.66.4'
211
+ meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
212
+ meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
213
+ meta.ImplementationClassUID = pydicom.uid.generate_uid()
214
+
215
+ ds = FileDataset(None, {}, file_meta=meta, preamble=b"\0" * 128)
216
+ ds.is_little_endian = True
217
+ ds.is_implicit_VR = False
218
+
219
+ ds.SOPClassUID = meta.MediaStorageSOPClassUID
220
+ ds.SOPInstanceUID = meta.MediaStorageSOPInstanceUID
221
+ ds.Modality = 'SEG'
222
+ ds.SeriesInstanceUID = pydicom.uid.generate_uid()
223
+ ds.StudyInstanceUID = pydicom.uid.generate_uid()
224
+ ds.FrameOfReferenceUID = pydicom.uid.generate_uid()
225
+ ds.PatientName = 'Seg^3D'
226
+ ds.PatientID = 'SEG3D001'
227
+ dt = datetime.datetime.now()
228
+ ds.ContentDate = dt.strftime('%Y%m%d')
229
+ ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
230
+
231
+ ds.Rows = rows
232
+ ds.Columns = cols
233
+ ds.SamplesPerPixel = 1
234
+ ds.BitsAllocated = 1
235
+ ds.BitsStored = 1
236
+ ds.HighBit = 0
237
+ ds.PixelRepresentation = 0
238
+ ds.PhotometricInterpretation = 'MONOCHROME2'
239
+ ds.SegmentationType = 'BINARY'
240
+ ds.BurnedInAnnotation = 'NO'
241
+ ds.ImageType = ['DERIVED', 'PRIMARY']
242
+ ds.ContentLabel = 'MULTIFRAME_SEG'
243
+ ds.ContentCreatorName = 'AutoGen'
244
+
245
+ ds.NumberOfFrames = num_slices * len(labels)
246
+
247
+ pixel_data_bytes = b''
248
+ segment_sequence = []
249
+ per_frame_sequence = []
250
+
251
+ for seg_index, label in enumerate(labels):
252
+ vol = masks_dict[label]
253
+ assert vol.shape == (num_slices, rows, cols)
254
+
255
+ segment = Dataset()
256
+ segment.SegmentNumber = seg_index + 1
257
+ segment.SegmentLabel = label
258
+ segment.SegmentAlgorithmType = 'MANUAL'
259
+ segment.SegmentAlgorithmName = 'ManualDraw'
260
+
261
+ segment.SegmentedPropertyCategoryCodeSequence = Sequence([Dataset()])
262
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeValue = 'T-D0050'
263
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodingSchemeDesignator = 'SRT'
264
+ segment.SegmentedPropertyCategoryCodeSequence[0].CodeMeaning = 'Tissue'
265
+
266
+ segment.SegmentedPropertyTypeCodeSequence = Sequence([Dataset()])
267
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeValue = 'T-00000'
268
+ segment.SegmentedPropertyTypeCodeSequence[0].CodingSchemeDesignator = '99LOCAL'
269
+ segment.SegmentedPropertyTypeCodeSequence[0].CodeMeaning = label
270
+
271
+ segment_sequence.append(segment)
272
+
273
+ for z in range(num_slices):
274
+ # Pack each slice (frame)
275
+ frame = np.packbits(vol[z].astype(np.uint8).flatten())
276
+ pixel_data_bytes += frame.tobytes()
277
+
278
+ # Functional Group for this frame
279
+ fg = Dataset()
280
+
281
+ # Position
282
+ pos = list(origin)
283
+ pos[2] += z * slice_thickness
284
+ plane = Dataset()
285
+ plane.ImagePositionPatient = [str(v) for v in pos]
286
+ fg.PlanePositionSequence = Sequence([plane])
287
+
288
+ # Segment reference
289
+ seg_id = Dataset()
290
+ seg_id.ReferencedSegmentNumber = seg_index + 1
291
+ fg.SegmentIdentificationSequence = Sequence([seg_id])
292
+
293
+ per_frame_sequence.append(fg)
294
+
295
+ ds.SegmentSequence = Sequence(segment_sequence)
296
+ ds.PixelData = pixel_data_bytes
297
+ ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_sequence)
298
+
299
+ # Shared functional groups
300
+ shared = Dataset()
301
+ geom = Dataset()
302
+ geom.PixelSpacing = [str(pixel_spacing[0]), str(pixel_spacing[1])]
303
+ geom.SliceThickness = str(slice_thickness)
304
+ geom.ImageOrientationPatient = ['1', '0', '0', '0', '1', '0']
305
+ shared.PixelMeasuresSequence = Sequence([geom])
306
+ ds.SharedFunctionalGroupsSequence = Sequence([shared])
307
+
308
+ return ds
309
+
310
+
311
+
@@ -3,23 +3,9 @@
3
3
  import numpy as np
4
4
 
5
5
  import pydicom
6
- from pydicom.dataset import Dataset, FileMetaDataset
6
+ from pydicom.dataset import FileMetaDataset
7
7
  from pydicom.sequence import Sequence
8
8
 
9
- from dbdicom.ds.dataset import DbDataset
10
-
11
- class UltrasoundMultiFrameImage(DbDataset):
12
- def __init__(self, dataset=None, template=None):
13
- super().__init__()
14
-
15
- if (dataset is None) and (template is None):
16
- template = 'DEFAULT'
17
-
18
- if dataset is not None:
19
- self.__dict__ = dataset.__dict__
20
-
21
- if template == 'DEFAULT':
22
- default(self)
23
9
 
24
10
  def default(ds):
25
11
 
@@ -2,25 +2,10 @@
2
2
  # Produced by pydicom codify utility script
3
3
  import numpy as np
4
4
  import pydicom
5
- from pydicom.dataset import Dataset, FileMetaDataset
5
+ from pydicom.dataset import FileMetaDataset
6
6
  from pydicom.sequence import Sequence
7
7
 
8
- from dbdicom.ds.dataset import DbDataset
9
-
10
- class XrayAngiographicImage(DbDataset):
11
- def __init__(self, dataset=None, template=None):
12
- super().__init__()
13
-
14
- if (dataset is None) and (template is None):
15
- template = 'ANGIO'
16
-
17
- if dataset is not None:
18
- self.__dict__ = dataset.__dict__
19
-
20
- if template == 'ANGIO':
21
- angio(self)
22
-
23
- def angio(ds):
8
+ def default(ds):
24
9
 
25
10
  # File meta info data elements
26
11
  ds.file_meta = FileMetaDataset()
@@ -0,0 +1,36 @@
1
+ import numpy as np
2
+
3
+
4
+ def meshvals(coords):
5
+ # Input array shape: (d, f) with d = nr of dims and f = nr of frames
6
+ # Output array shape: (d, f1,..., fd)
7
+ if coords.size == 0:
8
+ return np.array([])
9
+ # Sort by column
10
+ sorted_indices = np.lexsort(coords[::-1])
11
+ sorted_array = coords[:, sorted_indices]
12
+ # Find shape
13
+ shape = _mesh_shape(sorted_array)
14
+ # Reshape
15
+ mesh_array = sorted_array.reshape(shape)
16
+ return mesh_array, sorted_indices
17
+
18
+
19
+ def _mesh_shape(sorted_array):
20
+
21
+ nd = np.unique(sorted_array[0,:]).size
22
+ shape = (sorted_array.shape[0], nd)
23
+
24
+ for dim in range(1,shape[0]):
25
+ shape_dim = (shape[0], np.prod(shape[1:]), -1)
26
+ sorted_array = sorted_array.reshape(shape_dim)
27
+ nd = [np.unique(sorted_array[dim,d,:]).size for d in range(shape_dim[1])]
28
+ shape = shape + (max(nd),)
29
+
30
+ if np.prod(shape) != sorted_array.size:
31
+ raise ValueError(
32
+ 'These are not mesh coordinates.'
33
+ 'Make sure to specify dimensions for a multidimensional series.'
34
+ )
35
+
36
+ return shape
dbdicom/utils/files.py CHANGED
@@ -1,29 +1,9 @@
1
1
  import os
2
2
  import platform
3
3
  import zipfile
4
- import imageio
5
- import numpy as np
6
4
 
7
- from PIL import Image, ImageSequence
8
- import numpy as np
9
5
 
10
6
 
11
- def _load_gif_frames(image: Image, mode='RGBA'):
12
- return np.array([
13
- np.array(frame.convert(mode))
14
- for frame in ImageSequence.Iterator(image)
15
- ])
16
-
17
- def gif2numpy(file):
18
- with Image.open(file) as im:
19
- frames = _load_gif_frames(im)
20
- # GIF files are in RGBA format but assume grescale for now
21
- frames = frames[...,0]
22
- # Transpose in-plane x-y
23
- frames = np.transpose(frames, (0,2,1))
24
- return frames
25
- #return imageio.imread(file)
26
-
27
7
  def all_files(path):
28
8
  files = [item.path for item in scan_tree(path) if item.is_file()]
29
9
  # Windows has maximum path length of 260 - ignore any files that are longer