def skimage_radon_forward_projector(volume, geometry, proj_space, out=None): """Calculate forward projection using skimage. Parameters ---------- volume : `DiscretizedSpaceElement` The volume to project. geometry : `Geometry` The projection geometry to use. proj_space : `DiscretizedSpace` Space in which the projections (sinograms) live. out : ``proj_space`` element, optional Element to which the result should be written. Returns ------- sinogram : ``proj_space`` element Result of the forward projection. If ``out`` was given, the returned object is a reference to it. """ # Lazy import due to significant import time from skimage.transform import radon # Check basic requirements. Fully checking should be in wrapper assert volume.shape[0] == volume.shape[1] theta = np.degrees(geometry.angles) skimage_range = skimage_proj_space(geometry, volume.space, proj_space) # Rotate volume from (x, y) to (rows, cols), then project sino_arr = radon( np.rot90(volume.asarray(), 1), theta=theta, circle=False ) sinogram = skimage_range.element(sino_arr.T) if out is None: out = proj_space.element() with writable_array(out) as out_arr: point_collocation( clamped_interpolation(skimage_range, sinogram), proj_space.grid.meshgrid, out=out_arr, ) scale = volume.space.cell_sides[0] out *= scale return out
def astra_cuda_forward_projector(vol_data, geometry, proj_space, out=None): """Run an ASTRA forward projection on the given data using the GPU. Parameters ---------- vol_data : `DiscreteLpElement` Volume data to which the projector is applied geometry : `Geometry` Geometry defining the tomographic setup proj_space : `DiscreteLp` Space to which the calling operator maps out : ``proj_space`` element, optional Element of the projection space to which the result is written. If ``None``, an element in ``proj_space`` is created. Returns ------- out : ``proj_space`` element Projection data resulting from the application of the projector. If ``out`` was provided, the returned object is a reference to it. """ if not isinstance(vol_data, DiscreteLpElement): raise TypeError('volume data {!r} is not a DiscreteLpElement ' 'instance'.format(vol_data)) if not isinstance(geometry, Geometry): raise TypeError('geometry {!r} is not a Geometry instance' ''.format(geometry)) if vol_data.ndim != geometry.ndim: raise ValueError('dimensions {} of volume data and {} of geometry ' 'do not match' ''.format(vol_data.ndim, geometry.ndim)) if not isinstance(proj_space, DiscreteLp): raise TypeError('projection space {!r} is not a DiscreteLp ' 'instance'.format(proj_space)) if out is not None: if not isinstance(out, DiscreteLpElement): raise TypeError('`out` {} is neither None nor a ' 'DiscreteLpElement instance'.format(out)) ndim = vol_data.ndim # Create astra geometries vol_geom = astra_volume_geometry(vol_data.space) proj_geom = astra_projection_geometry(geometry) # Create ASTRA data structures # In the case dim == 3, we need to swap axes, so can't perform the FP # in-place vol_id = astra_data(vol_geom, datatype='volume', data=vol_data, allow_copy=True) # Create projector proj_id = astra_projector('nearest', vol_geom, proj_geom, ndim, impl='cuda') if ndim == 2: if out is None: out = proj_space.element() # Wrap the array in correct dtype etc if needed with writable_array(out, dtype='float32', order='C') as arr: sino_id = astra_data(proj_geom, datatype='projection', data=arr) # Create algorithm algo_id = astra_algorithm('forward', ndim, vol_id, sino_id, proj_id=proj_id, impl='cuda') # Run algorithm astra.algorithm.run(algo_id) elif ndim == 3: sino_id = astra_data(proj_geom, datatype='projection', ndim=proj_space.ndim) # Create algorithm algo_id = astra_algorithm('forward', ndim, vol_id, sino_id, proj_id=proj_id, impl='cuda') # Run algorithm astra.algorithm.run(algo_id) if out is None: out = proj_space.element(np.rollaxis(astra.data3d.get(sino_id), 0, 3)) else: out[:] = np.rollaxis(astra.data3d.get(sino_id), 0, 3) else: raise RuntimeError('unknown ndim') # Fix inconsistent scaling if isinstance(geometry, Parallel2dGeometry): # parallel2d scales with pixel stride out *= 1 / float(geometry.det_partition.cell_sides[0]) # Delete ASTRA objects astra.algorithm.delete(algo_id) if ndim == 2: astra.data2d.delete((vol_id, sino_id)) astra.projector.delete(proj_id) else: astra.data3d.delete((vol_id, sino_id)) astra.projector3d.delete(proj_id) return out
def skimage_radon_back_projector(sinogram, geometry, vol_space, out=None): """Calculate forward projection using skimage. Parameters ---------- sinogram : `DiscretizedSpaceElement` Sinogram (projections) to backproject. geometry : `Geometry` The projection geometry to use. vol_space : `DiscretizedSpace` Space in which reconstructed volumes live. out : ``vol_space`` element, optional An element to which the result should be written. Returns ------- volume : ``vol_space`` element Result of the back-projection. If ``out`` was given, the returned object is a reference to it. """ # Lazy import due to significant import time from skimage.transform import iradon theta = np.degrees(geometry.angles) skimage_range = skimage_proj_space(geometry, vol_space, sinogram.space) skimage_sinogram = skimage_range.element() with writable_array(skimage_sinogram) as sino_arr: point_collocation( clamped_interpolation(sinogram.space, sinogram), skimage_range.grid.meshgrid, out=sino_arr, ) if out is None: out = vol_space.element() else: # Only do asserts here since these are backend functions assert out in vol_space # Rotate back from (rows, cols) to (x, y), then back-project (no filter) backproj = iradon( skimage_sinogram.asarray().T, theta, output_size=vol_space.shape[0], filter=None, circle=False, ) out[:] = np.rot90(backproj, -1) # Empirically determined value, gives correct scaling scaling_factor = 4 * geometry.motion_params.length / (2 * np.pi) # Correct in case of non-weighted spaces proj_volume = np.prod(sinogram.space.partition.extent) proj_size = sinogram.space.partition.size proj_weighting = proj_volume / proj_size scaling_factor *= sinogram.space.weighting.const / proj_weighting scaling_factor /= vol_space.weighting.const / vol_space.cell_volume # Correctly scale the output out *= scaling_factor return out
def astra_cuda_back_projector(proj_data, geometry, reco_space, out=None): """Run an ASTRA backward projection on the given data using the GPU. Parameters ---------- proj_data : `DiscreteLp` element Projection data to which the backward projector is applied geometry : `Geometry` Geometry defining the tomographic setup reco_space : `DiscreteLp` Space to which the calling operator maps out : ``reco_space`` element, optional Element of the reconstruction space to which the result is written. If ``None``, an element in ``reco_space`` is created. Returns ------- out : ``reco_space`` element Reconstruction data resulting from the application of the backward projector. If ``out`` was provided, the returned object is a reference to it. """ if not isinstance(proj_data, DiscreteLpElement): raise TypeError('projection data {!r} is not a DiscreteLpElement ' 'instance'.format(proj_data)) if not isinstance(geometry, Geometry): raise TypeError('geometry {!r} is not a Geometry instance' ''.format(geometry)) if not isinstance(reco_space, DiscreteLp): raise TypeError('reconstruction space {!r} is not a DiscreteLp ' 'instance'.format(reco_space)) if reco_space.ndim != geometry.ndim: raise ValueError('dimensions {} of reconstruction space and {} of ' 'geometry do not match'.format(reco_space.ndim, geometry.ndim)) if out is not None: if not isinstance(out, DiscreteLpElement): raise TypeError('`out` {} is neither None nor a ' 'DiscreteLpElement instance'.format(out)) ndim = proj_data.ndim # Create geometries vol_geom = astra_volume_geometry(reco_space) proj_geom = astra_projection_geometry(geometry) if ndim == 2: swapped_proj_data = proj_data else: swapped_proj_data = np.ascontiguousarray( np.rollaxis(proj_data.asarray(), 2, 0)) sino_id = astra_data(proj_geom, datatype='projection', data=swapped_proj_data, allow_copy=True) # Create projector proj_id = astra_projector('nearest', vol_geom, proj_geom, ndim, impl='cuda') # Reconstruction volume if out is None: out = reco_space.element() # Wrap the array in correct dtype etc if needed with writable_array(out, dtype='float32', order='C') as arr: vol_id = astra_data(vol_geom, datatype='volume', data=arr, ndim=reco_space.ndim) # Create algorithm algo_id = astra_algorithm('backward', ndim, vol_id, sino_id, proj_id=proj_id, impl='cuda') # Run algorithm astra.algorithm.run(algo_id) out *= astra_cuda_bp_scaling_factor(reco_space, geometry) # Delete ASTRA objects astra.algorithm.delete(algo_id) if ndim == 2: astra.data2d.delete((vol_id, sino_id)) astra.projector.delete(proj_id) else: astra.data3d.delete((vol_id, sino_id)) astra.projector3d.delete(proj_id) return out