dbdicom 0.3.8__py3-none-any.whl → 0.3.9__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/api.py +6 -4
- dbdicom/database.py +2 -2
- dbdicom/dataset.py +29 -315
- dbdicom/dbd.py +52 -38
- dbdicom/sop_classes/enhanced_mr_image.py +190 -271
- dbdicom/utils/image.py +13 -13
- dbdicom/utils/pydicom_dataset.py +386 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/METADATA +1 -1
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/RECORD +12 -12
- dbdicom/utils/variables.py +0 -161
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/WHEEL +0 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {dbdicom-0.3.8.dist-info → dbdicom-0.3.9.dist-info}/top_level.txt +0 -0
|
@@ -1,163 +1,218 @@
|
|
|
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
|
-
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
+
import vreg
|
|
8
|
+
|
|
7
9
|
import pydicom
|
|
8
|
-
from pydicom.dataset import Dataset,
|
|
10
|
+
from pydicom.dataset import Dataset, FileMetaDataset
|
|
9
11
|
from pydicom.sequence import Sequence
|
|
10
|
-
from pydicom.uid import
|
|
11
|
-
generate_uid,
|
|
12
|
-
MRImageStorage,
|
|
13
|
-
EnhancedMRImageStorage,
|
|
14
|
-
ExplicitVRLittleEndian
|
|
15
|
-
)
|
|
12
|
+
from pydicom.uid import ExplicitVRLittleEndian, generate_uid
|
|
16
13
|
|
|
14
|
+
import dbdicom.utils.image as image_utils
|
|
15
|
+
from dbdicom.utils.pydicom_dataset import set_values, get_values
|
|
17
16
|
|
|
18
|
-
from dbdicom.utils import image
|
|
19
17
|
|
|
18
|
+
def from_volume(vol:vreg.Volume3D):
|
|
19
|
+
"""
|
|
20
|
+
Build an Enhanced MR Image DICOM dataset from N+3D array.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
file_meta
|
|
39
|
-
file_meta.
|
|
40
|
-
file_meta.
|
|
41
|
-
|
|
42
|
-
# Create FileDataset
|
|
43
|
-
ds = FileDataset(
|
|
44
|
-
filename_or_obj=None,
|
|
45
|
-
dataset=Dataset(),
|
|
46
|
-
file_meta=file_meta,
|
|
47
|
-
preamble=b"\0" * 128,
|
|
48
|
-
)
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
vol: vreg Volume3D
|
|
25
|
+
|
|
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()
|
|
37
|
+
|
|
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
|
|
49
42
|
|
|
50
43
|
ds.is_little_endian = True
|
|
51
44
|
ds.is_implicit_VR = False
|
|
45
|
+
ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID
|
|
46
|
+
ds.SOPInstanceUID = generate_uid()
|
|
52
47
|
|
|
53
|
-
#
|
|
54
|
-
ds.SOPClassUID = EnhancedMRImageStorage
|
|
55
|
-
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
|
|
56
|
-
ds.PatientName = "FiveD^Phantom"
|
|
57
|
-
ds.PatientID = "555555"
|
|
58
|
-
ds.StudyInstanceUID = generate_uid()
|
|
48
|
+
# Study/Series
|
|
59
49
|
ds.SeriesInstanceUID = generate_uid()
|
|
60
|
-
ds.
|
|
61
|
-
ds.
|
|
50
|
+
ds.StudyInstanceUID = generate_uid()
|
|
51
|
+
ds.FrameOfReferenceUID = generate_uid()
|
|
62
52
|
ds.Modality = "MR"
|
|
63
|
-
ds.
|
|
64
|
-
ds.
|
|
65
|
-
ds.
|
|
66
|
-
ds.
|
|
67
|
-
|
|
68
|
-
# Image
|
|
69
|
-
ds.
|
|
70
|
-
ds.
|
|
71
|
-
ds.NumberOfFrames =
|
|
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:])
|
|
72
62
|
ds.SamplesPerPixel = 1
|
|
73
63
|
ds.PhotometricInterpretation = "MONOCHROME2"
|
|
74
64
|
ds.BitsAllocated = 16
|
|
75
|
-
ds.BitsStored =
|
|
76
|
-
ds.HighBit =
|
|
77
|
-
ds.PixelRepresentation =
|
|
78
|
-
ds.PixelSpacing = [
|
|
79
|
-
ds.SliceThickness =
|
|
80
|
-
ds.FrameOfReferenceUID = generate_uid()
|
|
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]
|
|
81
70
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
ds.
|
|
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)
|
|
85
80
|
|
|
86
81
|
# Shared Functional Groups
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
ds.SharedFunctionalGroupsSequence = [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
])
|
|
99
|
-
|
|
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,) + tuple(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])
|
|
100
142
|
|
|
101
|
-
ds
|
|
143
|
+
return ds
|
|
102
144
|
|
|
103
|
-
# Time dimension
|
|
104
|
-
temporal = Dataset()
|
|
105
|
-
temporal.DimensionOrganizationUID = dim_org_uid
|
|
106
|
-
temporal.DimensionIndexPointer = 0x00209164 # TemporalPositionIndex
|
|
107
|
-
temporal.FunctionalGroupPointer = 0x00209113 # TemporalPositionSequence
|
|
108
|
-
ds.DimensionIndexSequence.append(temporal)
|
|
109
|
-
|
|
110
|
-
# Flip angle dimension
|
|
111
|
-
flip = Dataset()
|
|
112
|
-
flip.DimensionOrganizationUID = dim_org_uid
|
|
113
|
-
flip.DimensionIndexPointer = 0x00181314 # FlipAngle
|
|
114
|
-
flip.FunctionalGroupPointer = 0x00189105 # MRImagingModifierSequence
|
|
115
|
-
ds.DimensionIndexSequence.append(flip)
|
|
116
|
-
|
|
117
|
-
# Slice position
|
|
118
|
-
slice_dim = Dataset()
|
|
119
|
-
slice_dim.DimensionOrganizationUID = dim_org_uid
|
|
120
|
-
slice_dim.DimensionIndexPointer = 0x00200032 # ImagePositionPatient
|
|
121
|
-
slice_dim.FunctionalGroupPointer = 0x00209113 # PlanePositionSequence
|
|
122
|
-
ds.DimensionIndexSequence.append(slice_dim)
|
|
123
|
-
|
|
124
|
-
# Per-Frame Functional Groups
|
|
125
|
-
per_frame_seq = []
|
|
126
|
-
|
|
127
|
-
base_time = now
|
|
128
|
-
flip_angle_values = np.linspace(5, 50, flip_angles) # Example flip angles
|
|
129
|
-
|
|
130
|
-
for t in range(time_points):
|
|
131
|
-
for f in range(flip_angles):
|
|
132
|
-
for z in range(slices):
|
|
133
|
-
frame = Dataset()
|
|
134
|
-
|
|
135
|
-
# Frame content
|
|
136
|
-
fc = Dataset()
|
|
137
|
-
fc.FrameAcquisitionNumber = len(per_frame_seq)
|
|
138
|
-
fc.AcquisitionTime = (base_time + timedelta(seconds=t)).strftime("%H%M%S.%f")[:13]
|
|
139
|
-
frame.FrameContentSequence = [fc]
|
|
140
|
-
|
|
141
|
-
# Temporal position
|
|
142
|
-
tp = Dataset()
|
|
143
|
-
tp.TemporalPositionIndex = t + 1
|
|
144
|
-
frame.TemporalPositionSequence = [tp]
|
|
145
|
-
|
|
146
|
-
# Flip angle
|
|
147
|
-
fa = Dataset()
|
|
148
|
-
fa.FlipAngle = float(flip_angle_values[f])
|
|
149
|
-
frame.MRImagingModifierSequence = [fa]
|
|
150
|
-
|
|
151
|
-
# Slice position
|
|
152
|
-
pos = Dataset()
|
|
153
|
-
pos.ImagePositionPatient = [0.0, 0.0, float(z)]
|
|
154
|
-
frame.PlanePositionSequence = [pos]
|
|
155
|
-
|
|
156
|
-
per_frame_seq.append(frame)
|
|
157
|
-
|
|
158
|
-
ds.PerFrameFunctionalGroupsSequence = Sequence(per_frame_seq)
|
|
159
145
|
|
|
160
|
-
|
|
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)
|
|
161
216
|
|
|
162
217
|
|
|
163
218
|
|
|
@@ -624,139 +679,3 @@ def ukrin_maps_per_frame_functional_group():
|
|
|
624
679
|
|
|
625
680
|
return ds
|
|
626
681
|
|
|
627
|
-
def get_window(ds):
|
|
628
|
-
"""Centre and width of the pixel data after applying rescale slope and intercept.
|
|
629
|
-
|
|
630
|
-
In this case retrieve the centre and width values of the first frame
|
|
631
|
-
NOT In USE
|
|
632
|
-
"""
|
|
633
|
-
|
|
634
|
-
centre = ds.PerFrameFunctionalGroupsSequence[0].FrameVOILUTSequence[0].WindowCenter
|
|
635
|
-
width = ds.PerFrameFunctionalGroupsSequence[0].FrameVOILUTSequence[0].WindowWidth
|
|
636
|
-
if centre is None or width is None:
|
|
637
|
-
array = ds.get_pixel_array()
|
|
638
|
-
if centre is None:
|
|
639
|
-
centre = np.median(array)
|
|
640
|
-
if width is None:
|
|
641
|
-
p = np.percentile(array, [25, 75])
|
|
642
|
-
width = p[1] - p[0]
|
|
643
|
-
|
|
644
|
-
return centre, width
|
|
645
|
-
|
|
646
|
-
def get_pixel_array(ds):
|
|
647
|
-
|
|
648
|
-
array = ds.pixel_array.astype(np.float32)
|
|
649
|
-
frames = ds.PerFrameFunctionalGroupsSequence
|
|
650
|
-
for index, frame in enumerate(frames):
|
|
651
|
-
slice = np.squeeze(array[index, ...])
|
|
652
|
-
if [0x2005, 0x100E] in ds: # 'Philips Rescale Slope'
|
|
653
|
-
slope = ds[(0x2005, 0x100E)].value
|
|
654
|
-
intercept = ds[(0x2005, 0x100D)].value
|
|
655
|
-
slice = (slice - intercept) / slope
|
|
656
|
-
else:
|
|
657
|
-
transform = frame.PixelValueTransformationSequence[0]
|
|
658
|
-
slope = float(getattr(transform, 'RescaleSlope', 1))
|
|
659
|
-
intercept = float(getattr(transform, 'RescaleIntercept', 0))
|
|
660
|
-
slice = slice * slope + intercept
|
|
661
|
-
array[index, ...] = np.transpose(slice)
|
|
662
|
-
|
|
663
|
-
return array
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
def set_pixel_array(ds, array, value_range=None):
|
|
667
|
-
|
|
668
|
-
if (0x2005, 0x100E) in ds:
|
|
669
|
-
del ds[0x2005, 0x100E] # Delete 'Philips Rescale Slope'
|
|
670
|
-
if (0x2005, 0x100D) in ds:
|
|
671
|
-
del ds[0x2005, 0x100D]
|
|
672
|
-
|
|
673
|
-
array = image.clip(array, value_range=value_range)
|
|
674
|
-
array, slope, intercept = image.scale_to_range(array, ds.BitsAllocated)
|
|
675
|
-
array = np.transpose(array, (0, 2, 1))
|
|
676
|
-
|
|
677
|
-
maximum = np.amax(array)
|
|
678
|
-
minimum = np.amin(array)
|
|
679
|
-
shape = np.shape(array)
|
|
680
|
-
|
|
681
|
-
ds.NumberOfFrames = np.shape(array)[0]
|
|
682
|
-
del ds.PerFrameFunctionalGroupsSequence[ds.NumberOfFrames:]
|
|
683
|
-
|
|
684
|
-
ds.PixelRepresentation = 0
|
|
685
|
-
ds.SmallestImagePixelValue = int(maximum)
|
|
686
|
-
ds.LargestImagePixelValue = int(minimum)
|
|
687
|
-
ds.RescaleSlope = 1 / slope
|
|
688
|
-
ds.RescaleIntercept = - intercept / slope
|
|
689
|
-
ds.WindowCenter = (maximum + minimum) / 2
|
|
690
|
-
ds.WindowWidth = maximum - minimum
|
|
691
|
-
ds.Rows = shape[0]
|
|
692
|
-
ds.Columns = shape[1]
|
|
693
|
-
ds.PixelData = array.tobytes()
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
def image_type(ds):
|
|
697
|
-
"""Determine if a dataset is Magnitude, Phase, Real or Imaginary"""
|
|
698
|
-
|
|
699
|
-
image_type = []
|
|
700
|
-
for slice in ds.PerFrameFunctionalGroupsSequence:
|
|
701
|
-
sequence = slice.MRImageFrameTypeSequence[0]
|
|
702
|
-
|
|
703
|
-
if hasattr(sequence, 'FrameType'):
|
|
704
|
-
type = set(sequence.FrameType)
|
|
705
|
-
if set(['M', 'MAGNITUDE']).intersection(type):
|
|
706
|
-
image_type.append('MAGNITUDE')
|
|
707
|
-
elif set(['P', 'PHASE']).intersection(type):
|
|
708
|
-
image_type.append('PHASE')
|
|
709
|
-
elif set(['R', 'REAL']).intersection(type):
|
|
710
|
-
image_type.append('REAL')
|
|
711
|
-
elif set(['I', 'IMAGINARY']).intersection(type):
|
|
712
|
-
image_type.append('IMAGINARY')
|
|
713
|
-
elif hasattr(sequence, 'ComplexImageComponent'):
|
|
714
|
-
type = set(sequence.ComplexImageComponent)
|
|
715
|
-
if set(['M', 'MAGNITUDE']).intersection(type):
|
|
716
|
-
image_type.append('MAGNITUDE')
|
|
717
|
-
elif set(['P', 'PHASE']).intersection(type):
|
|
718
|
-
image_type.append('PHASE')
|
|
719
|
-
elif set(['R', 'REAL']).intersection(type):
|
|
720
|
-
image_type.append('REAL')
|
|
721
|
-
elif set(['I', 'IMAGINARY']).intersection(type):
|
|
722
|
-
image_type.append('IMAGINARY')
|
|
723
|
-
else:
|
|
724
|
-
image_type.append('UNKNOWN')
|
|
725
|
-
|
|
726
|
-
return image_type
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
def signal_type(ds):
|
|
730
|
-
"""Determine if an image is Water, Fat, In-Phase, Out-phase image or None"""
|
|
731
|
-
|
|
732
|
-
signal_type = []
|
|
733
|
-
for slice in ds.PerFrameFunctionalGroupsSequence:
|
|
734
|
-
sequence = slice.MRImageFrameTypeSequence[0]
|
|
735
|
-
|
|
736
|
-
if hasattr(sequence, 'FrameType'):
|
|
737
|
-
type = set(sequence.FrameType)
|
|
738
|
-
if set(['W', 'WATER']).intersection(type):
|
|
739
|
-
signal_type.append('WATER')
|
|
740
|
-
elif set(['F', 'FAT']).intersection(type):
|
|
741
|
-
signal_type.append('FAT')
|
|
742
|
-
elif set(['IP', 'IN_PHASE']).intersection(type):
|
|
743
|
-
signal_type.append('IN-PHASE')
|
|
744
|
-
elif set(['OP', 'OUT_PHASE']).intersection(type):
|
|
745
|
-
signal_type.append('OP-PHASE')
|
|
746
|
-
else:
|
|
747
|
-
signal_type.append('UNKNOWN')
|
|
748
|
-
|
|
749
|
-
return signal_type
|
|
750
|
-
|
|
751
|
-
def get_affine_matrix(ds):
|
|
752
|
-
"""Affine transformation matrix for all images in a multiframe image"""
|
|
753
|
-
|
|
754
|
-
affineList = []
|
|
755
|
-
for frame in ds.PerFrameFunctionalGroupsSequence:
|
|
756
|
-
affine = image.affine_matrix(
|
|
757
|
-
frame.PlaneOrientationSequence[0].ImageOrientationPatient,
|
|
758
|
-
frame.PlanePositionSequence[0].ImagePositionPatient,
|
|
759
|
-
frame.PixelMeasuresSequence[0].PixelSpacing,
|
|
760
|
-
frame.PixelMeasuresSequence[0].SliceThickness)
|
|
761
|
-
affineList.append(affine)
|
|
762
|
-
return np.squeeze(np.array(affineList))
|
dbdicom/utils/image.py
CHANGED
|
@@ -25,21 +25,21 @@ def affine_matrix( # single slice function
|
|
|
25
25
|
return affine
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def slice_location(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
# def slice_location(
|
|
29
|
+
# image_orientation:list, # ImageOrientationPatient
|
|
30
|
+
# image_position:list, # ImagePositionPatient
|
|
31
|
+
# ) -> float:
|
|
32
|
+
# """Calculate Slice Location"""
|
|
33
|
+
|
|
34
|
+
# row_cosine = np.array(image_orientation[:3])
|
|
35
|
+
# column_cosine = np.array(image_orientation[3:])
|
|
36
|
+
# slice_cosine = np.cross(row_cosine, column_cosine)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
# # # The coronal orientation has a left-handed reference frame
|
|
39
|
+
# # if np.array_equal(np.around(image_orientation, 3), [1,0,0,0,0,-1]):
|
|
40
|
+
# # slice_cosine = -slice_cosine
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
# return np.dot(np.array(image_position), slice_cosine)
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def dismantle_affine_matrix(affine):
|