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,381 @@
1
+ import numpy as np
2
+ import pydicom
3
+ from pydicom.dataset import Dataset, FileDataset
4
+ from pydicom.sequence import Sequence
5
+ from pydicom.uid import generate_uid, ParametricMapStorage
6
+ from datetime import datetime
7
+
8
+
9
+
10
+ def default():
11
+
12
+ rows=16
13
+ cols=16
14
+ frames=1
15
+
16
+ # File Meta Information
17
+ file_meta = Dataset()
18
+ file_meta.MediaStorageSOPClassUID = ParametricMapStorage
19
+ file_meta.MediaStorageSOPInstanceUID = generate_uid()
20
+ file_meta.ImplementationClassUID = generate_uid()
21
+
22
+ # Main Dataset
23
+ ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128)
24
+
25
+ # Required UIDs
26
+ ds.SOPClassUID = ParametricMapStorage
27
+ ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
28
+ ds.StudyInstanceUID = generate_uid()
29
+ ds.SeriesInstanceUID = generate_uid()
30
+ ds.FrameOfReferenceUID = generate_uid()
31
+
32
+ # Patient and Study
33
+ ds.PatientName = "Dummy^Patient"
34
+ ds.PatientID = "123456"
35
+ ds.StudyDate = datetime.now().strftime("%Y%m%d")
36
+ ds.StudyTime = datetime.now().strftime("%H%M%S")
37
+ ds.ContentDate = datetime.now().strftime("%Y%m%d")
38
+ ds.ContentTime = datetime.now().strftime("%H%M%S")
39
+ ds.Modality = "OT"
40
+ ds.Manufacturer = "SyntheticGenerator"
41
+ ds.SeriesDescription = 'Minimal parametric map'
42
+
43
+ # General Image
44
+ ds.SeriesNumber = 1
45
+ ds.InstanceNumber = 1
46
+
47
+ # Parametric Map specifics
48
+ ds.ImageType = ['DERIVED', 'PRIMARY']
49
+ ds.ContentLabel = "PMAP"
50
+ ds.ContentDescription = "Synthetic Parametric Map"
51
+ ds.ContentCreatorName = "dbdicom"
52
+
53
+ # Pixel Data
54
+ ds.NumberOfFrames = frames
55
+ ds.SamplesPerPixel = 1
56
+ ds.PhotometricInterpretation = "MONOCHROME2"
57
+
58
+ ds.Rows = rows
59
+ ds.Columns = cols
60
+ ds.BitsAllocated = 32
61
+ ds.BitsStored = 32
62
+ ds.HighBit = 31
63
+ ds.PixelRepresentation = 1 # 1 = signed, 0 = unsigned
64
+ ds.FloatPixelData = np.zeros((rows, cols), dtype=np.float32).tobytes()
65
+ #ds.PixelData = np.zeros((rows, cols), dtype=np.int16).tobytes()
66
+
67
+ # Required Parametric Map Attributes
68
+ ds.PixelMeasuresSequence = [Dataset()]
69
+ ds.PixelMeasuresSequence[0].SliceThickness = 1.0
70
+ ds.PixelMeasuresSequence[0].PixelSpacing = [1.0, 1.0]
71
+
72
+ ds.FrameOfReferenceUID = pydicom.uid.generate_uid()
73
+
74
+ # Functional Group Sequences (minimal dummy values)
75
+ ds.SharedFunctionalGroupsSequence = [Dataset()]
76
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence = [Dataset()]
77
+ ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].PixelSpacing = [1.0, 1.0]
78
+ ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence = [Dataset()]
79
+ ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence[0].ImageOrientationPatient = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
80
+
81
+ ds.PerFrameFunctionalGroupsSequence = []
82
+ for i in range(ds.NumberOfFrames):
83
+ frame = Dataset()
84
+ frame.PlanePositionSequence = [Dataset()]
85
+ frame.PlanePositionSequence[0].ImagePositionPatient = [0.0, 0.0, float(i)]
86
+ ds.PerFrameFunctionalGroupsSequence.append(frame)
87
+
88
+ return ds
89
+
90
+
91
+
92
+ def set_pixel_data(ds, array):
93
+
94
+ array = np.transpose(array)
95
+ ds.RescaleSlope = 1
96
+ ds.RescaleIntercept = 0
97
+ ds.Rows = array.shape[0]
98
+ ds.Columns = array.shape[1]
99
+
100
+ if array.dtype==np.int16:
101
+ ds.BitsAllocated = 16
102
+ ds.BitsStored = 16
103
+ ds.HighBit = 15
104
+ ds.PixelRepresentation = 1 # signed
105
+ ds.PixelData = array.tobytes()
106
+ elif array.dtype==np.float32:
107
+ ds.BitsAllocated = 32
108
+ ds.BitsStored = 32
109
+ ds.HighBit = 31
110
+ ds.PixelRepresentation = 1 # signed
111
+ ds.FloatPixelData = array.tobytes()
112
+ elif array.dtype==np.float64:
113
+ ds.BitsAllocated = 64
114
+ ds.BitsStored = 64
115
+ ds.HighBit = 63
116
+ ds.PixelRepresentation = 1 # signed
117
+ ds.DoubleFloatPixelData = array.tobytes()
118
+ else:
119
+ raise ValueError(
120
+ f"Parametric map storage currently only available for "
121
+ f"32-bit float, 64-bit float or 16-bit int."
122
+ )
123
+
124
+
125
+ def pixel_data(ds):
126
+
127
+ try:
128
+ array = ds.pixel_array
129
+ except:
130
+ raise ValueError("Dataset has no pixel data.")
131
+
132
+ if ds.PixelRepresentation != 1:
133
+ raise ValueError(
134
+ "Currently only signed integer or floating point supported."
135
+ )
136
+
137
+ slope = float(getattr(ds, 'RescaleSlope', 1))
138
+ intercept = float(getattr(ds, 'RescaleIntercept', 0))
139
+ array *= slope
140
+ array += intercept
141
+ if hasattr(ds, 'PixelData'):
142
+ array = array.astype(np.int16)
143
+ elif hasattr(ds, 'FloatPixelData'):
144
+ array = array.astype(np.float32)
145
+ elif hasattr(ds, 'DoubleFloatPixelData'):
146
+ array = array.astype(np.float64)
147
+ return np.transpose(array)
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+ def create_int16_parametric_map_template(
157
+ rows=128, cols=128,
158
+ num_slices=10, num_custom1=5, num_custom2=3
159
+ ):
160
+ ds = FileDataset(None, {}, file_meta=Dataset(), preamble=b"\0" * 128)
161
+ ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
162
+ ds.is_little_endian = True
163
+ ds.is_implicit_VR = False
164
+
165
+ ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.30' # Parametric Map Storage
166
+ ds.SOPInstanceUID = pydicom.uid.generate_uid()
167
+ ds.Modality = 'OT'
168
+ ds.PatientName = 'Integer^Patient'
169
+ ds.PatientID = 'INT001'
170
+
171
+ ds.Rows = rows
172
+ ds.Columns = cols
173
+ num_frames = num_slices * num_custom1 * num_custom2
174
+ ds.NumberOfFrames = str(num_frames)
175
+
176
+ # Image pixel properties for int16
177
+ ds.PhotometricInterpretation = "MONOCHROME2"
178
+ ds.SamplesPerPixel = 1
179
+ ds.BitsAllocated = 16
180
+ ds.BitsStored = 16
181
+ ds.HighBit = 15
182
+ ds.PixelRepresentation = 1 # 1 = signed integer
183
+
184
+ # Date/time
185
+ dt = datetime.datetime.now()
186
+ ds.ContentDate = dt.strftime('%Y%m%d')
187
+ ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
188
+
189
+ # Dimension Organization
190
+ dim_org = Dataset()
191
+ dim_org.DimensionOrganizationUID = pydicom.uid.generate_uid()
192
+ ds.DimensionOrganizationSequence = Sequence([dim_org])
193
+
194
+ # Dimension Index Sequence
195
+ dim_index_seq = []
196
+
197
+ d1 = Dataset()
198
+ d1.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
199
+ d1.DimensionDescriptionLabel = 'SliceLocation'
200
+ dim_index_seq.append(d1)
201
+
202
+ d2 = Dataset()
203
+ d2.DimensionIndexPointer = (0x0011, 0x1010)
204
+ d2.DimensionDescriptionLabel = 'CustomDim1'
205
+ dim_index_seq.append(d2)
206
+
207
+ d3 = Dataset()
208
+ d3.DimensionIndexPointer = (0x0011, 0x1020)
209
+ d3.DimensionDescriptionLabel = 'CustomDim2'
210
+ dim_index_seq.append(d3)
211
+
212
+ ds.DimensionIndexSequence = Sequence(dim_index_seq)
213
+
214
+ # Shared Functional Groups
215
+ shared_fg = Dataset()
216
+
217
+ pm = Dataset()
218
+ pm.PixelSpacing = [1.0, 1.0]
219
+ pm.SliceThickness = 1.0
220
+ shared_fg.PixelMeasuresSequence = Sequence([pm])
221
+
222
+ po = Dataset()
223
+ po.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
224
+ shared_fg.PlaneOrientationSequence = Sequence([po])
225
+
226
+ ds.SharedFunctionalGroupsSequence = Sequence([shared_fg])
227
+
228
+ # Per Frame Functional Groups Sequence
229
+ per_frame_seq = []
230
+ for slice_idx in range(num_slices):
231
+ for custom1_idx in range(num_custom1):
232
+ for custom2_idx in range(num_custom2):
233
+ fg = Dataset()
234
+
235
+ # Plane Position Sequence
236
+ pp = Dataset()
237
+ pp.ImagePositionPatient = [0.0, 0.0, float(slice_idx * 5)]
238
+ fg.PlanePositionSequence = Sequence([pp])
239
+
240
+ # Custom dimension values (private tags)
241
+ fg.add_new((0x0011, 0x1010), 'LO', str(custom1_idx))
242
+ fg.add_new((0x0011, 0x1020), 'LO', str(custom2_idx))
243
+
244
+ per_frame_seq.append(fg)
245
+
246
+ ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_seq)
247
+
248
+ # Create int16 pixel data (dummy values)
249
+ pixel_array = np.zeros((num_frames, rows, cols), dtype=np.int16)
250
+ ds.PixelData = pixel_array.tobytes()
251
+
252
+ # Optional: Real World Value Mapping (for scaled physical interpretation)
253
+ rwvm = Dataset()
254
+ rwvm.RealWorldValueIntercept = 0.0 # to convert stored values to real-world values
255
+ rwvm.RealWorldValueSlope = 1.0
256
+ rwvm.LUTLabel = 'IntegerMap' # "T1_Mapping", "Perfusion", etc
257
+ unit_code = Dataset()
258
+ unit_code.CodeValue = 'kPa'
259
+ unit_code.CodingSchemeDesignator = 'UCUM'
260
+ unit_code.CodeMeaning = 'kilopascal'
261
+ rwvm.MeasurementUnitsCodeSequence = Sequence([unit_code])
262
+ ds.RealWorldValueMappingSequence = Sequence([rwvm])
263
+
264
+ return ds
265
+
266
+
267
+
268
+
269
+
270
+ def create_float32_parametric_map_template(
271
+ rows=128, cols=128,
272
+ num_slices=10, num_custom1=5, num_custom2=3
273
+ ):
274
+ ds = FileDataset(None, {}, file_meta=Dataset(), preamble=b"\0" * 128)
275
+ ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
276
+ ds.is_little_endian = True
277
+ ds.is_implicit_VR = False
278
+
279
+ ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.30' # Parametric Map Storage
280
+ ds.SOPInstanceUID = pydicom.uid.generate_uid()
281
+ ds.Modality = 'OT'
282
+ ds.PatientName = 'Float^Patient'
283
+ ds.PatientID = 'FLOAT001'
284
+
285
+ ds.Rows = rows
286
+ ds.Columns = cols
287
+ num_frames = num_slices * num_custom1 * num_custom2
288
+ ds.NumberOfFrames = str(num_frames)
289
+
290
+ # Image pixel properties for float32
291
+ ds.PhotometricInterpretation = "MONOCHROME2"
292
+ ds.SamplesPerPixel = 1
293
+ ds.BitsAllocated = 32
294
+ ds.BitsStored = 32
295
+ ds.HighBit = 31
296
+ ds.PixelRepresentation = 0 # 0 = unsigned (for float32)
297
+
298
+ # Date/time
299
+ dt = datetime.datetime.now()
300
+ ds.ContentDate = dt.strftime('%Y%m%d')
301
+ ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
302
+
303
+ # Dimension Organization
304
+ dim_org = Dataset()
305
+ dim_org.DimensionOrganizationUID = pydicom.uid.generate_uid()
306
+ ds.DimensionOrganizationSequence = Sequence([dim_org])
307
+
308
+ # Dimension Index Sequence
309
+ dim_index_seq = []
310
+
311
+ d1 = Dataset()
312
+ d1.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
313
+ d1.DimensionDescriptionLabel = 'SliceLocation'
314
+ dim_index_seq.append(d1)
315
+
316
+ d2 = Dataset()
317
+ d2.DimensionIndexPointer = (0x0011, 0x1010)
318
+ d2.DimensionDescriptionLabel = 'CustomDim1'
319
+ dim_index_seq.append(d2)
320
+
321
+ d3 = Dataset()
322
+ d3.DimensionIndexPointer = (0x0011, 0x1020)
323
+ d3.DimensionDescriptionLabel = 'CustomDim2'
324
+ dim_index_seq.append(d3)
325
+
326
+ ds.DimensionIndexSequence = Sequence(dim_index_seq)
327
+
328
+ # Shared Functional Groups
329
+ shared_fg = Dataset()
330
+
331
+ pm = Dataset()
332
+ pm.PixelSpacing = [1.0, 1.0]
333
+ pm.SliceThickness = 1.0
334
+ shared_fg.PixelMeasuresSequence = Sequence([pm])
335
+
336
+ po = Dataset()
337
+ po.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
338
+ shared_fg.PlaneOrientationSequence = Sequence([po])
339
+
340
+ ds.SharedFunctionalGroupsSequence = Sequence([shared_fg])
341
+
342
+ # Per Frame Functional Groups Sequence
343
+ per_frame_seq = []
344
+ for slice_idx in range(num_slices):
345
+ for custom1_idx in range(num_custom1):
346
+ for custom2_idx in range(num_custom2):
347
+ fg = Dataset()
348
+
349
+ # Plane Position Sequence
350
+ pp = Dataset()
351
+ pp.ImagePositionPatient = [0.0, 0.0, float(slice_idx * 5)]
352
+ fg.PlanePositionSequence = Sequence([pp])
353
+
354
+ # Custom dimension values (private tags)
355
+ fg.add_new((0x0011, 0x1010), 'LO', str(custom1_idx))
356
+ fg.add_new((0x0011, 0x1020), 'LO', str(custom2_idx))
357
+
358
+ per_frame_seq.append(fg)
359
+
360
+ ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_seq)
361
+
362
+ # Create float32 pixel data (dummy)
363
+ pixel_array = np.zeros((num_frames, rows, cols), dtype=np.float32)
364
+ ds.PixelData = pixel_array.tobytes()
365
+
366
+ # Optional: Real World Value Mapping
367
+ rwvm = Dataset()
368
+ rwvm.RealWorldValueIntercept = 0.0
369
+ rwvm.RealWorldValueSlope = 1.0
370
+ rwvm.LUTLabel = 'FloatMap'
371
+ unit_code = Dataset()
372
+ unit_code.CodeValue = '1'
373
+ unit_code.CodingSchemeDesignator = 'UCUM'
374
+ unit_code.CodeMeaning = 'no units'
375
+ rwvm.MeasurementUnitsCodeSequence = Sequence([unit_code])
376
+ ds.RealWorldValueMappingSequence = Sequence([rwvm])
377
+
378
+ return ds
379
+
380
+
381
+
@@ -0,0 +1,140 @@
1
+ import numpy as np
2
+ import pydicom
3
+ from pydicom.dataset import Dataset, FileDataset, FileMetaDataset
4
+ from pydicom.uid import SecondaryCaptureImageStorage, generate_uid, ExplicitVRLittleEndian
5
+ from datetime import datetime
6
+ from pydicom.sequence import Sequence
7
+
8
+
9
+
10
+ def create_3d_secondary_capture_dataset_with_dimensions(depth=16, rows=256, cols=256, pixel_volume=None):
11
+ now = datetime.now()
12
+
13
+ # File Meta
14
+ file_meta = FileMetaDataset()
15
+ file_meta.MediaStorageSOPClassUID = SecondaryCaptureImageStorage
16
+ file_meta.MediaStorageSOPInstanceUID = generate_uid()
17
+ file_meta.ImplementationClassUID = generate_uid()
18
+ file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
19
+
20
+ # Dataset
21
+ ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128)
22
+ ds.is_little_endian = True
23
+ ds.is_implicit_VR = False
24
+
25
+ # Patient & Study Info
26
+ ds.PatientName = "SC^ThreeD"
27
+ ds.PatientID = "3D123456"
28
+ ds.StudyInstanceUID = generate_uid()
29
+ ds.SeriesInstanceUID = generate_uid()
30
+ ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
31
+ ds.SOPClassUID = SecondaryCaptureImageStorage
32
+ ds.StudyDate = now.strftime("%Y%m%d")
33
+ ds.StudyTime = now.strftime("%H%M%S")
34
+ ds.Modality = "OT"
35
+ ds.Manufacturer = "PythonSC"
36
+
37
+ ds.SeriesNumber = 1
38
+ ds.InstanceNumber = 1
39
+
40
+ # Image Attributes
41
+ ds.SamplesPerPixel = 1
42
+ ds.PhotometricInterpretation = "MONOCHROME2"
43
+ ds.Rows = rows
44
+ ds.Columns = cols
45
+ ds.BitsAllocated = 16
46
+ ds.BitsStored = 12
47
+ ds.HighBit = 11
48
+ ds.PixelRepresentation = 0
49
+ ds.NumberOfFrames = str(depth)
50
+ ds.ImageType = ["DERIVED", "SECONDARY"]
51
+
52
+ # Pixel Data
53
+ if pixel_volume is None:
54
+ pixel_volume = np.random.randint(0, 4095, size=(depth, rows, cols), dtype=np.uint16)
55
+ ds.PixelData = pixel_volume.tobytes()
56
+
57
+ # === DIMENSIONS ===
58
+ dim_uid = generate_uid()
59
+
60
+ ds.DimensionOrganizationSequence = Sequence([
61
+ Dataset()
62
+ ])
63
+ ds.DimensionOrganizationSequence[0].DimensionOrganizationUID = dim_uid
64
+
65
+ # Define 1 dimension: slice index (z-dimension)
66
+ dim_index = Dataset()
67
+ dim_index.DimensionOrganizationUID = dim_uid
68
+ dim_index.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
69
+ dim_index.FunctionalGroupPointer = 0x00209113 # PlanePositionSequence
70
+
71
+ ds.DimensionIndexSequence = Sequence([dim_index])
72
+
73
+ # Per-Frame Functional Groups
74
+ ds.PerFrameFunctionalGroupsSequence = Sequence()
75
+
76
+ for z in range(depth):
77
+ frame = Dataset()
78
+
79
+ # Plane position sequence with slice position
80
+ pos = Dataset()
81
+ pos.ImagePositionPatient = [0.0, 0.0, float(z)] # Simple linear z spacing
82
+ frame.PlanePositionSequence = Sequence([pos])
83
+
84
+ ds.PerFrameFunctionalGroupsSequence.append(frame)
85
+
86
+ return ds
87
+
88
+
89
+
90
+ def create_3d_secondary_capture_dataset(depth=16, rows=256, cols=256, pixel_volume=None):
91
+ now = datetime.now()
92
+ total_frames = depth
93
+
94
+ # File Meta
95
+ file_meta = pydicom.dataset.FileMetaDataset()
96
+ file_meta.MediaStorageSOPClassUID = SecondaryCaptureImageStorage
97
+ file_meta.MediaStorageSOPInstanceUID = generate_uid()
98
+ file_meta.ImplementationClassUID = generate_uid()
99
+ file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
100
+
101
+ # FileDataset
102
+ ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128)
103
+ ds.is_little_endian = True
104
+ ds.is_implicit_VR = False
105
+
106
+ # Basic metadata
107
+ ds.SOPClassUID = SecondaryCaptureImageStorage
108
+ ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
109
+ ds.PatientName = "SC^ThreeD"
110
+ ds.PatientID = "3D123456"
111
+ ds.StudyInstanceUID = generate_uid()
112
+ ds.SeriesInstanceUID = generate_uid()
113
+ ds.StudyDate = now.strftime("%Y%m%d")
114
+ ds.StudyTime = now.strftime("%H%M%S")
115
+ ds.Modality = "OT"
116
+ ds.Manufacturer = "PythonGenerator"
117
+
118
+ ds.SeriesNumber = 1
119
+ ds.InstanceNumber = 1
120
+
121
+ # Image attributes
122
+ ds.SamplesPerPixel = 1
123
+ ds.PhotometricInterpretation = "MONOCHROME2"
124
+ ds.Rows = rows
125
+ ds.Columns = cols
126
+ ds.BitsAllocated = 16
127
+ ds.BitsStored = 12
128
+ ds.HighBit = 11
129
+ ds.PixelRepresentation = 0
130
+ ds.NumberOfFrames = str(total_frames)
131
+ ds.ImageType = ["DERIVED", "SECONDARY"]
132
+
133
+ # Dummy or real 3D pixel data
134
+ if pixel_volume is None:
135
+ pixel_volume = np.random.randint(0, 4095, size=(depth, rows, cols), dtype=np.uint16)
136
+
137
+ ds.PixelData = pixel_volume.tobytes()
138
+
139
+ return ds
140
+