1111In addition to the standard ODL requirements, this library also requires:
1212
1313 - tqdm
14- - dicom
15- - A copy of the Mayo dataset, see
16- https://www.aapm.org/GrandChallenge/LowDoseCT/#registration
14+ - pydicom
15+ - Samples from the Mayo dataset, see
16+ https://ctcicblog.mayo.edu/hubcap/patient-ct-projection-data-library/
1717"""
1818
1919from __future__ import division
2323import pydicom
2424import odl
2525import tqdm
26+ from functools import partial
2627
2728from pydicom .datadict import DicomDictionary , keyword_dict
2829from odl .discr .discr_utils import linear_interpolator
3839__all__ = ('load_projections' , 'load_reconstruction' )
3940
4041
41- def _read_projections (folder , indices ):
42- """Read mayo projections from a folder ."""
42+ def _read_projections (dir , indices ):
43+ """Read mayo projections from a directory ."""
4344 datasets = []
4445 data_array = []
4546
4647 # Get the relevant file names
47- file_names = sorted ([f for f in os .listdir (folder ) if f .endswith (".dcm" )])
48+ file_names = sorted ([f for f in os .listdir (dir ) if f .endswith (".dcm" )])
4849
4950 if len (file_names ) == 0 :
50- raise ValueError ('No DICOM files found in {}' .format (folder ))
51+ raise ValueError ('No DICOM files found in {}' .format (dir ))
5152
5253 file_names = file_names [indices ]
5354
5455 for i , file_name in enumerate (tqdm .tqdm (file_names ,
5556 'Loading projection data' )):
5657 # read the file
5758 try :
58- dataset = pydicom .read_file (folder + os .path .sep + file_name )
59+ dataset = pydicom .read_file (os .path .join ( dir , file_name ) )
5960 except :
6061 print ("corrupted file: {}" .format (file_name ), file = sys .stderr )
6162 print ("error:\n {}" .format (sys .exc_info ()[1 ]), file = sys .stderr )
62- print ("skipping to next file.." , file = sys .stderr )
63- continue
63+ raise
6464
6565 if not data_array :
6666 # Get some required data
@@ -76,29 +76,28 @@ def _read_projections(folder, indices):
7676 assert rescale_intercept == dataset .RescaleIntercept
7777 assert rescale_slope == dataset .RescaleSlope
7878
79-
8079 # Load the array as bytes
8180 proj_array = np .array (np .frombuffer (dataset .PixelData , 'H' ),
8281 dtype = 'float32' )
8382 proj_array = proj_array .reshape ([cols , rows ])
8483 data_array .append (proj_array [:, ::- 1 ])
8584 datasets .append (dataset )
8685
87- data_array = np .array (data_array )
86+ data_array = np .stack (data_array )
8887 # Rescale array
8988 data_array *= rescale_slope
9089 data_array += rescale_intercept
9190
9291 return datasets , data_array
9392
9493
95- def load_projections (folder , indices = None , use_ffs = True ):
96- """Load geometry and data stored in Mayo format from folder .
94+ def load_projections (dir , indices = None , use_ffs = True ):
95+ """Load geometry and data stored in Mayo format from dir .
9796
9897 Parameters
9998 ----------
100- folder : str
101- Path to the folder where the Mayo DICOM files are stored.
99+ dir : str
100+ Path to the directory where the Mayo DICOM files are stored.
102101 indices : optional
103102 Indices of the projections to load.
104103 Accepts advanced indexing such as slice or list of indices.
@@ -114,7 +113,7 @@ def load_projections(folder, indices=None, use_ffs=True):
114113 Projection data, given as the line integral of the linear attenuation
115114 coefficient (g/cm^3). Its unit is thus g/cm^2.
116115 """
117- datasets , data_array = _read_projections (folder , indices )
116+ datasets , data_array = _read_projections (dir , indices )
118117
119118 # Get the angles
120119 angles = np .array ([d .DetectorFocalCenterAngularPosition for d in datasets ])
@@ -139,8 +138,8 @@ def load_projections(folder, indices=None, use_ffs=True):
139138 # For unknown reasons, mayo does not include the tag
140139 # "TableFeedPerRotation", which is what we want.
141140 # Instead we manually compute the pitch
142- table_dist = datasets [- 1 ].DetectorFocalCenterAxialPosition - \
143- datasets [0 ].DetectorFocalCenterAxialPosition
141+ table_dist = ( datasets [- 1 ].DetectorFocalCenterAxialPosition -
142+ datasets [0 ].DetectorFocalCenterAxialPosition )
144143 num_rot = (angles [- 1 ] - angles [0 ]) / (2 * np .pi )
145144 pitch = table_dist / num_rot
146145
@@ -160,17 +159,19 @@ def load_projections(folder, indices=None, use_ffs=True):
160159 detector_partition = odl .uniform_partition (det_minp , det_maxp , det_shape )
161160
162161 # Convert offset to odl definitions
163- offset_along_axis = datasets [0 ].DetectorFocalCenterAxialPosition - \
164- angles [0 ] / (2 * np .pi ) * pitch
162+ offset_along_axis = ( datasets [0 ].DetectorFocalCenterAxialPosition -
163+ angles [0 ] / (2 * np .pi ) * pitch )
165164
166165 # Assemble geometry
167166 angle_partition = odl .nonuniform_partition (angles )
168167
169168 # Flying focal spot
170169 src_shift_func = None
171170 if use_ffs :
172- src_shift_func = lambda angle : odl .tomo .flying_focal_spot (
173- angle , apart = angle_partition , shifts = shifts )
171+ src_shift_func = partial (
172+ odl .tomo .flying_focal_spot , apart = angle_partition , shifts = shifts )
173+ else :
174+ src_shift_func = None
174175
175176 geometry = odl .tomo .ConeBeamGeometry (angle_partition ,
176177 detector_partition ,
@@ -203,8 +204,8 @@ def interpolate_flat_grid(data_array, range_grid, radial_dist):
203204 """
204205
205206 # convert coordinates
206- theta , up , vp = range_grid .meshgrid #ray_trafo.range.grid.meshgrid
207- # d = src_radius + det_radius
207+ theta , up , vp = range_grid .meshgrid
208+
208209 u = radial_dist * np .arctan (up / radial_dist )
209210 v = radial_dist / np .sqrt (radial_dist ** 2 + up ** 2 ) * vp
210211
@@ -218,13 +219,13 @@ def interpolate_flat_grid(data_array, range_grid, radial_dist):
218219 return proj_data
219220
220221
221- def load_reconstruction (folder , slice_start = 0 , slice_end = - 1 ):
222- """Load a volume from folder , also returns the corresponding partition.
222+ def load_reconstruction (dir , slice_start = 0 , slice_end = - 1 ):
223+ """Load a volume from dir , also returns the corresponding partition.
223224
224225 Parameters
225226 ----------
226- folder : str
227- Path to the folder where the DICOM files are stored.
227+ dir : str
228+ Path to the directory where the DICOM files are stored.
228229 slice_start : int
229230 Index of the first slice to use. Used for subsampling.
230231 slice_end : int
@@ -249,10 +250,10 @@ def load_reconstruction(folder, slice_start=0, slice_end=-1):
249250 This function should handle all of these peculiarities and give a volume
250251 with the correct coordinate system attached.
251252 """
252- file_names = sorted ([f for f in os .listdir (folder ) if f .endswith (".dcm" )])
253+ file_names = sorted ([f for f in os .listdir (dir ) if f .endswith (".dcm" )])
253254
254255 if len (file_names ) == 0 :
255- raise ValueError ('No DICOM files found in {}' .format (folder ))
256+ raise ValueError ('No DICOM files found in {}' .format (dir ))
256257
257258 volumes = []
258259 datasets = []
@@ -261,7 +262,7 @@ def load_reconstruction(folder, slice_start=0, slice_end=-1):
261262
262263 for file_name in tqdm .tqdm (file_names , 'loading volume data' ):
263264 # read the file
264- dataset = pydicom .read_file (folder + os .path .sep + file_name )
265+ dataset = pydicom .read_file (os .path .join ( dir , file_name ) )
265266
266267 # Get parameters
267268 pixel_size = np .array (dataset .PixelSpacing )
0 commit comments