Skip to content

Commit a851738

Browse files
committed
STY: Add documentation
1 parent 8a5a197 commit a851738

File tree

3 files changed

+38
-53
lines changed

3 files changed

+38
-53
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Reconstruct Mayo dataset using FBP and compare to reference recon.
22
33
Note that this example requires that Mayo has been previously downloaded and is
4-
stored in the location indicated by "mayo_dir".
4+
stored in the location indicated by "proj_folder" and "rec_folder".
55
66
In this example we only use a subset of the data for performance reasons,
7-
there are ~32 000 projections in the full dataset.
7+
there are ~32 000 projections per patient in the full dataset.
88
"""
99
import numpy as np
1010
import odl
@@ -14,16 +14,16 @@
1414
# define data folders
1515
proj_folder = odl.__path__[0] + '/../../data/LDCT-and-Projection-data/' \
1616
'L004/08-21-2018-10971/1.000000-Full dose projections-24362/'
17-
img_folder = odl.__path__[0] + '/../../data/LDCT-and-Projection-data/' \
17+
rec_folder = odl.__path__[0] + '/../../data/LDCT-and-Projection-data/' \
1818
'L004/08-21-2018-84608/1.000000-Full dose images-59704/'
1919

2020
# Load projection data
2121
print("Loading projection data from {:s}".format(proj_folder))
2222
geometry, proj_data = mayo.load_projections(proj_folder,
2323
indices=slice(16000, 19000))
2424
# Load reconstruction data
25-
print("Loading reference data from {:s}".format(img_folder))
26-
recon_space, volume = mayo.load_reconstruction(img_folder)
25+
print("Loading reference data from {:s}".format(rec_folder))
26+
recon_space, volume = mayo.load_reconstruction(rec_folder)
2727

2828
# ray transform
2929
ray_trafo = odl.tomo.RayTransform(recon_space, geometry)

odl/contrib/datasets/ct/mayo.py

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def _read_projections(folder, indices):
4242
"""Read mayo projections from a folder."""
4343
datasets = []
4444
data_array = []
45-
angles = []
4645

4746
# Get the relevant file names
4847
file_names = sorted([f for f in os.listdir(folder) if f.endswith(".dcm")])
@@ -82,19 +81,15 @@ def _read_projections(folder, indices):
8281
proj_array = np.array(np.frombuffer(dataset.PixelData, 'H'),
8382
dtype='float32')
8483
proj_array = proj_array.reshape([cols, rows])
85-
# proj_array = proj_array.reshape([rows, cols], order='F').T
86-
87-
# Rescale array (no HU)
88-
proj_array *= rescale_slope
89-
proj_array += rescale_intercept
90-
9184
data_array.append(proj_array[:, ::-1])
92-
angles.append(dataset.DetectorFocalCenterAngularPosition)
9385
datasets.append(dataset)
9486

9587
data_array = np.array(data_array)
96-
angles = np.array(angles)
97-
return datasets, data_array, angles
88+
# Rescale array
89+
data_array *= rescale_slope
90+
data_array += rescale_intercept
91+
92+
return datasets, data_array
9893

9994

10095
def load_projections(folder, indices=None, use_ffs=True):
@@ -107,10 +102,9 @@ def load_projections(folder, indices=None, use_ffs=True):
107102
indices : optional
108103
Indices of the projections to load.
109104
Accepts advanced indexing such as slice or list of indices.
110-
num_slices: int, optional
111-
Number of slices to consider for the reconstruction volume;
112-
the other parameters are hard-coded. With default value (None)
113-
a *temporary* volume is created.
105+
use_ffs : bool, optional
106+
If ``True``, a source shift is applied to compensate the flying focal spot.
107+
Default: ``True``
114108
115109
Returns
116110
-------
@@ -120,10 +114,12 @@ def load_projections(folder, indices=None, use_ffs=True):
120114
Projection data, given as the line integral of the linear attenuation
121115
coefficient (g/cm^3). Its unit is thus g/cm^2.
122116
"""
123-
datasets, data_array, angles = _read_projections(folder, indices)
117+
datasets, data_array = _read_projections(folder, indices)
124118

119+
# Get the angles
120+
angles = np.array([d.DetectorFocalCenterAngularPosition for d in datasets])
125121
# Reverse angular axis and set origin at 6 o'clock
126-
angles = -np.unwrap(angles) - np.pi
122+
angles = -np.unwrap(angles) - np.pi
127123

