Source code for nabu.pipeline.helical.fbp
from ...reconstruction.fbp import *
from .filtering import HelicalSinoFilter
from ...utils import convert_index
[docs]
class BackprojectorHelical(Backprojector):
"""This is the Backprojector derived class for helical reconstruction.
The modifications are detailed here :
* the backprojection is decoupled from the filtering. This allows, in the pipeline,
for doing first a filtering using backprojector_object.sino_filter subobject,
then calling backprojector_object.backprojection only after reweigthing the result of
the filter_sino method of sino_filter subobject.
* the angles can be resetted on the fly, and the class can work with a variable number of projection.
As a matter of fact, with the helical_chunked_regridded.py pipeline, the reconstruction is done
each time with the same set of angles, this because of the regridding mechanism. The feature
might return useful in the future if alternative approachs are developed again.
*
"""
[docs]
def set_custom_angles_and_axis_corrections(self, angles_rad, x_per_proj):
"""To arbitrarily change angles
Parameters
==========
angles_rad: array of floats
one angle per each projection in radians
x_per_proj: array of floats
each entry is the axis shift for a projection, in pixels.
"""
self.n_provided_angles = len(angles_rad)
self._axis_correction = np.zeros((1, self.n_angles), dtype=np.float32)
self._axis_correction[0, : self.n_provided_angles] = -x_per_proj
self.angles[: self.n_provided_angles] = angles_rad
self._compute_angles_again()
self.kern_proj_args[1] = self.n_provided_angles
self.kern_proj_args[6] = self.offsets["x"]
self.kern_proj_args[7] = self.offsets["y"]
def _compute_angles_again(self):
"""to update angles dependent auxiliary arrays"""
self.h_cos[0] = np.cos(self.angles).astype("f")
self.h_msin[0] = (-np.sin(self.angles)).astype("f")
self._d_msin[:] = self.h_msin[0]
self._d_cos[:] = self.h_cos[0]
if self._axis_correction is not None:
self._d_axcorr[:] = self._axis_correction
def _init_filter(self, filter_name):
"""To use the HelicalSinoFilter which is derived from SinoFilter
with a slightly different padding scheme
"""
self.filter_name = filter_name
self.sino_filter = HelicalSinoFilter(
self.sino_shape,
filter_name=self.filter_name,
padding_mode=self.padding_mode,
cuda_options={"ctx": self.cuda_processing.ctx},
)
[docs]
def backprojection(self, sino, output=None):
"""Redefined here to do backprojection only, compare to the base class method."""
self._d_sino[:] = sino
res = self.backproj(self._d_sino, output=output)
return res
def _init_geometry(self, sino_shape, slice_shape, angles, rot_center, slice_roi):
"""this is identical to _init_geometry of the base class with the
exception that self.extra_options["centered_axis"] is not considered
and as a consequence self.offsets is not set here and the one of _set_slice_roi
is kept.
"""
if slice_shape is not None and slice_roi is not None:
raise ValueError("slice_shape and slice_roi cannot be used together")
self.sino_shape = sino_shape
if len(sino_shape) == 2:
n_angles, dwidth = sino_shape
else:
raise ValueError("Expected 2D sinogram")
self.dwidth = dwidth
self.rot_center = rot_center or (self.dwidth - 1) / 2.0
self._set_slice_shape(
slice_shape,
)
self.axis_pos = self.rot_center
self._set_angles(angles, n_angles)
self._set_slice_roi(slice_roi)
self._set_axis_corr()
def _set_slice_shape(self, slice_shape):
"""this is identical to the _set_slice_shape ofthe base class
with the exception that n_y,n_x default to the largest possible reconstructible slice
"""
n_y = self.dwidth + abs(self.dwidth - 1 - self.rot_center * 2)
n_x = self.dwidth + abs(self.dwidth - 1 - self.rot_center * 2)
if slice_shape is not None:
if np.isscalar(slice_shape):
slice_shape = (slice_shape, slice_shape)
n_y, n_x = slice_shape
self.n_x = n_x
self.n_y = n_y
self.slice_shape = (n_y, n_x)
def _set_slice_roi(self, slice_roi):
"""Automatically tune the offset to in all cases."""
self.slice_roi = slice_roi
if slice_roi is None:
off = -(self.dwidth - 1 - self.rot_center * 2)
if off < 0:
self.offsets = {"x": off, "y": off}
else:
self.offsets = {"x": 0, "y": 0}
else:
start_x, end_x, start_y, end_y = slice_roi
# convert negative indices
slice_width, _ = self.slice_shape
off = min(0, -(self.dwidth - 1 - self.rot_center * 2))
if end_x < start_x:
start_x = off
end_x = off + slice_width
if end_y < start_y:
start_y = off
end_y = off + slice_width
self.slice_shape = (end_y - start_y, end_x - start_x)
self.n_x = self.slice_shape[-1]
self.n_y = self.slice_shape[-2]
self.offsets = {"x": start_x, "y": start_y}