dbdicom 0.2.6__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/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.6.dist-info/METADATA +0 -72
- dbdicom-0.2.6.dist-info/RECORD +0 -66
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.0.dist-info}/WHEEL +0 -0
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
"""Remnant code from old version - saved here for now"""
|
|
2
|
-
|
|
3
|
-
from pydicom.dataset import Dataset
|
|
4
|
-
import numpy as np
|
|
5
|
-
import datetime
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ParametricMap(object):
|
|
10
|
-
def selectParametricMap(self, dicom, imageArray, argument):
|
|
11
|
-
methodName = argument
|
|
12
|
-
method = getattr(self, methodName, lambda: "No valid Parametric Map chosen")
|
|
13
|
-
return method(dicom, imageArray)
|
|
14
|
-
|
|
15
|
-
def RGB(self, dicom, imageArray):
|
|
16
|
-
dicom.PhotometricInterpretation = 'RGB'
|
|
17
|
-
dicom.SamplesPerPixel = 3
|
|
18
|
-
dicom.BitsAllocated = 8
|
|
19
|
-
dicom.BitsStored = 8
|
|
20
|
-
dicom.HighBit = 7
|
|
21
|
-
dicom.add_new(0x00280006, 'US', 0) # Planar Configuration
|
|
22
|
-
dicom.RescaleSlope = 1
|
|
23
|
-
dicom.RescaleIntercept = 0
|
|
24
|
-
pixelArray = imageArray.astype(np.uint8) # Should we multiply by 255?
|
|
25
|
-
dicom.WindowCenter = int((np.amax(imageArray) - np.amin(imageArray)) / 2)
|
|
26
|
-
dicom.WindowWidth = np.absolute(int(np.amax(imageArray) - np.amin(imageArray)))
|
|
27
|
-
dicom.PixelData = pixelArray.tobytes()
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
def ADC(self, dicom, imageArray):
|
|
31
|
-
# The commented parts are to apply when we decide to include Parametric Map IOD. No readers can deal with this yet
|
|
32
|
-
# dicom.SOPClassUID = '1.2.840.10008.5.1.4.1.1.67'
|
|
33
|
-
dicom.SeriesDescription = "Apparent Diffusion Coefficient (um2/s)"
|
|
34
|
-
dicom.Modality = "RWV"
|
|
35
|
-
dicom.FrameLaterality = "U"
|
|
36
|
-
dicom.DerivedPixelContrast = "ADC"
|
|
37
|
-
dicom.BitsAllocated = 32
|
|
38
|
-
dicom.PixelRepresentation = 1
|
|
39
|
-
dicom.PhotometricInterpretation = "MONOCHROME2"
|
|
40
|
-
dicom.PixelAspectRatio = ["1", "1"] # Need to have a better look at this
|
|
41
|
-
dicom.RescaleSlope = 1
|
|
42
|
-
dicom.RescaleIntercept = 0
|
|
43
|
-
# Rotate the image back to the original orientation
|
|
44
|
-
imageArray = np.transpose(imageArray)
|
|
45
|
-
dicom.Rows = np.shape(imageArray)[-2]
|
|
46
|
-
dicom.Columns = np.shape(imageArray)[-1]
|
|
47
|
-
dicom.WindowCenter = int((np.amax(imageArray) - np.amin(imageArray)) / 2)
|
|
48
|
-
dicom.WindowWidth = np.absolute(int(np.amax(imageArray) - np.amin(imageArray)))
|
|
49
|
-
dicom.FloatPixelData = bytes(imageArray.astype(np.float32).flatten())
|
|
50
|
-
del dicom.PixelData, dicom.BitsStored, dicom.HighBit
|
|
51
|
-
|
|
52
|
-
dicom.RealWorldValueMappingSequence = [Dataset(), Dataset(), Dataset(), Dataset()]
|
|
53
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence = [Dataset(), Dataset()]
|
|
54
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence[0].ValueType = "CODE"
|
|
55
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence[1].ConceptCodeSequence = [Dataset(), Dataset(), Dataset()]
|
|
56
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence[1].ConceptCodeSequence[0].CodeValue = "113041"
|
|
57
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence[1].ConceptCodeSequence[1].CodingSchemeDesignator = "DCM"
|
|
58
|
-
dicom.RealWorldValueMappingSequence[0].QuantityDefinitionSequence[1].ConceptCodeSequence[2].CodeMeaning = "Apparent Diffusion Coefficient"
|
|
59
|
-
dicom.RealWorldValueMappingSequence[1].MeasurementUnitsCodeSequence = [Dataset(), Dataset(), Dataset()]
|
|
60
|
-
dicom.RealWorldValueMappingSequence[1].MeasurementUnitsCodeSequence[0].CodeValue = "um2/s"
|
|
61
|
-
dicom.RealWorldValueMappingSequence[1].MeasurementUnitsCodeSequence[1].CodingSchemeDesignator = "UCUM"
|
|
62
|
-
dicom.RealWorldValueMappingSequence[1].MeasurementUnitsCodeSequence[2].CodeMeaning = "um2/s"
|
|
63
|
-
dicom.RealWorldValueMappingSequence[2].RealWorldValueSlope = 1
|
|
64
|
-
|
|
65
|
-
anatomyString = dicom.BodyPartExamined
|
|
66
|
-
saveAnatomicalInfo(anatomyString, dicom.RealWorldValueMappingSequence[3])
|
|
67
|
-
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
def T2Star(self, dicom, imageArray):
|
|
71
|
-
dicom.PixelSpacing = [3, 3] # find a mechanism to pass reconstruct pixel here
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
def SEG(self, dicom, imageArray):
|
|
75
|
-
#dicom.SOPClassUID = '1.2.840.10008.5.1.4.1.1.66.4' # WILL NOT BE USED HERE - This is for PACS. There will be another one for DICOM Standard
|
|
76
|
-
# The commented parts are to apply when we decide to include SEG IOD. No readers can deal with this yet
|
|
77
|
-
dicom.BitsAllocated = 8 # According to Federov DICOM Standard this should be 1-bit
|
|
78
|
-
dicom.BitsStored = 8
|
|
79
|
-
dicom.HighBit = 7
|
|
80
|
-
#dicom.SmallestImagePixelValue = 0
|
|
81
|
-
#dicom.LargestImagePixelValue = int(np.amax(imageArray)) # max 255
|
|
82
|
-
dicom.add_new('0x00280106', 'US', 0) # Minimum
|
|
83
|
-
dicom.add_new('0x00280107', 'US', int(np.amax(imageArray))) # Maximum
|
|
84
|
-
dicom.PixelRepresentation = 0
|
|
85
|
-
dicom.SamplesPerPixel = 1
|
|
86
|
-
#dicom.WindowCenter = 0.5
|
|
87
|
-
#dicom.WindowWidth = 1.1
|
|
88
|
-
dicom.add_new('0x00281050', 'DS', 0.5) # WindowCenter
|
|
89
|
-
dicom.add_new('0x00281051', 'DS', 1.1) # WindowWidth
|
|
90
|
-
#dicom.RescaleIntercept = 0
|
|
91
|
-
#dicom.RescaleSlope = 1
|
|
92
|
-
dicom.add_new('0x00281052', 'DS', 0) # RescaleIntercept
|
|
93
|
-
dicom.add_new('0x00281053', 'DS', 1) # RescaleSlope
|
|
94
|
-
dicom.LossyImageCompression = '00'
|
|
95
|
-
pixelArray = np.transpose(imageArray.astype(np.uint8)) # Should we multiply by 255?
|
|
96
|
-
dicom.PixelData = pixelArray.tobytes()
|
|
97
|
-
|
|
98
|
-
dicom.Modality = 'SEG'
|
|
99
|
-
dicom.SegmentationType = 'FRACTIONAL'
|
|
100
|
-
dicom.MaximumFractionalValue = int(np.amax(imageArray)) # max 255
|
|
101
|
-
dicom.SegmentationFractionalType = 'OCCUPANCY'
|
|
102
|
-
|
|
103
|
-
# Segment Labels
|
|
104
|
-
if hasattr(dicom, "ImageComments"):
|
|
105
|
-
dicom.ContentDescription = dicom.ImageComments.split('_')[-1] # 'Image segmentation'
|
|
106
|
-
segment_numbers = np.unique(pixelArray)
|
|
107
|
-
segment_dictionary = dict(list(enumerate(segment_numbers)))
|
|
108
|
-
segment_label = dicom.ImageComments.split('_')[-1]
|
|
109
|
-
segment_dictionary[0] = 'Background'
|
|
110
|
-
segment_dictionary[1] = segment_label
|
|
111
|
-
for key in segment_dictionary:
|
|
112
|
-
dicom.SegmentSequence = [Dataset(), Dataset(), Dataset(), Dataset(), Dataset(), Dataset()]
|
|
113
|
-
dicom.SegmentSequence[0].SegmentAlgorithmType = 'MANUAL'
|
|
114
|
-
dicom.SegmentSequence[1].SegmentNumber = key
|
|
115
|
-
dicom.SegmentSequence[2].SegmentDescription = str(segment_dictionary[key])
|
|
116
|
-
dicom.SegmentSequence[3].SegmentLabel = str(segment_dictionary[key])
|
|
117
|
-
dicom.SegmentSequence[4].SegmentAlgorithmName = "Weasel"
|
|
118
|
-
if hasattr(dicom, "BodyPartExamined"):
|
|
119
|
-
anatomyString = dicom.BodyPartExamined
|
|
120
|
-
saveAnatomicalInfo(anatomyString, dicom.SegmentSequence[5])
|
|
121
|
-
else:
|
|
122
|
-
dicom.ContentDescription = "Mask with no label"
|
|
123
|
-
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
def Registration(self, dicom, imageArray):
|
|
127
|
-
dicom.Modality = "REG"
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
def Signal(self, dicom, imageArray):
|
|
131
|
-
dicom.Modality = "RWV"
|
|
132
|
-
dicom.DerivedPixelContrast = "GraphPlot"
|
|
133
|
-
dicom.PhotometricInterpretation = "MONOCHROME2"
|
|
134
|
-
dicom.RescaleSlope = 1
|
|
135
|
-
dicom.RescaleIntercept = 0
|
|
136
|
-
imageArray = np.transpose(imageArray.astype(np.float32))
|
|
137
|
-
center = (np.amax(imageArray) + np.amin(imageArray)) / 2
|
|
138
|
-
width = np.amax(imageArray) - np.amin(imageArray)
|
|
139
|
-
dicom.add_new('0x00281050', 'DS', center)
|
|
140
|
-
dicom.add_new('0x00281051', 'DS', width)
|
|
141
|
-
dicom.BitsAllocated = 32
|
|
142
|
-
dicom.Rows = np.shape(imageArray)[0]
|
|
143
|
-
dicom.Columns = np.shape(imageArray)[1]
|
|
144
|
-
dicom.FloatPixelData = bytes(imageArray.flatten())
|
|
145
|
-
del dicom.PixelData, dicom.BitsStored, dicom.HighBit
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
# Could insert a method regarding ROI colours, like in ITK-SNAP???
|
|
149
|
-
def saveAnatomicalInfo(anatomyString, dicom):
|
|
150
|
-
try:
|
|
151
|
-
# FOR NOW, THE PRIORITY WILL BE ON KIDNEY
|
|
152
|
-
if "KIDNEY" or "ABDOMEN" in anatomyString.upper():
|
|
153
|
-
dicom.AnatomicRegionSequence = [Dataset(), Dataset(), Dataset()]
|
|
154
|
-
dicom.AnatomicRegionSequence[0].CodeValue = "T-71000"
|
|
155
|
-
dicom.AnatomicRegionSequence[1].CodingSchemeDesignator = "SRT"
|
|
156
|
-
dicom.AnatomicRegionSequence[2].CodeMeaning = "Kidney"
|
|
157
|
-
elif "LIVER" in anatomyString.upper():
|
|
158
|
-
dicom.AnatomicRegionSequence = [Dataset(), Dataset(), Dataset()]
|
|
159
|
-
dicom.AnatomicRegionSequence[0].CodeValue = "T-62000"
|
|
160
|
-
dicom.AnatomicRegionSequence[1].CodingSchemeDesignator = "SRT"
|
|
161
|
-
dicom.AnatomicRegionSequence[2].CodeMeaning = "Liver"
|
|
162
|
-
elif "PROSTATE" in anatomyString.upper():
|
|
163
|
-
dicom.AnatomicRegionSequence = [Dataset(), Dataset(), Dataset()]
|
|
164
|
-
dicom.AnatomicRegionSequence[0].CodeValue = "T-9200B"
|
|
165
|
-
dicom.AnatomicRegionSequence[1].CodingSchemeDesignator = "SRT"
|
|
166
|
-
dicom.AnatomicRegionSequence[2].CodeMeaning = "Prostate"
|
|
167
|
-
elif "BODY" in anatomyString.upper():
|
|
168
|
-
dicom.AnatomicRegionSequence = [Dataset(), Dataset(), Dataset()]
|
|
169
|
-
dicom.AnatomicRegionSequence[0].CodeValue = "P5-0905E"
|
|
170
|
-
dicom.AnatomicRegionSequence[1].CodingSchemeDesignator = "LN"
|
|
171
|
-
dicom.AnatomicRegionSequence[2].CodeMeaning = "MRI whole body"
|
|
172
|
-
except:
|
|
173
|
-
pass
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
def editDicom(newDicom, imageArray, parametricMap):
|
|
177
|
-
|
|
178
|
-
callCase = ParametricMap()
|
|
179
|
-
callCase.selectParametricMap(newDicom, imageArray, parametricMap)
|
|
180
|
-
|
|
181
|
-
dt = datetime.datetime.now()
|
|
182
|
-
timeStr = dt.strftime('%H%M%S') # long format with micro seconds
|
|
183
|
-
newDicom.PerformedProcedureStepStartDate = dt.strftime('%Y%m%d')
|
|
184
|
-
newDicom.PerformedProcedureStepStartTime = timeStr
|
|
185
|
-
newDicom.PerformedProcedureStepDescription = "Post-processing application"
|
|
186
|
-
|
|
187
|
-
return newDicom
|
|
188
|
-
|
|
189
|
-
# Series, Instance and Class for Reference
|
|
190
|
-
#newDicom.ReferencedSeriesSequence = [Dataset(), Dataset()]
|
|
191
|
-
#newDicom.ReferencedSeriesSequence[0].SeriesInstanceUID = dicom_data.SeriesInstanceUID
|
|
192
|
-
#newDicom.ReferencedSeriesSequence[1].ReferencedInstanceSequence = [Dataset(), Dataset()]
|
|
193
|
-
#newDicom.ReferencedSeriesSequence[1].ReferencedInstanceSequence[0].ReferencedSOPClassUID = dicom_data.SOPClassUID
|
|
194
|
-
#newDicom.ReferencedSeriesSequence[1].ReferencedInstanceSequence[1].ReferencedSOPInstanceUID = dicom_data.SOPInstanceUID
|
|
195
|
-
|
|
196
|
-
# rwv_sequence = Sequence()
|
|
197
|
-
# dicom.RealWorldValueMappingSequence = rwv_sequence
|
|
198
|
-
# rwv_slope = Dataset()
|
|
199
|
-
# rwv_slope.RealWorldValueSlope = 1
|
|
200
|
-
# rwv_sequence.append(rwv_slope)
|
|
201
|
-
|
|
202
|
-
# quantity_def = Dataset()
|
|
203
|
-
# quantity_def_sequence = Sequence()
|
|
204
|
-
# quantity_def.QuantityDefinitionSequence = quantity_def_sequence
|
|
205
|
-
# value_type = Dataset()
|
|
206
|
-
# value_type.ValueType = "CODE"
|
|
207
|
-
# quantity_def_sequence.append(value_type)
|
|
208
|
-
# concept_code = Dataset()
|
|
209
|
-
# concept_code_sequence = Sequence()
|
|
210
|
-
# concept_code.ConceptCodeSequence = concept_code_sequence
|
|
211
|
-
# code_code = Dataset()
|
|
212
|
-
# code_code.CodeValue = "113041"
|
|
213
|
-
# code_code.CodingSchemeDesignator = "DCM"
|
|
214
|
-
# code_code.CodeMeaning = "Apparent Diffusion Coefficient"
|
|
215
|
-
# concept_code_sequence.append(code_code)
|
|
216
|
-
# rwv_sequence.append(quantity_def)
|
|
217
|
-
|
|
218
|
-
# measure_units = Dataset()
|
|
219
|
-
# measure_units_sequence = Sequence()
|
|
220
|
-
# measure_units.MeasurementUnitsCodeSequence = measure_units_sequence
|
|
221
|
-
# measure_code = Dataset()
|
|
222
|
-
# measure_code.CodeValue = "um2/s"
|
|
223
|
-
# measure_code.CodingSchemeDesignator = "UCUM"
|
|
224
|
-
# measure_code.CodeMeaning = "um2/s"
|
|
225
|
-
# measure_units_sequence.append(measure_code)
|
|
226
|
-
# rwv_sequence.append(measure_units)
|
dbdicom/extensions/__init__.py
DELETED
dbdicom/extensions/dipy.py
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from dipy.align.imwarp import SymmetricDiffeomorphicRegistration
|
|
3
|
-
from dipy.align.metrics import CCMetric, EMMetric, SSDMetric
|
|
4
|
-
from dipy.align.imaffine import MutualInformationMetric, AffineRegistration, transform_centers_of_mass
|
|
5
|
-
from dipy.align.transforms import (
|
|
6
|
-
TranslationTransform2D, RigidTransform2D, AffineTransform2D,
|
|
7
|
-
TranslationTransform3D, RigidTransform3D, AffineTransform3D)
|
|
8
|
-
from dipy.align import center_of_mass
|
|
9
|
-
from dipy.align.vector_fields import (
|
|
10
|
-
warp_3d_nn,
|
|
11
|
-
warp_3d,
|
|
12
|
-
warp_2d_nn,
|
|
13
|
-
warp_2d,
|
|
14
|
-
invert_vector_field_fixed_point_3d,
|
|
15
|
-
invert_vector_field_fixed_point_2d,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
from dipy.segment.mask import median_otsu as median_otsu_np
|
|
19
|
-
import dbdicom.extensions.vreg as vreg
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def median_otsu(series, **kwargs):
|
|
23
|
-
|
|
24
|
-
# Get arrays for fixed and moving series
|
|
25
|
-
array, headers = series.array('SliceLocation', pixels_first=True)
|
|
26
|
-
|
|
27
|
-
# Apply Otsu
|
|
28
|
-
mask = np.empty(array.shape)
|
|
29
|
-
cnt=0
|
|
30
|
-
for z in range(array.shape[2]):
|
|
31
|
-
for k in range(array.shape[3]):
|
|
32
|
-
cnt+=1
|
|
33
|
-
series.status.progress(cnt, array.shape[2]*array.shape[3], 'Applying Otsu segmentation..')
|
|
34
|
-
image = np.squeeze(array[:,:,z,k])
|
|
35
|
-
array[:,:,z,k], mask[:,:,z,k] = median_otsu_np(image, **kwargs)
|
|
36
|
-
|
|
37
|
-
# Create new series
|
|
38
|
-
masked_series = series.new_sibling(suffix='masked')
|
|
39
|
-
otsu_mask = series.new_sibling(suffix ='otsu mask')
|
|
40
|
-
|
|
41
|
-
# Set values and return
|
|
42
|
-
masked_series.set_array(array, headers, pixels_first=True)
|
|
43
|
-
otsu_mask.set_array(mask, headers, pixels_first=True)
|
|
44
|
-
return masked_series, otsu_mask
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def align_center_of_mass_3d(moving, fixed):
|
|
48
|
-
|
|
49
|
-
# Get arrays for fixed and moving series
|
|
50
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
51
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
52
|
-
|
|
53
|
-
# Coregister fixed and moving slice-by-slice
|
|
54
|
-
identity = np.eye(4)
|
|
55
|
-
array_moving, _ = center_of_mass(array_moving, array_fixed, static_affine=identity, moving_affine=identity)
|
|
56
|
-
|
|
57
|
-
# Create new series
|
|
58
|
-
moved = moving.new_sibling(suffix='aligned')
|
|
59
|
-
moved.set_array(array_moving, headers_moving, pixels_first=True)
|
|
60
|
-
|
|
61
|
-
return moved
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def coregister_translation_3d(moving, fixed):
|
|
65
|
-
|
|
66
|
-
# Get arrays for fixed and moving series
|
|
67
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
68
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
69
|
-
|
|
70
|
-
# Set up coregistration
|
|
71
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None,)
|
|
72
|
-
affreg = AffineRegistration(
|
|
73
|
-
metric = metric,
|
|
74
|
-
level_iters = [10000, 1000, 100],
|
|
75
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
76
|
-
factors = [4, 2, 1],
|
|
77
|
-
)
|
|
78
|
-
transform = TranslationTransform3D()
|
|
79
|
-
params0 = None
|
|
80
|
-
|
|
81
|
-
# Perform coregistration
|
|
82
|
-
moving.message('Performing coregistration..')
|
|
83
|
-
mapping = affreg.optimize(array_fixed, array_moving, transform, params0)
|
|
84
|
-
coregistered = mapping.transform(array_moving, 'linear')
|
|
85
|
-
|
|
86
|
-
# Save as DICOM
|
|
87
|
-
coreg = moving.new_sibling(suffix='translated')
|
|
88
|
-
coreg.set_array(coregistered, headers_moving, pixels_first=True)
|
|
89
|
-
|
|
90
|
-
return coreg
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def coregister_rigid_3d(moving, fixed):
|
|
94
|
-
|
|
95
|
-
# Get arrays for fixed and moving series
|
|
96
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
97
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
98
|
-
|
|
99
|
-
# Setup coregistration
|
|
100
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None)
|
|
101
|
-
affreg = AffineRegistration(
|
|
102
|
-
metric = metric,
|
|
103
|
-
level_iters = [10000, 1000, 100],
|
|
104
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
105
|
-
factors = [4, 2, 1],
|
|
106
|
-
)
|
|
107
|
-
transform = RigidTransform3D()
|
|
108
|
-
params0 = None
|
|
109
|
-
|
|
110
|
-
# Perform coregistration
|
|
111
|
-
moving.message('Performing coregistration..')
|
|
112
|
-
mapping = affreg.optimize(array_fixed, array_moving, transform, params0)
|
|
113
|
-
coregistered = mapping.transform(array_moving, 'linear')
|
|
114
|
-
|
|
115
|
-
# Save as DICOM
|
|
116
|
-
coreg = moving.new_sibling(suffix='rigid transform')
|
|
117
|
-
coreg.set_array(coregistered, headers_moving, pixels_first=True)
|
|
118
|
-
return coreg
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def coregister_affine_3d(moving, fixed):
|
|
123
|
-
|
|
124
|
-
# Get arrays for fixed and moving series
|
|
125
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
126
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
127
|
-
|
|
128
|
-
# Setup coregistration
|
|
129
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None)
|
|
130
|
-
affreg = AffineRegistration(
|
|
131
|
-
metric = metric,
|
|
132
|
-
level_iters = [10000, 1000, 100],
|
|
133
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
134
|
-
factors = [4, 2, 1],
|
|
135
|
-
)
|
|
136
|
-
transform = AffineTransform3D()
|
|
137
|
-
params0 = None
|
|
138
|
-
|
|
139
|
-
# Perform coregistration
|
|
140
|
-
moving.message('Performing coregistration..')
|
|
141
|
-
mapping = affreg.optimize(array_fixed, array_moving, transform, params0)
|
|
142
|
-
coregistered = mapping.transform(array_moving, 'linear')
|
|
143
|
-
|
|
144
|
-
# Save as DICOM
|
|
145
|
-
coreg = moving.new_sibling(suffix='rigid transform')
|
|
146
|
-
coreg.set_array(coregistered, headers_moving, pixels_first=True)
|
|
147
|
-
return coreg
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def coregister_deformable_3d(moving, fixed, **kwargs):
|
|
151
|
-
|
|
152
|
-
# Get arrays for fixed and moving series
|
|
153
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
154
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
155
|
-
|
|
156
|
-
# Perform coregistration
|
|
157
|
-
moving.status.message('Performing coregistration..')
|
|
158
|
-
array_moving, deformation = _coregister_arrays(array_fixed, array_moving, **kwargs)
|
|
159
|
-
|
|
160
|
-
# Create new series
|
|
161
|
-
coreg = moving.new_sibling(suffix='registered')
|
|
162
|
-
deform = moving.new_sibling(suffix='deformation field')
|
|
163
|
-
|
|
164
|
-
# Set arrays
|
|
165
|
-
coreg.set_array(array_moving, headers_moving, pixels_first=True)
|
|
166
|
-
for dim in range(deformation.shape[-1]):
|
|
167
|
-
deform.set_array(deformation[...,dim], headers_moving, pixels_first=True)
|
|
168
|
-
|
|
169
|
-
# Return coregistered images and deformation field
|
|
170
|
-
return coreg, deform
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def align_center_of_mass_2d(moving, fixed):
|
|
174
|
-
|
|
175
|
-
# Get arrays for fixed and moving series
|
|
176
|
-
zaxis = 'SliceLocation'
|
|
177
|
-
array_moving = moving.pixel_values(zaxis)
|
|
178
|
-
array_fixed = vreg.pixel_values(fixed, zaxis, on=moving)
|
|
179
|
-
|
|
180
|
-
# Coregister fixed and moving slice-by-slice
|
|
181
|
-
id = np.eye(3)
|
|
182
|
-
for z in range(array_moving.shape[2]):
|
|
183
|
-
moving.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
184
|
-
c_of_mass = transform_centers_of_mass(array_fixed[:,:,z], id, array_moving[:,:,z], id)
|
|
185
|
-
array_moving[:,:,z] = c_of_mass.transform(array_moving[:,:,z])
|
|
186
|
-
|
|
187
|
-
# Save as DICOM (new API)
|
|
188
|
-
coreg = moving.copy(SeriesDescription=moving.SeriesDescription + ' [coreg]')
|
|
189
|
-
coreg.set_pixel_values(array_moving, coords=moving.coords(zaxis))
|
|
190
|
-
return coreg
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def coregister_translation_2d(moving, fixed):
|
|
195
|
-
|
|
196
|
-
# # Get arrays for fixed and moving series (old API)
|
|
197
|
-
# array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
198
|
-
# array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
199
|
-
|
|
200
|
-
# Get arrays for fixed and moving series (new API)
|
|
201
|
-
#zaxis = ('SliceLocation',)
|
|
202
|
-
zaxis = 'SliceLocation'
|
|
203
|
-
array_moving = moving.pixel_values(zaxis)
|
|
204
|
-
array_fixed = vreg.pixel_values(fixed, zaxis, on=moving)
|
|
205
|
-
|
|
206
|
-
# Set up coregistration
|
|
207
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None)
|
|
208
|
-
affreg = AffineRegistration(
|
|
209
|
-
metric = metric,
|
|
210
|
-
level_iters = [10000, 1000, 100],
|
|
211
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
212
|
-
factors = [4, 2, 1],
|
|
213
|
-
)
|
|
214
|
-
transform = TranslationTransform2D()
|
|
215
|
-
|
|
216
|
-
# Coregister fixed and moving slice-by-slice
|
|
217
|
-
for z in range(array_moving.shape[2]):
|
|
218
|
-
moving.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
219
|
-
# Coregister slice
|
|
220
|
-
params0 = None
|
|
221
|
-
mapping = affreg.optimize(array_fixed[:,:,z], array_moving[:,:,z], transform, params0)
|
|
222
|
-
array_moving[:,:,z] = mapping.transform(array_moving[:,:,z], 'linear')
|
|
223
|
-
|
|
224
|
-
# # Save as DICOM (old API)
|
|
225
|
-
# coreg = moving.new_sibling(suffix= 'registered')
|
|
226
|
-
# coreg.set_array(array_moving, headers_moving, pixels_first=True)
|
|
227
|
-
|
|
228
|
-
# Save as DICOM (new API)
|
|
229
|
-
coreg = moving.copy(SeriesDescription=moving.SeriesDescription + ' [coreg]')
|
|
230
|
-
coreg.set_pixel_values(array_moving, coords=moving.coords(zaxis))
|
|
231
|
-
return coreg
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def coregister_rigid_2d(moving, fixed):
|
|
235
|
-
|
|
236
|
-
# Get arrays for fixed and moving series
|
|
237
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
238
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
239
|
-
|
|
240
|
-
# Set up coregistration
|
|
241
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None)
|
|
242
|
-
affreg = AffineRegistration(
|
|
243
|
-
metric = metric,
|
|
244
|
-
level_iters = [10000, 1000, 100],
|
|
245
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
246
|
-
factors = [4, 2, 1],
|
|
247
|
-
)
|
|
248
|
-
transform = RigidTransform2D()
|
|
249
|
-
|
|
250
|
-
# Coregister fixed and moving slice-by-slice
|
|
251
|
-
for z in range(array_moving.shape[2]):
|
|
252
|
-
moving.status.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
253
|
-
# Coregister slice
|
|
254
|
-
params0 = None
|
|
255
|
-
mapping = affreg.optimize(array_fixed[:,:,z], array_moving[:,:,z], transform, params0)
|
|
256
|
-
array_moving[:,:,z] = mapping.transform(array_moving[:,:,z], 'linear')
|
|
257
|
-
|
|
258
|
-
# Save as DICOM
|
|
259
|
-
coreg = moving.new_sibling(suffix= 'registered')
|
|
260
|
-
coreg.set_array(array_moving, headers_moving, pixels_first=True)
|
|
261
|
-
return coreg
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def coregister_affine_2d(moving, fixed):
|
|
265
|
-
|
|
266
|
-
# Get arrays for fixed and moving series
|
|
267
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
268
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
269
|
-
|
|
270
|
-
# Set up coregistration
|
|
271
|
-
metric = MutualInformationMetric(nbins=32, sampling_proportion=None)
|
|
272
|
-
affreg = AffineRegistration(
|
|
273
|
-
metric = metric,
|
|
274
|
-
level_iters = [10000, 1000, 100],
|
|
275
|
-
sigmas = [3.0, 1.0, 0.0],
|
|
276
|
-
factors = [4, 2, 1],
|
|
277
|
-
)
|
|
278
|
-
transform = AffineTransform2D()
|
|
279
|
-
|
|
280
|
-
# Coregister fixed and moving slice-by-slice
|
|
281
|
-
for z in range(array_moving.shape[2]):
|
|
282
|
-
moving.status.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
283
|
-
# Coregister slice
|
|
284
|
-
params0 = None
|
|
285
|
-
mapping = affreg.optimize(array_fixed[:,:,z], array_moving[:,:,z], transform, params0)
|
|
286
|
-
array_moving[:,:,z] =mapping.transform(array_moving[:,:,z], 'linear')
|
|
287
|
-
|
|
288
|
-
# Save as DICOM
|
|
289
|
-
coreg = moving.new_sibling(suffix= 'registered')
|
|
290
|
-
coreg.set_array(array_moving, headers_moving, pixels_first=True)
|
|
291
|
-
return coreg
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def coregister_deformable_2d(moving, fixed, **kwargs):
|
|
296
|
-
|
|
297
|
-
# Get arrays for fixed and moving series
|
|
298
|
-
array_moving, headers_moving = moving.array(sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
299
|
-
array_fixed = vreg.array(fixed, on=moving, sortby='SliceLocation', pixels_first=True, first_volume=True)
|
|
300
|
-
|
|
301
|
-
# Coregister fixed and moving slice-by-slice
|
|
302
|
-
deformation = np.empty(array_moving.shape + (2,))
|
|
303
|
-
for z in range(array_moving.shape[2]):
|
|
304
|
-
moving.status.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
305
|
-
coreg, deform = _coregister_arrays(array_fixed[:,:,z], array_moving[:,:,z], **kwargs)
|
|
306
|
-
array_moving[:,:,z] = coreg
|
|
307
|
-
deformation[:,:,z,:] = deform
|
|
308
|
-
|
|
309
|
-
# Create new series
|
|
310
|
-
coreg = moving.new_sibling(suffix= 'registered')
|
|
311
|
-
deform = moving.new_sibling(suffix='deformation field')
|
|
312
|
-
|
|
313
|
-
# Set values & return
|
|
314
|
-
coreg.set_array(array_moving, headers_moving, pixels_first=True)
|
|
315
|
-
for dim in range(deformation.shape[-1]):
|
|
316
|
-
deform.set_array(deformation[...,dim], headers_moving, pixels_first=True)
|
|
317
|
-
return coreg, deform
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def invert_deformation_field(deformation_field, **kwargs):
|
|
325
|
-
|
|
326
|
-
# Get array
|
|
327
|
-
array, headers = deformation_field.array('SliceLocation', pixels_first=True)
|
|
328
|
-
|
|
329
|
-
# Invert
|
|
330
|
-
array = _invert_deformation_field_array(array, deformation_field.status, **kwargs)
|
|
331
|
-
|
|
332
|
-
# Create new series
|
|
333
|
-
inv = deformation_field.new_sibling(suffix='inverse')
|
|
334
|
-
inv.set_array(array, headers, pixels_first=True)
|
|
335
|
-
|
|
336
|
-
return inv
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
def warp(image, deformation_field, **kwargs):
|
|
340
|
-
|
|
341
|
-
# Get arrays
|
|
342
|
-
array, headers = image.array('SliceLocation', pixels_first=True, first_volume=True)
|
|
343
|
-
array_deform = vreg.array(deformation_field, on=image, sortby='SliceLocation', pixels_first=True)
|
|
344
|
-
|
|
345
|
-
# Perform warping
|
|
346
|
-
array = _warp_array(array, array_deform, image.status, **kwargs)
|
|
347
|
-
|
|
348
|
-
# Create new series
|
|
349
|
-
warped = image.new_sibling(suffix='warped')
|
|
350
|
-
warped.set_array(array, headers, pixels_first=True)
|
|
351
|
-
|
|
352
|
-
return warped
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
### ARRAY functions
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def _invert_deformation_field_array(array, status, max_iter=10, tolerance=0.1):
|
|
362
|
-
status.message('Inverting deformation field..')
|
|
363
|
-
dim = array.shape[-1]
|
|
364
|
-
d_world2grid = np.eye(dim+1)
|
|
365
|
-
spacing = np.ones(dim)
|
|
366
|
-
if dim==3:
|
|
367
|
-
return invert_vector_field_fixed_point_3d(array, d_world2grid, spacing, max_iter, tolerance)
|
|
368
|
-
elif dim==2:
|
|
369
|
-
nslices = array.shape[2]
|
|
370
|
-
for z in range(nslices):
|
|
371
|
-
status.progress(z+1, nslices, 'Inverting deformation field..')
|
|
372
|
-
array[:,:,z] = invert_vector_field_fixed_point_2d(array[:,:,z], d_world2grid, spacing, max_iter, tolerance)
|
|
373
|
-
return array
|
|
374
|
-
else:
|
|
375
|
-
msg = 'This series is not a deformation field.'
|
|
376
|
-
msg += 'A deformation field must have either 2 or 3 components.'
|
|
377
|
-
raise ValueError(msg)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def _warp_array(array, deform, status, interpolate=True):
|
|
381
|
-
status.message('Warping array..')
|
|
382
|
-
dim = deform.shape[-1]
|
|
383
|
-
if dim==3:
|
|
384
|
-
if interpolate:
|
|
385
|
-
return warp_3d(array, deform)
|
|
386
|
-
else:
|
|
387
|
-
return warp_3d_nn(array, deform)
|
|
388
|
-
elif dim==2:
|
|
389
|
-
nslices = deform.shape[2]
|
|
390
|
-
for z in range(nslices):
|
|
391
|
-
status.progress(z+1, nslices, 'Warping array..')
|
|
392
|
-
if interpolate:
|
|
393
|
-
array[:,:,z] = warp_2d(array[:,:,z], deform[:,:,z,:])
|
|
394
|
-
else:
|
|
395
|
-
array[:,:,z] = warp_2d_nn(array[:,:,z], deform[:,:,z,:])
|
|
396
|
-
return array
|
|
397
|
-
else:
|
|
398
|
-
msg = 'This series is not a deformation field.'
|
|
399
|
-
msg += 'A deformation field must have either 2 or 3 components.'
|
|
400
|
-
raise ValueError(msg)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
def _coregister_arrays(fixed, moving, transformation='Symmetric Diffeomorphic', metric="Cross-Correlation"):
|
|
404
|
-
"""
|
|
405
|
-
Coregister two arrays and return coregistered + deformation field
|
|
406
|
-
"""
|
|
407
|
-
|
|
408
|
-
dim = fixed.ndim
|
|
409
|
-
|
|
410
|
-
# 3D registration does not seem to work with smaller slabs
|
|
411
|
-
# Exclude this case
|
|
412
|
-
if dim == 3:
|
|
413
|
-
if fixed.shape[-1] < 6:
|
|
414
|
-
msg = 'The 3D volume does not have enough slices for 3D registration. \n'
|
|
415
|
-
msg += 'Try 2D registration instead.'
|
|
416
|
-
raise ValueError(msg)
|
|
417
|
-
|
|
418
|
-
# Define the metric
|
|
419
|
-
if metric == "Cross-Correlation":
|
|
420
|
-
sigma_diff = 3.0 # Gaussian Kernel
|
|
421
|
-
radius = 4 # Window for local CC
|
|
422
|
-
metric = CCMetric(dim, sigma_diff, radius)
|
|
423
|
-
elif metric == 'Expectation-Maximization':
|
|
424
|
-
metric = EMMetric(dim, smooth=1.0)
|
|
425
|
-
elif metric == 'Sum of Squared Differences':
|
|
426
|
-
metric = SSDMetric(dim, smooth=4.0)
|
|
427
|
-
else:
|
|
428
|
-
msg = 'The metric ' + metric + ' is currently not implemented.'
|
|
429
|
-
raise ValueError(msg)
|
|
430
|
-
|
|
431
|
-
# Define the deformation model
|
|
432
|
-
if transformation == 'Symmetric Diffeomorphic':
|
|
433
|
-
level_iters = [200, 100, 50, 25]
|
|
434
|
-
sdr = SymmetricDiffeomorphicRegistration(metric, level_iters, inv_iter=50)
|
|
435
|
-
else:
|
|
436
|
-
msg = 'The transform ' + transformation + ' is currently not implemented.'
|
|
437
|
-
raise ValueError(msg)
|
|
438
|
-
|
|
439
|
-
# Perform the optimization, return a DiffeomorphicMap object
|
|
440
|
-
mapping = sdr.optimize(fixed, moving)
|
|
441
|
-
|
|
442
|
-
# Get forward deformation field
|
|
443
|
-
deformation_field = mapping.get_forward_field()
|
|
444
|
-
|
|
445
|
-
# Warp the moving image
|
|
446
|
-
warped_moving = mapping.transform(moving, 'linear')
|
|
447
|
-
|
|
448
|
-
return warped_moving, deformation_field
|