128124
# Set minimum and maximum corners
129125
det_shape = np.array([datasets[0].NumberofDetectorColumns,
@@ -148,11 +144,11 @@ def load_projections(folder, indices=None, use_ffs=True):
148144
num_rot = (angles[-1] - angles[0]) / (2 * np.pi)
149145
pitch = table_dist / num_rot
150146

151-
# offsets: focal spot -> detector’s focal center
147+
# offsets: detector’s focal center -> focal spot
152148
offset_angular = np.array([d.SourceAngularPositionShift for d in datasets])
153149
offset_radial = np.array([d.SourceRadialDistanceShift for d in datasets])
154150
offset_axial = np.array([d.SourceAxialPositionShift for d in datasets])
155-
151+
156152
# angles have inverse convention
157153
shift_d = np.cos(-offset_angular) * (src_radius + offset_radial) - src_radius
158154
shift_t = np.sin(-offset_angular) * (src_radius + offset_radial)
@@ -164,7 +160,6 @@ def load_projections(folder, indices=None, use_ffs=True):
164160
detector_partition = odl.uniform_partition(det_minp, det_maxp, det_shape)
165161

166162
# Convert offset to odl definitions
167-
# offset_along_axis = (mean_offset_along_axis_for_ffz +
168163
offset_along_axis = datasets[0].DetectorFocalCenterAxialPosition - \
169164
angles[0] / (2 * np.pi) * pitch
170165

@@ -187,35 +182,26 @@ def load_projections(folder, indices=None, use_ffs=True):
187182

188183
return geometry, data_array
189184

190-
191185

192-
def get_default_recon_space():
193-
# Create a *temporary* ray transform (we need its range)
194-
num_slices = 97
195-
pixel_spacing = np.array([0.75,0.75])
196-
num_pixel = np.array([512,512])
197-
slice_dist = 5.
198-
origin = np.zeros(3)
199-
mid_table = (datasets[0].DetectorFocalCenterAxialPosition +
200-
datasets[-1].DetectorFocalCenterAxialPosition) / 2
201-
min_pt = np.copy(origin)
202-
min_pt[:2] -= pixel_spacing * num_pixel / 2
203-
min_pt[2] += mid_table - num_slices * slice_dist / 2
204-
205-
max_pt = np.copy(min_pt)
206-
max_pt[:2] += pixel_spacing * num_pixel
207-
max_pt[2] += num_slices * slice_dist
208-
209-
recon_dim = np.array([*num_pixel, num_slices], dtype=np.int32)
210-
recon_space = odl.uniform_discr(min_pt, max_pt,
211-
shape=recon_dim,
212-
dtype=np.float32)
213-
return recon_space
186+
def interpolate_flat_grid(data_array, range_grid, radial_dist):
187+
"""Return the linear interpolator of the projection data on a flat detector.
214188
189+
Parameters
190+
----------
191+
data_array : `numpy.ndarray`
192+
Projection data on the cylindrical detector that should be interpolated.
193+
range_grid : RectGrid
194+
Rectilinear grid on the flat detector.
195+
radial_dist : float
196+
The constant radial distance, that is the distance between the detector’s
197+
focal center and its central element.
215198
216-
# ray_trafo = odl.tomo.RayTransform(recon_space, geometry, interp='linear')
199+
Returns
200+
-------
201+
proj_data : `numpy.ndarray`
202+
Interpolated projection data on the flat rectilinear grid.
203+
"""
217204

218-
def interpolate_flat_grid(data_array, range_grid, radial_dist):
219205
# convert coordinates
220206
theta, up, vp = range_grid.meshgrid #ray_trafo.range.grid.meshgrid
221207
# d = src_radius + det_radius
@@ -225,14 +211,13 @@ def interpolate_flat_grid(data_array, range_grid, radial_dist):
225211
# Calculate projection data in rectangular coordinates since we have no
226212
# backend that supports cylindrical
227213
interpolator = linear_interpolator(
228-
data_array, range_grid.coord_vectors # ray_trafo.range.grid.coord_vectors
214+
data_array, range_grid.coord_vectors
229215
)
230216
proj_data = interpolator((theta, u, v))
231217

232218
return proj_data
233219

234220

235-
236221
def load_reconstruction(folder, slice_start=0, slice_end=-1):
237222
"""Load a volume from folder, also returns the corresponding partition.
238223
@@ -291,7 +276,6 @@ def load_reconstruction(folder, slice_start=0, slice_end=-1):
291276
data_array = np.rot90(data_array, -1)
292277

293278
# Convert from storage type to densities
294-
# TODO: Optimize these computations
295279
hu_values = (dataset.RescaleSlope * data_array +
296280
dataset.RescaleIntercept)
297281

odl/contrib/datasets/ct/mayo_dicom_dict.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# _dicom_dict.py
22
"""
33
DICOM data dictionary auto-generated by DICOM-CT-PD-dict_to_pydicom.py
4-
Update: Added (7041,1001) "Water Attenuation Coefficient"
4+
Update: Add (7041,1001) "Water Attenuation Coefficient"
5+
Swap (7033,100B) and (7033,100C)
56
"""
67
from __future__ import absolute_import
78

0 commit comments

Comments
 (0)