Source code for toupy.tomo.tomorecons

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# standard packages
import time

# third party package
from ..utils.plot_utils import plt
import numpy as np
from ..utils import tqdm

# local packages
from ..utils import display_slice
from .iradon import backprojector, reconsSART
from ..restoration import derivatives_sino
from .fdk import fdk_reconstruct          # cone-beam FDK
from .geometry import ConeBeamGeometry    # cone-beam geometry descriptor

__all__ = ["full_tomo_recons", "tomo_recons", "fdk_tomo_recons"]


[docs] def tomo_recons(sinogram, theta, **params): """ Wrapper to select tomographic algorithm Parameters ---------- sinogram : ndarray A 2-dimensional array containing the sinogram theta : ndarray A 1-dimensional array of thetas params : dict Dictionary containing additional parameters params["algorithm"] : str Choice of algorithm. Two algorithm implemented: "FBP" and "SART" params["slicenum"] : int Slice number params["filtertype"] : str Name of the filter to be applied in frequency domain filtering. The options are: `ram-lak`, `shepp-logan`, `cosine`, `hamming`, `hann`. Assign None to use no filter. params["freqcutoff"] : float Frequency cutoff (between 0 and 1) params["circle"] : bool Multiply the reconstructed slice by a circle to remove borders params["weight_angles"] : bool If `True`, weights each projection with a factor proportional to the angular distance between the neighboring projections. .. math:: \Delta \phi_0 \longmapsto \Delta \phi_j = \frac{\phi_{j+1} - \phi_{j-1}}{2} params["derivatives"] : bool If the projections are derivatives. Only for FBP. params["calc_derivatives"] : bool Calculate derivatives of the sinogram if not done yet. params["opencl"] : bool Implement the tomographic reconstruction in opencl as implemented in Silx params["autosave"] : bool Save the data at the end without asking params["vmin_plot"] : float Minimum value for the gray level at each display params["vmax_plot"] : float Maximum value for the gray level at each display params["colormap"] : str Colormap params["showrecons"] : bool If to show the reconstructed slices Returns ------- recons : ndarray A 2-dimensional array containing the reconstructed slice. """ if params["algorithm"] == "FBP": if params["calc_derivatives"]: print("Calculating the derivatives of the sinogram") sinogram = derivatives_sino(sinogram, shift_method="fourier") recons = backprojector(sinogram, theta, **params) elif params["algorithm"] == "SART": if params["calc_derivatives"]: raise ValueError("Reconstruction from derivatives only works with FBP") recons = reconsSART(sinogram, theta, **params) return recons
[docs] def full_tomo_recons(input_stack, theta, **params): """ Full tomographic reconstruction Parameters ---------- input_stack : ndarray A 3-dimensional array containing the stack of projections. The order should be ``[projection_num, row, column]`` theta : ndarray A 1-dimensional array of thetas params : dict Dictionary containing additional parameters params["algorithm"] : str Choice of algorithm. Two algorithm implemented: "FBP" and "SART" params["slicenum"] : int Slice number params["filtertype"] : str Filter to use for FBP params["freqcutoff"] : float Frequency cutoff (between 0 and 1) params["circle"] : bool Multiply the reconstructed slice by a circle to remove borders params["derivatives"] : bool If the projections are derivatives. Only for FBP. params["calc_derivatives"] : bool Calculate derivatives of the sinogram if not done yet. params["opencl"] : bool Implement the tomographic reconstruction in CUDA params["autosave"] : bool Save the data at the end without asking params["vmin_plot"] : float Minimum value for the gray level at each display params["vmax_plot"] : float Maximum value for the gray level at each display params["colormap"] : str Colormap params["showrecons"] : bool If to show the reconstructed slices Returns ------- tomogram : ndarray A 3-dimensional array containing the full reconstructed tomogram. """ print("Calculating a slice for display") slicenum = params["slicenum"] sinogram0 = np.transpose(input_stack[:, slicenum, :]) # calculating one slice for estimating sizes t0 = time.time() tomogram0 = tomo_recons(sinogram0, theta, **params) nr, nc = tomogram0.shape # size of the slices print("Calculation done. Time elapsed: {:.02f} s".format(time.time() - t0)) # display one slice plt.close("all") display_slice( tomogram0, colormap=params["colormap"], vmin=params["cliplow"], vmax=params["cliphigh"], ) # estimate the total number of slices nslices = input_stack.shape[1] print( "The total number of slices is {}. Starting full reconstruction …".format( nslices ), flush=True, ) print( "Tip: interrupt the kernel at any time to abort. " "Adjust params['vmin_plot'] / params['vmax_plot'] if the preview " "colour scale needs changing before re-running.", flush=True, ) plt.close("all") tomogram = np.zeros((nslices, nr, nc), dtype=np.float32) for ii in tqdm(range(nslices), desc="Reconstructing slices"): sinogram = np.transpose(input_stack[:, ii, :]) tomogram[ii] = tomo_recons(sinogram, theta, **params) return tomogram
# --------------------------------------------------------------------------- # Cone-beam high-level wrapper # ---------------------------------------------------------------------------
[docs] def fdk_tomo_recons( projections, geometry: ConeBeamGeometry, **params, ): """ High-level FDK cone-beam reconstruction wrapper. Mirrors :func:`tomo_recons` for cone-beam geometry. Validates the geometry, applies optional pre-processing, calls the three-step FDK pipeline, and optionally displays the central reconstructed slice. Parameters ---------- projections : ndarray, shape (n_angles, n_v, n_u) Flat-field-corrected, Beer-Lambert log-normalised cone-beam projections. geometry : ConeBeamGeometry Validated acquisition geometry. **params Algorithm parameters. Recognised keys: filtertype : str Ramp-filter window (forwarded to :func:`~toupy.tomo.fdk.fdk_filter`). Default ``'ram-lak'``. freqcutoff : float Normalised frequency cutoff in ``(0, 1]``. Default ``1``. output_size : int or None Transaxial reconstruction grid side length. ``None`` uses ``geometry.n_u``. cuda : bool Use CuPy GPU path. Default ``False``. circle : bool Multiply the reconstructed volume by a cylindrical mask to suppress border artefacts. Default ``False``. showrecons : bool Display the central axial slice after reconstruction. Default ``False``. colormap : str Colormap for display. Default ``'bone'``. vmin_plot : float or None Display window minimum. vmax_plot : float or None Display window maximum. autosave : bool Save the volume to disk without prompting. Default ``False``. Returns ------- volume : ndarray, shape (n_v, N, N) Reconstructed 3-D volume. Raises ------ ValueError If ``projections.ndim != 3`` or shape is inconsistent with ``geometry``. See Also -------- tomo_recons : Parallel-beam (2-D) slice reconstruction wrapper. full_tomo_recons : Slice-by-slice parallel-beam full reconstruction. toupy.tomo.fdk.fdk_reconstruct : Bare FDK pipeline (no display/save). """ from ..utils import create_circle filtertype = params.get("filtertype", "ram-lak") freqcutoff = params.get("freqcutoff", 1.0) output_size = params.get("output_size", None) cuda = params.get("cuda", False) circle = params.get("circle", False) showrecons = params.get("showrecons", False) colormap = params.get("colormap", "bone") vmin_plot = params.get("vmin_plot", None) vmax_plot = params.get("vmax_plot", None) geometry.validate() if projections.ndim != 3: raise ValueError( "projections must be 3-D (n_angles, n_v, n_u), got ndim={}.".format( projections.ndim ) ) volume = fdk_reconstruct( projections, geometry, filter_type=filtertype, freqcutoff=freqcutoff, output_size=output_size, cuda=cuda, ) if circle: # Apply cylindrical mask to each axial slice central = volume[volume.shape[0] // 2] mask = create_circle(central) volume = volume * mask[np.newaxis, :, :] if showrecons: central = volume[volume.shape[0] // 2] display_slice(central, colormap=colormap, vmin=vmin_plot, vmax=vmax_plot) return volume