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.

Files changed (52) hide show
  1. dbdicom/__init__.py +1 -28
  2. dbdicom/api.py +267 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/dataset.py +752 -0
  5. dbdicom/dbd.py +719 -0
  6. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  7. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/register.py +527 -0
  10. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  11. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +153 -26
  12. dbdicom/{ds/types → sop_classes}/mr_image.py +185 -140
  13. dbdicom/sop_classes/parametric_map.py +307 -0
  14. dbdicom/sop_classes/secondary_capture.py +140 -0
  15. dbdicom/sop_classes/segmentation.py +311 -0
  16. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  17. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  18. dbdicom/utils/arrays.py +36 -0
  19. dbdicom/utils/files.py +0 -20
  20. dbdicom/utils/image.py +10 -629
  21. dbdicom-0.3.0.dist-info/METADATA +28 -0
  22. dbdicom-0.3.0.dist-info/RECORD +53 -0
  23. dbdicom/create.py +0 -457
  24. dbdicom/dro.py +0 -174
  25. dbdicom/ds/__init__.py +0 -10
  26. dbdicom/ds/create.py +0 -63
  27. dbdicom/ds/dataset.py +0 -869
  28. dbdicom/ds/dictionaries.py +0 -620
  29. dbdicom/ds/types/parametric_map.py +0 -226
  30. dbdicom/extensions/__init__.py +0 -9
  31. dbdicom/extensions/dipy.py +0 -448
  32. dbdicom/extensions/elastix.py +0 -503
  33. dbdicom/extensions/matplotlib.py +0 -107
  34. dbdicom/extensions/numpy.py +0 -271
  35. dbdicom/extensions/scipy.py +0 -1512
  36. dbdicom/extensions/skimage.py +0 -1030
  37. dbdicom/extensions/sklearn.py +0 -243
  38. dbdicom/extensions/vreg.py +0 -1390
  39. dbdicom/manager.py +0 -2132
  40. dbdicom/message.py +0 -119
  41. dbdicom/pipelines.py +0 -66
  42. dbdicom/record.py +0 -1893
  43. dbdicom/types/database.py +0 -107
  44. dbdicom/types/instance.py +0 -231
  45. dbdicom/types/patient.py +0 -40
  46. dbdicom/types/series.py +0 -2874
  47. dbdicom/types/study.py +0 -58
  48. dbdicom-0.2.6.dist-info/METADATA +0 -72
  49. dbdicom-0.2.6.dist-info/RECORD +0 -66
  50. {dbdicom-0.2.6.dist-info → dbdicom-0.3.0.dist-info}/WHEEL +0 -0
  51. {dbdicom-0.2.6.dist-info → dbdicom-0.3.0.dist-info}/licenses/LICENSE +0 -0
  52. {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)
@@ -1,9 +0,0 @@
1
- from . import (
2
- dipy,
3
- elastix,
4
- numpy,
5
- scipy,
6
- skimage,
7
- sklearn,
8
- matplotlib,
9
- )
@@ -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