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
@@ -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,142 @@
1
+ import numpy as np
2
+
3
+ from typing import List, Tuple
4
+
5
+ def to_array_list(values):
6
+ arrays = []
7
+ for v in values:
8
+ if np.isscalar(v[0]):
9
+ v_arr = np.array(v)
10
+ else:
11
+ v_arr = np.empty(len(v), dtype=object)
12
+ v_arr[:] = v
13
+ arrays.append(v_arr)
14
+ return arrays
15
+
16
+
17
+ def meshvals(values) -> Tuple[List[np.ndarray], np.ndarray]:
18
+ """
19
+ Lexicographically sort flattened N coordinate arrays and reshape back to inferred grid shape,
20
+ preserving original type of each input array.
21
+
22
+ Parameters
23
+ ----------
24
+ *arrays : array-like
25
+ Flattened coordinate arrays of the same length. Can be numbers, strings, or list objects.
26
+
27
+ Returns
28
+ -------
29
+ sorted_arrays : list[np.ndarray]
30
+ Coordinate arrays reshaped to inferred N-D grid shape, dtype/type preserved.
31
+ indices : np.ndarray
32
+ Permutation indices applied to the flattened arrays.
33
+ shape : tuple[int, ...]
34
+ Inferred grid shape (number of unique values per axis).
35
+ """
36
+ # Ensure the list elements are arrays
37
+ arrays = to_array_list(values)
38
+
39
+ # Remember original type/dtype for each array
40
+ orig_types = [a.dtype if isinstance(a[0], np.ndarray) else type(a[0]) for a in arrays]
41
+
42
+ # Convert non arrays to object arrays
43
+ arrs = []
44
+ for a in arrays:
45
+ arrs_a = np.empty(len(a), dtype=object)
46
+ arrs_a[:] = a
47
+ arrs.append(arrs_a)
48
+
49
+ # Stack arrays as columns (M x N)
50
+ coords = np.stack(arrs, axis=1)
51
+
52
+ # Lexicographic sort using structured array
53
+ indices = np.lexsort(coords.T[::-1])
54
+ sorted_coords = coords[indices]
55
+
56
+ # Check that all coordinates are unique
57
+ points = [tuple(col) for col in sorted_coords]
58
+ if not all_elements_unique(points):
59
+ raise ValueError(
60
+ f"Improper coordinates. Coordinate values are not unique."
61
+ )
62
+
63
+ # Infer shape from unique values per axis
64
+ shape = tuple(len(np.unique(sorted_coords[:, i])) for i in range(sorted_coords.shape[1]))
65
+
66
+ # Check perfect grid
67
+ if np.prod(shape) != sorted_coords.shape[0]:
68
+ raise ValueError(
69
+ f"Coordinates do not form a perfect Cartesian grid: inferred shape {shape} "
70
+ f"does not match number of points {sorted_coords.shape[0]}"
71
+ )
72
+
73
+ # Split back into individual arrays and cast to original type
74
+ sorted_arrays = []
75
+ for i, orig_type in enumerate(orig_types):
76
+ arr = sorted_coords[:, i]
77
+ arr = arr.astype(orig_type).reshape(shape)
78
+ sorted_arrays.append(arr)
79
+
80
+ return sorted_arrays, indices
81
+
82
+
83
+ def all_elements_unique(items):
84
+ """
85
+ The most general uniqueness check, but also the slowest (O(n^2)).
86
+
87
+ It works for ANY type that supports equality checking (==), including
88
+ lists, dicts, and custom objects, without requiring them to be hashable.
89
+ """
90
+ for i in range(len(items)):
91
+ for j in range(i + 1, len(items)):
92
+ if items[i] == items[j]:
93
+ return False
94
+ return True
95
+
96
+
97
+
98
+ # def NEWmeshvals(coords):
99
+ # stack_coords = [np.array(c, dtype=object) for c in coords]
100
+ # stack_coords = np.stack(stack_coords)
101
+ # mesh_coords, sorted_indices = _meshvals(stack_coords)
102
+ # mesh_coords = [mesh_coords[d,...] for d in range(mesh_coords.shape[0])]
103
+ # return mesh_coords, sorted_indices
104
+
105
+
106
+ # def _meshvals(coords):
107
+ # # Input array shape: (d, f) with d = nr of dims and f = nr of frames
108
+ # # Output array shape: (d, f1,..., fd)
109
+ # if coords.size == 0:
110
+ # return np.array([])
111
+ # # Sort by column
112
+ # sorted_indices = np.lexsort(coords[::-1])
113
+ # sorted_array = coords[:, sorted_indices]
114
+ # # Find shape
115
+ # shape = _mesh_shape(sorted_array)
116
+ # # Reshape
117
+ # mesh_array = sorted_array.reshape(shape)
118
+ # return mesh_array, sorted_indices
119
+
120
+
121
+ # def _mesh_shape(sorted_array):
122
+
123
+ # nd = np.unique(sorted_array[0,:]).size
124
+ # shape = (sorted_array.shape[0], nd)
125
+
126
+ # for dim in range(1,shape[0]):
127
+ # shape_dim = (shape[0], np.prod(shape[1:]), -1)
128
+ # sorted_array = sorted_array.reshape(shape_dim)
129
+ # nd = [np.unique(sorted_array[dim,d,:]).size for d in range(shape_dim[1])]
130
+ # shape = shape + (max(nd),)
131
+
132
+ # if np.prod(shape) != sorted_array.size:
133
+ # raise ValueError(
134
+ # 'Improper dimensions for the series. This usually means '
135
+ # 'that there are multiple images at the same location, \n or that '
136
+ # 'there are no images at one or more locations. \n\n'
137
+ # 'Make sure to specify proper dimensions when reading a pixel array or volume. \n'
138
+ # 'If the default dimensions of pixel_array (InstanceNumber) generate this error, '
139
+ # 'the DICOM data may be corrupted.'
140
+ # )
141
+
142
+ # 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