Skip to content

Commit 1a5f3c3

Browse files
author
Holger Kohr
committed
ENH: rotate geometry instead of data in 2D
1 parent 3184558 commit 1a5f3c3

File tree

3 files changed

+34
-41
lines changed

3 files changed

+34
-41
lines changed

odl/tomo/backends/astra_cpu.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,7 @@ def astra_cpu_forward_projector(vol_data, geometry, proj_space, out=None):
9797
impl='cpu')
9898

9999
# Create ASTRA data structures
100-
# Since ASTRA uses (rows, cols) == (-y, x) as coordinate system, we need
101-
# to rotate by 90 degrees counter-clockwise to match the axes (x, y) as
102-
# used in `astra_volume_geometry`.
103-
vol_data_arr = np.rot90(np.asarray(vol_data), 1)
100+
vol_data_arr = np.asarray(vol_data)
104101
vol_id = astra_data(vol_geom, datatype='volume', data=vol_data_arr,
105102
allow_copy=True)
106103

@@ -195,8 +192,8 @@ def astra_cpu_back_projector(proj_data, geometry, reco_space, out=None):
195192

196193
# Convert out to correct dtype and order if needed.
197194
# Since we transpose, we need to use F-contiguousness.
198-
with writable_array(out, dtype='float32', order='F') as out_arr:
199-
vol_id = astra_data(vol_geom, datatype='volume', data=out_arr.T,
195+
with writable_array(out, dtype='float32', order='C') as out_arr:
196+
vol_id = astra_data(vol_geom, datatype='volume', data=out_arr,
200197
ndim=reco_space.ndim)
201198
# Create algorithm
202199
algo_id = astra_algorithm('backward', ndim, vol_id, sino_id, proj_id,
@@ -205,13 +202,6 @@ def astra_cpu_back_projector(proj_data, geometry, reco_space, out=None):
205202
# Run algorithm
206203
astra.algorithm.run(algo_id)
207204

208-
# If we hadn't transposed `out_arr` we would have to rotate
209-
# clockwise by 90 degrees to invert the transition from (rows, cols)
210-
# to (x, y). Since transposition happens automatically when exiting
211-
# this context, we need to flip vertically, because
212-
# rot90(a, -1) == fliplr(a.T)
213-
out_arr[:] = np.fliplr(out_arr)
214-
215205
# Weight the adjoint by appropriate weights
216206
scaling_factor = float(proj_data.space.weighting.const)
217207
scaling_factor /= float(reco_space.weighting.const)

odl/tomo/backends/astra_cuda.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,7 @@ def call_forward(self, vol_data, out=None):
8787

8888
# Copy data to GPU memory
8989
if self.geometry.ndim == 2:
90-
# Rotate 90 degrees counter-clockwise to transfer from coordinate
91-
# system (x, y) to (rows, cols)
92-
# TODO: rotate the geometry instead
93-
astra.data2d.store(self.vol_id, np.rot90(vol_data.asarray(), 1))
90+
astra.data2d.store(self.vol_id, vol_data.asarray())
9491
elif self.geometry.ndim == 3:
9592
astra.data3d.store(self.vol_id, vol_data.asarray())
9693
else:
@@ -127,8 +124,7 @@ def create_ids(self):
127124

128125
if proj_ndim == 2:
129126
astra_proj_shape = proj_shape
130-
astra_vol_shape = (self.reco_space.shape[1],
131-
self.reco_space.shape[0])
127+
astra_vol_shape = self.reco_space.shape
132128
elif proj_ndim == 3:
133129
astra_proj_shape = (proj_shape[2], proj_shape[0], proj_shape[1])
134130
astra_vol_shape = self.reco_space.shape
@@ -247,12 +243,7 @@ def call_backward(self, proj_data, out=None):
247243
astra.algorithm.run(self.algo_id)
248244

249245
# Copy result to CPU memory
250-
if self.geometry.ndim == 2:
251-
# Rotate 90 degrees clockwise from coordinate system
252-
# (rows, cols) to (x, y)
253-
out[:] = np.rot90(self.out_array, -1)
254-
elif self.geometry.ndim == 3:
255-
out[:] = self.out_array
246+
out[:] = self.out_array
256247

257248
# Fix scaling to weight by pixel/voxel size
258249
out *= astra_cuda_bp_scaling_factor(
@@ -273,8 +264,7 @@ def create_ids(self):
273264

274265
if proj_ndim == 2:
275266
astra_proj_shape = proj_shape
276-
astra_vol_shape = (self.reco_space.shape[1],
277-
self.reco_space.shape[0])
267+
astra_vol_shape = self.reco_space.shape
278268
elif proj_ndim == 3:
279269
astra_proj_shape = (proj_shape[2], proj_shape[0], proj_shape[1])
280270
astra_vol_shape = self.reco_space.shape

odl/tomo/backends/astra_setup.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from odl.discr import DiscreteLp, DiscreteLpElement
5454
from odl.tomo.geometry import (
5555
Geometry, DivergentBeamGeometry, ParallelBeamGeometry, FlatDetector)
56+
from odl.tomo.util.utility import euler_matrix
5657
from odl.util.utility import pkg_supports
5758

5859

@@ -165,17 +166,21 @@ def astra_volume_geometry(discr_reco):
165166
'non-isotropic pixels in 2d volumes not supported by ASTRA '
166167
'v{}'.format(ASTRA_VERSION))
167168
# Given a 2D array of shape (x, y), a volume geometry is created as:
168-
# astra.create_vol_geom(y, x, x_min, x_max, y_min, y_max)
169+
# astra.create_vol_geom(x, y, y_min, y_max, x_min, x_max)
169170
# yielding a dictionary:
170-
# {'GridRowCount': y,
171-
# 'GridColCount': x,
172-
# 'WindowMinX': x_min,
173-
# 'WindowMaxX': x_max,
174-
# 'WindowMinY': y_min,
175-
# 'WindowMaxY': y_max}
176-
vol_geom = astra.create_vol_geom(vol_shp[1], vol_shp[0],
177-
vol_min[0], vol_max[0],
178-
vol_min[1], vol_max[1])
171+
# {'GridRowCount': x,
172+
# 'GridColCount': y,
173+
# 'WindowMinX': y_min,
174+
# 'WindowMaxX': y_max,
175+
# 'WindowMinY': x_min,
176+
# 'WindowMaxY': x_max}
177+
#
178+
# NOTE: this setting is flipped with respect to x and y. We do this
179+
# as part of a global rotation of the geometry by -90 degrees, which
180+
# avoids rotating the data.
181+
vol_geom = astra.create_vol_geom(vol_shp[0], vol_shp[1],
182+
vol_min[1], vol_max[1],
183+
vol_min[0], vol_max[0])
179184
elif discr_reco.ndim == 3:
180185
# Not supported in all versions of ASTRA
181186
if (not discr_reco.partition.has_isotropic_cells and
@@ -300,19 +305,24 @@ def astra_conebeam_2d_geom_to_vec(geometry):
300305
.. _ASTRA projection geometry documentation:
301306
http://www.astra-toolbox.com/docs/geom3d.html#projection-geometries
302307
"""
308+
# Instead of rotating the data by 90 degrees counter-clockwise,
309+
# we subtract pi/2 from the geometry angles, thereby rotating the
310+
# geometry by 90 degrees clockwise
311+
rot_minus_90 = euler_matrix(-np.pi / 2)
303312
angles = geometry.angles
304313
vectors = np.zeros((angles.size, 6))
305314

306315
for ang_idx, angle in enumerate(angles):
307316
# Source position
308-
vectors[ang_idx, 0:2] = geometry.src_position(angle)
317+
vectors[ang_idx, 0:2] = rot_minus_90.dot(geometry.src_position(angle))
309318

310319
# Center of detector
311320
mid_pt = geometry.det_params.mid_pt
312-
vectors[ang_idx, 2:4] = geometry.det_point_position(angle, mid_pt)
321+
vectors[ang_idx, 2:4] = rot_minus_90.dot(
322+
geometry.det_point_position(angle, mid_pt))
313323

314324
# Vector from detector pixel 0 to 1
315-
det_axis = geometry.det_axis(angle)
325+
det_axis = rot_minus_90.dot(geometry.det_axis(angle))
316326
px_size = geometry.det_partition.cell_sides[0]
317327
vectors[ang_idx, 4:6] = det_axis * px_size
318328

@@ -413,7 +423,10 @@ def astra_projection_geometry(geometry):
413423
# TODO: change to parallel_vec when available
414424
det_width = geometry.det_partition.cell_sides[0]
415425
det_count = geometry.detector.size
416-
angles = geometry.angles
426+
# Instead of rotating the data by 90 degrees counter-clockwise,
427+
# we subtract pi/2 from the geometry angles, thereby rotating the
428+
# geometry by 90 degrees clockwise
429+
angles = geometry.angles - np.pi / 2
417430
proj_geom = astra.create_proj_geom('parallel', det_width, det_count,
418431
angles)
419432

0 commit comments

Comments
 (0)