Skip to content

Commit 96a7d5e

Browse files
committed
STY: pull request mods
1 parent a851738 commit 96a7d5e

File tree

3 files changed

+58
-53
lines changed

3 files changed

+58
-53
lines changed

odl/contrib/datasets/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Reference datasets with accompanying ODL geometries etc.
2222
* `walnut_data`
2323
* `lotus_root_data`
2424

25-
CT data as provided by Mayo Clinic. The data is from a human and of high resolution (512x512). To access the data, see [the webpage](https://www.aapm.org/GrandChallenge/LowDoseCT/#registration). Note that downloading this dataset requires signing up and signing a terms of use form.
25+
CT data as provided by Mayo Clinic. The data is from a human and of high resolution (512x512). To access the data, see [the webpage](https://ctcicblog.mayo.edu/hubcap/patient-ct-projection-data-library/). Note that downloading this dataset requires installing the NBIA Data Retriever.
2626
* `load_projections`
2727
* `load_reconstruction`
2828
* `images`

odl/contrib/datasets/ct/examples/mayo_reconstruct.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
"""Reconstruct Mayo dataset using FBP and compare to reference recon.
22
3-
Note that this example requires that Mayo has been previously downloaded and is
4-
stored in the location indicated by "proj_folder" and "rec_folder".
3+
Note that this example requires that projection and reconstruction data
4+
of a CT scan from the Mayo dataset have been previously downloaded (see
5+
[the webpage](https://ctcicblog.mayo.edu/hubcap/patient-ct-projection-data-library/))
6+
and are stored in the locations indicated by "proj_dir" and "rec_dir".
57
6-
In this example we only use a subset of the data for performance reasons,
7-
there are ~32 000 projections per patient in the full dataset.
8+
9+
In this example we only use a subset of the data for performance reasons.
10+
The number of projections per patient varies in the full dataset. To get
11+
a reconstruction of the central part of the volume, modify the indices in
12+
the argument of the `mayo.load_projections` function accordingly.
813
"""
914
import numpy as np
15+
import os
1016
import odl
1117
from odl.contrib.datasets.ct import mayo
1218
from time import perf_counter
1319

14-
# define data folders
15-
proj_folder = odl.__path__[0] + '/../../data/LDCT-and-Projection-data/' \
16-
'L004/08-21-2018-10971/1.000000-Full dose projections-24362/'
17-
rec_folder = odl.__path__[0] + '/../../data/LDCT-and-Projection-data/' \
18-
'L004/08-21-2018-84608/1.000000-Full dose images-59704/'
20+
# replace with your local directory
21+
mayo_dir = ''
22+
# define projection and reconstruction data directories
23+
# e.g. for patient L004 full dose CT scan:
24+
proj_dir = os.path.join(
25+
mayo_dir, 'L004/08-21-2018-10971/1.000000-Full dose projections-24362/')
26+
rec_dir = os.path.join(
27+
mayo_dir, 'L004/08-21-2018-84608/1.000000-Full dose images-59704/')
1928

20-
# Load projection data
21-
print("Loading projection data from {:s}".format(proj_folder))
22-
geometry, proj_data = mayo.load_projections(proj_folder,
29+
# Load projection data restricting to a central slice
30+
print("Loading projection data from {:s}".format(proj_dir))
31+
geometry, proj_data = mayo.load_projections(proj_dir,
2332
indices=slice(16000, 19000))
2433
# Load reconstruction data
25-
print("Loading reference data from {:s}".format(rec_folder))
26-
recon_space, volume = mayo.load_reconstruction(rec_folder)
34+
print("Loading reference data from {:s}".format(rec_dir))
35+
recon_space, volume = mayo.load_reconstruction(rec_dir)
2736

2837
# ray transform
2938
ray_trafo = odl.tomo.RayTransform(recon_space, geometry)
@@ -48,11 +57,6 @@
4857

4958
fbp_result_HU = (fbp_result-0.0192)/0.0192*1000
5059

51-
# Save reconstruction in Numpy format
52-
fbp_filename = proj_folder+'/fbp_result.npy'
53-
print("Saving reconstruction data in {:s}".format(fbp_filename))
54-
np.save(fbp_filename, fbp_result_HU)
55-
5660
# Compare the computed recon to reference reconstruction (coronal slice)
5761
ref = recon_space.element(volume)
5862
diff = recon_space.element(volume - fbp_result_HU.asarray())
@@ -64,4 +68,4 @@
6468

6569
coords = [0, None, None]
6670
fbp_result_HU.show('Recon (sagittal)', coords=coords)
67-
ref.show('Reference (sagittal)', coords=coords)
71+
ref.show('Reference (sagittal)', coords=coords)

odl/contrib/datasets/ct/mayo.py

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
In 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

1919
from __future__ import division
@@ -23,6 +23,7 @@
2323
import pydicom
2424
import odl
2525
import tqdm
26+
from functools import partial
2627

2728
from pydicom.datadict import DicomDictionary, keyword_dict
2829
from odl.discr.discr_utils import linear_interpolator
@@ -38,29 +39,28 @@
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

Comments
 (0)