dbdicom 0.2.5__py3-none-any.whl → 0.3.0__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.
- dbdicom/__init__.py +1 -28
- dbdicom/api.py +267 -0
- dbdicom/const.py +144 -0
- dbdicom/dataset.py +752 -0
- dbdicom/dbd.py +719 -0
- dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/register.py +527 -0
- dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
- dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +153 -26
- dbdicom/{ds/types → sop_classes}/mr_image.py +185 -140
- dbdicom/sop_classes/parametric_map.py +307 -0
- dbdicom/sop_classes/secondary_capture.py +140 -0
- dbdicom/sop_classes/segmentation.py +311 -0
- dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
- dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
- dbdicom/utils/arrays.py +36 -0
- dbdicom/utils/files.py +0 -20
- dbdicom/utils/image.py +10 -629
- dbdicom-0.3.0.dist-info/METADATA +28 -0
- dbdicom-0.3.0.dist-info/RECORD +53 -0
- {dbdicom-0.2.5.dist-info → dbdicom-0.3.0.dist-info}/WHEEL +1 -1
- dbdicom/create.py +0 -457
- dbdicom/dro.py +0 -174
- dbdicom/ds/__init__.py +0 -10
- dbdicom/ds/create.py +0 -63
- dbdicom/ds/dataset.py +0 -869
- dbdicom/ds/dictionaries.py +0 -620
- dbdicom/ds/types/parametric_map.py +0 -226
- dbdicom/extensions/__init__.py +0 -9
- dbdicom/extensions/dipy.py +0 -448
- dbdicom/extensions/elastix.py +0 -503
- dbdicom/extensions/matplotlib.py +0 -107
- dbdicom/extensions/numpy.py +0 -271
- dbdicom/extensions/scipy.py +0 -1512
- dbdicom/extensions/skimage.py +0 -1030
- dbdicom/extensions/sklearn.py +0 -243
- dbdicom/extensions/vreg.py +0 -1390
- dbdicom/manager.py +0 -2132
- dbdicom/message.py +0 -119
- dbdicom/pipelines.py +0 -66
- dbdicom/record.py +0 -1893
- dbdicom/types/database.py +0 -107
- dbdicom/types/instance.py +0 -231
- dbdicom/types/patient.py +0 -40
- dbdicom/types/series.py +0 -2874
- dbdicom/types/study.py +0 -58
- dbdicom-0.2.5.dist-info/METADATA +0 -71
- dbdicom-0.2.5.dist-info/RECORD +0 -66
- {dbdicom-0.2.5.dist-info → dbdicom-0.3.0.dist-info/licenses}/LICENSE +0 -0
- {dbdicom-0.2.5.dist-info → dbdicom-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
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 create_parametric_map(rows=64, cols=64, frames=1):
|
|
11
|
+
# Create dummy pixel data (floating point)
|
|
12
|
+
pixel_array = np.random.rand(frames, rows, cols).astype(np.float32)
|
|
13
|
+
|
|
14
|
+
# File Meta Information
|
|
15
|
+
file_meta = Dataset()
|
|
16
|
+
file_meta.MediaStorageSOPClassUID = ParametricMapStorage
|
|
17
|
+
file_meta.MediaStorageSOPInstanceUID = generate_uid()
|
|
18
|
+
file_meta.ImplementationClassUID = generate_uid()
|
|
19
|
+
|
|
20
|
+
# Main Dataset
|
|
21
|
+
ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\0" * 128)
|
|
22
|
+
|
|
23
|
+
# Required UIDs
|
|
24
|
+
ds.SOPClassUID = ParametricMapStorage
|
|
25
|
+
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
|
|
26
|
+
ds.StudyInstanceUID = generate_uid()
|
|
27
|
+
ds.SeriesInstanceUID = generate_uid()
|
|
28
|
+
ds.FrameOfReferenceUID = generate_uid()
|
|
29
|
+
|
|
30
|
+
# Patient and Study
|
|
31
|
+
ds.PatientName = "Dummy^Patient"
|
|
32
|
+
ds.PatientID = "123456"
|
|
33
|
+
ds.StudyDate = datetime.now().strftime("%Y%m%d")
|
|
34
|
+
ds.StudyTime = datetime.now().strftime("%H%M%S")
|
|
35
|
+
ds.Modality = "OT"
|
|
36
|
+
ds.Manufacturer = "SyntheticGenerator"
|
|
37
|
+
|
|
38
|
+
# General Image
|
|
39
|
+
ds.SeriesNumber = 1
|
|
40
|
+
ds.InstanceNumber = 1
|
|
41
|
+
|
|
42
|
+
# Parametric Map specifics
|
|
43
|
+
ds.ImageType = ['DERIVED', 'PRIMARY']
|
|
44
|
+
ds.ContentLabel = "PMAP"
|
|
45
|
+
ds.ContentDescription = "Synthetic Parametric Map"
|
|
46
|
+
ds.ContentCreatorName = "OpenAI"
|
|
47
|
+
|
|
48
|
+
# Pixel Data
|
|
49
|
+
ds.Rows = rows
|
|
50
|
+
ds.Columns = cols
|
|
51
|
+
ds.NumberOfFrames = frames
|
|
52
|
+
ds.PixelData = pixel_array.tobytes()
|
|
53
|
+
ds.SamplesPerPixel = 1
|
|
54
|
+
ds.PhotometricInterpretation = "MONOCHROME2"
|
|
55
|
+
ds.BitsAllocated = 32
|
|
56
|
+
ds.BitsStored = 32
|
|
57
|
+
ds.HighBit = 31
|
|
58
|
+
ds.PixelRepresentation = 1 # 1 = signed, 0 = unsigned
|
|
59
|
+
ds.FloatPixelData = pixel_array.astype(np.float32).tobytes()
|
|
60
|
+
ds.PixelData = b'' # Actual data goes in FloatPixelData
|
|
61
|
+
|
|
62
|
+
# Functional Group Sequences (minimal dummy values)
|
|
63
|
+
ds.SharedFunctionalGroupsSequence = [Dataset()]
|
|
64
|
+
ds.PerFrameFunctionalGroupsSequence = [Dataset() for _ in range(frames)]
|
|
65
|
+
|
|
66
|
+
# Add dummy Dimension Organization
|
|
67
|
+
ds.DimensionOrganizationSequence = [Dataset()]
|
|
68
|
+
ds.DimensionOrganizationSequence[0].DimensionOrganizationUID = generate_uid()
|
|
69
|
+
|
|
70
|
+
ds.DimensionIndexSequence = [
|
|
71
|
+
Dataset() for _ in range(1)
|
|
72
|
+
]
|
|
73
|
+
ds.DimensionIndexSequence[0].DimensionOrganizationUID = ds.DimensionOrganizationSequence[0].DimensionOrganizationUID
|
|
74
|
+
ds.DimensionIndexSequence[0].DimensionIndexPointer = (0x0020, 0x9157) # In-stack position
|
|
75
|
+
ds.DimensionIndexSequence[0].FunctionalGroupPointer = (0x0020, 0x9116)
|
|
76
|
+
|
|
77
|
+
return ds
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def create_int16_parametric_map_template(
|
|
83
|
+
rows=128, cols=128,
|
|
84
|
+
num_slices=10, num_custom1=5, num_custom2=3
|
|
85
|
+
):
|
|
86
|
+
ds = FileDataset(None, {}, file_meta=Dataset(), preamble=b"\0" * 128)
|
|
87
|
+
ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
|
|
88
|
+
ds.is_little_endian = True
|
|
89
|
+
ds.is_implicit_VR = False
|
|
90
|
+
|
|
91
|
+
ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.30' # Parametric Map Storage
|
|
92
|
+
ds.SOPInstanceUID = pydicom.uid.generate_uid()
|
|
93
|
+
ds.Modality = 'OT'
|
|
94
|
+
ds.PatientName = 'Integer^Patient'
|
|
95
|
+
ds.PatientID = 'INT001'
|
|
96
|
+
|
|
97
|
+
ds.Rows = rows
|
|
98
|
+
ds.Columns = cols
|
|
99
|
+
num_frames = num_slices * num_custom1 * num_custom2
|
|
100
|
+
ds.NumberOfFrames = str(num_frames)
|
|
101
|
+
|
|
102
|
+
# Image pixel properties for int16
|
|
103
|
+
ds.PhotometricInterpretation = "MONOCHROME2"
|
|
104
|
+
ds.SamplesPerPixel = 1
|
|
105
|
+
ds.BitsAllocated = 16
|
|
106
|
+
ds.BitsStored = 16
|
|
107
|
+
ds.HighBit = 15
|
|
108
|
+
ds.PixelRepresentation = 1 # 1 = signed integer
|
|
109
|
+
|
|
110
|
+
# Date/time
|
|
111
|
+
dt = datetime.datetime.now()
|
|
112
|
+
ds.ContentDate = dt.strftime('%Y%m%d')
|
|
113
|
+
ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
|
|
114
|
+
|
|
115
|
+
# Dimension Organization
|
|
116
|
+
dim_org = Dataset()
|
|
117
|
+
dim_org.DimensionOrganizationUID = pydicom.uid.generate_uid()
|
|
118
|
+
ds.DimensionOrganizationSequence = Sequence([dim_org])
|
|
119
|
+
|
|
120
|
+
# Dimension Index Sequence
|
|
121
|
+
dim_index_seq = []
|
|
122
|
+
|
|
123
|
+
d1 = Dataset()
|
|
124
|
+
d1.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
|
|
125
|
+
d1.DimensionDescriptionLabel = 'SliceLocation'
|
|
126
|
+
dim_index_seq.append(d1)
|
|
127
|
+
|
|
128
|
+
d2 = Dataset()
|
|
129
|
+
d2.DimensionIndexPointer = (0x0011, 0x1010)
|
|
130
|
+
d2.DimensionDescriptionLabel = 'CustomDim1'
|
|
131
|
+
dim_index_seq.append(d2)
|
|
132
|
+
|
|
133
|
+
d3 = Dataset()
|
|
134
|
+
d3.DimensionIndexPointer = (0x0011, 0x1020)
|
|
135
|
+
d3.DimensionDescriptionLabel = 'CustomDim2'
|
|
136
|
+
dim_index_seq.append(d3)
|
|
137
|
+
|
|
138
|
+
ds.DimensionIndexSequence = Sequence(dim_index_seq)
|
|
139
|
+
|
|
140
|
+
# Shared Functional Groups
|
|
141
|
+
shared_fg = Dataset()
|
|
142
|
+
|
|
143
|
+
pm = Dataset()
|
|
144
|
+
pm.PixelSpacing = [1.0, 1.0]
|
|
145
|
+
pm.SliceThickness = 1.0
|
|
146
|
+
shared_fg.PixelMeasuresSequence = Sequence([pm])
|
|
147
|
+
|
|
148
|
+
po = Dataset()
|
|
149
|
+
po.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
|
|
150
|
+
shared_fg.PlaneOrientationSequence = Sequence([po])
|
|
151
|
+
|
|
152
|
+
ds.SharedFunctionalGroupsSequence = Sequence([shared_fg])
|
|
153
|
+
|
|
154
|
+
# Per Frame Functional Groups Sequence
|
|
155
|
+
per_frame_seq = []
|
|
156
|
+
for slice_idx in range(num_slices):
|
|
157
|
+
for custom1_idx in range(num_custom1):
|
|
158
|
+
for custom2_idx in range(num_custom2):
|
|
159
|
+
fg = Dataset()
|
|
160
|
+
|
|
161
|
+
# Plane Position Sequence
|
|
162
|
+
pp = Dataset()
|
|
163
|
+
pp.ImagePositionPatient = [0.0, 0.0, float(slice_idx * 5)]
|
|
164
|
+
fg.PlanePositionSequence = Sequence([pp])
|
|
165
|
+
|
|
166
|
+
# Custom dimension values (private tags)
|
|
167
|
+
fg.add_new((0x0011, 0x1010), 'LO', str(custom1_idx))
|
|
168
|
+
fg.add_new((0x0011, 0x1020), 'LO', str(custom2_idx))
|
|
169
|
+
|
|
170
|
+
per_frame_seq.append(fg)
|
|
171
|
+
|
|
172
|
+
ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_seq)
|
|
173
|
+
|
|
174
|
+
# Create int16 pixel data (dummy values)
|
|
175
|
+
pixel_array = np.zeros((num_frames, rows, cols), dtype=np.int16)
|
|
176
|
+
ds.PixelData = pixel_array.tobytes()
|
|
177
|
+
|
|
178
|
+
# Optional: Real World Value Mapping (for scaled physical interpretation)
|
|
179
|
+
rwvm = Dataset()
|
|
180
|
+
rwvm.RealWorldValueIntercept = 0.0 # to convert stored values to real-world values
|
|
181
|
+
rwvm.RealWorldValueSlope = 1.0
|
|
182
|
+
rwvm.LUTLabel = 'IntegerMap' # "T1_Mapping", "Perfusion", etc
|
|
183
|
+
unit_code = Dataset()
|
|
184
|
+
unit_code.CodeValue = 'kPa'
|
|
185
|
+
unit_code.CodingSchemeDesignator = 'UCUM'
|
|
186
|
+
unit_code.CodeMeaning = 'kilopascal'
|
|
187
|
+
rwvm.MeasurementUnitsCodeSequence = Sequence([unit_code])
|
|
188
|
+
ds.RealWorldValueMappingSequence = Sequence([rwvm])
|
|
189
|
+
|
|
190
|
+
return ds
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def create_float32_parametric_map_template(
|
|
197
|
+
rows=128, cols=128,
|
|
198
|
+
num_slices=10, num_custom1=5, num_custom2=3
|
|
199
|
+
):
|
|
200
|
+
ds = FileDataset(None, {}, file_meta=Dataset(), preamble=b"\0" * 128)
|
|
201
|
+
ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian
|
|
202
|
+
ds.is_little_endian = True
|
|
203
|
+
ds.is_implicit_VR = False
|
|
204
|
+
|
|
205
|
+
ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.30' # Parametric Map Storage
|
|
206
|
+
ds.SOPInstanceUID = pydicom.uid.generate_uid()
|
|
207
|
+
ds.Modality = 'OT'
|
|
208
|
+
ds.PatientName = 'Float^Patient'
|
|
209
|
+
ds.PatientID = 'FLOAT001'
|
|
210
|
+
|
|
211
|
+
ds.Rows = rows
|
|
212
|
+
ds.Columns = cols
|
|
213
|
+
num_frames = num_slices * num_custom1 * num_custom2
|
|
214
|
+
ds.NumberOfFrames = str(num_frames)
|
|
215
|
+
|
|
216
|
+
# Image pixel properties for float32
|
|
217
|
+
ds.PhotometricInterpretation = "MONOCHROME2"
|
|
218
|
+
ds.SamplesPerPixel = 1
|
|
219
|
+
ds.BitsAllocated = 32
|
|
220
|
+
ds.BitsStored = 32
|
|
221
|
+
ds.HighBit = 31
|
|
222
|
+
ds.PixelRepresentation = 0 # 0 = unsigned (for float32)
|
|
223
|
+
|
|
224
|
+
# Date/time
|
|
225
|
+
dt = datetime.datetime.now()
|
|
226
|
+
ds.ContentDate = dt.strftime('%Y%m%d')
|
|
227
|
+
ds.ContentTime = dt.strftime('%H%M%S.%f')[:13]
|
|
228
|
+
|
|
229
|
+
# Dimension Organization
|
|
230
|
+
dim_org = Dataset()
|
|
231
|
+
dim_org.DimensionOrganizationUID = pydicom.uid.generate_uid()
|
|
232
|
+
ds.DimensionOrganizationSequence = Sequence([dim_org])
|
|
233
|
+
|
|
234
|
+
# Dimension Index Sequence
|
|
235
|
+
dim_index_seq = []
|
|
236
|
+
|
|
237
|
+
d1 = Dataset()
|
|
238
|
+
d1.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
|
|
239
|
+
d1.DimensionDescriptionLabel = 'SliceLocation'
|
|
240
|
+
dim_index_seq.append(d1)
|
|
241
|
+
|
|
242
|
+
d2 = Dataset()
|
|
243
|
+
d2.DimensionIndexPointer = (0x0011, 0x1010)
|
|
244
|
+
d2.DimensionDescriptionLabel = 'CustomDim1'
|
|
245
|
+
dim_index_seq.append(d2)
|
|
246
|
+
|
|
247
|
+
d3 = Dataset()
|
|
248
|
+
d3.DimensionIndexPointer = (0x0011, 0x1020)
|
|
249
|
+
d3.DimensionDescriptionLabel = 'CustomDim2'
|
|
250
|
+
dim_index_seq.append(d3)
|
|
251
|
+
|
|
252
|
+
ds.DimensionIndexSequence = Sequence(dim_index_seq)
|
|
253
|
+
|
|
254
|
+
# Shared Functional Groups
|
|
255
|
+
shared_fg = Dataset()
|
|
256
|
+
|
|
257
|
+
pm = Dataset()
|
|
258
|
+
pm.PixelSpacing = [1.0, 1.0]
|
|
259
|
+
pm.SliceThickness = 1.0
|
|
260
|
+
shared_fg.PixelMeasuresSequence = Sequence([pm])
|
|
261
|
+
|
|
262
|
+
po = Dataset()
|
|
263
|
+
po.ImageOrientationPatient = [1, 0, 0, 0, 1, 0]
|
|
264
|
+
shared_fg.PlaneOrientationSequence = Sequence([po])
|
|
265
|
+
|
|
266
|
+
ds.SharedFunctionalGroupsSequence = Sequence([shared_fg])
|
|
267
|
+
|
|
268
|
+
# Per Frame Functional Groups Sequence
|
|
269
|
+
per_frame_seq = []
|
|
270
|
+
for slice_idx in range(num_slices):
|
|
271
|
+
for custom1_idx in range(num_custom1):
|
|
272
|
+
for custom2_idx in range(num_custom2):
|
|
273
|
+
fg = Dataset()
|
|
274
|
+
|
|
275
|
+
# Plane Position Sequence
|
|
276
|
+
pp = Dataset()
|
|
277
|
+
pp.ImagePositionPatient = [0.0, 0.0, float(slice_idx * 5)]
|
|
278
|
+
fg.PlanePositionSequence = Sequence([pp])
|
|
279
|
+
|
|
280
|
+
# Custom dimension values (private tags)
|
|
281
|
+
fg.add_new((0x0011, 0x1010), 'LO', str(custom1_idx))
|
|
282
|
+
fg.add_new((0x0011, 0x1020), 'LO', str(custom2_idx))
|
|
283
|
+
|
|
284
|
+
per_frame_seq.append(fg)
|
|
285
|
+
|
|
286
|
+
ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_seq)
|
|
287
|
+
|
|
288
|
+
# Create float32 pixel data (dummy)
|
|
289
|
+
pixel_array = np.zeros((num_frames, rows, cols), dtype=np.float32)
|
|
290
|
+
ds.PixelData = pixel_array.tobytes()
|
|
291
|
+
|
|
292
|
+
# Optional: Real World Value Mapping
|
|
293
|
+
rwvm = Dataset()
|
|
294
|
+
rwvm.RealWorldValueIntercept = 0.0
|
|
295
|
+
rwvm.RealWorldValueSlope = 1.0
|
|
296
|
+
rwvm.LUTLabel = 'FloatMap'
|
|
297
|
+
unit_code = Dataset()
|
|
298
|
+
unit_code.CodeValue = '1'
|
|
299
|
+
unit_code.CodingSchemeDesignator = 'UCUM'
|
|
300
|
+
unit_code.CodeMeaning = 'no units'
|
|
301
|
+
rwvm.MeasurementUnitsCodeSequence = Sequence([unit_code])
|
|
302
|
+
ds.RealWorldValueMappingSequence = Sequence([rwvm])
|
|
303
|
+
|
|
304
|
+
return ds
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
@@ -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
|
+
|