Source code for cil.plugins.astra.operators.ProjectionOperator

#  Copyright 2020 United Kingdom Research and Innovation
#  Copyright 2020 The University of Manchester
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
# Authors:
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt
import logging

from cil.framework import BlockGeometry
from cil.framework.labels import AcquisitionDimension, ImageDimension, AcquisitionType
from cil.optimisation.operators import BlockOperator, LinearOperator, ChannelwiseOperator
from cil.plugins.astra.operators import AstraProjector2D, AstraProjector3D

log = logging.getLogger(__name__)


[docs] class ProjectionOperator(LinearOperator): """ ProjectionOperator configures and calls appropriate ASTRA Projectors for your dataset. Parameters ---------- image_geometry : ``ImageGeometry``, default used if None A description of the area/volume to reconstruct acquisition_geometry : ``AcquisitionGeometry``, ``BlockGeometry`` A description of the acquisition data. If passed a BlockGeometry it will return a BlockOperator. device : string, default='gpu' 'gpu' will run on a compatible CUDA capable device using the ASTRA 3D CUDA Projectors, 'cpu' will run on CPU using the ASTRA 2D CPU Projectors Example ------- >>> from cil.plugins.astra import ProjectionOperator >>> PO = ProjectionOperator(image.geometry, data.geometry) >>> forward_projection = PO.direct(image) >>> backward_projection = PO.adjoint(data) Notes ----- For multichannel data the ProjectionOperator will broadcast across all channels. """ def __new__(cls, image_geometry=None, acquisition_geometry=None, \ device='gpu', **kwargs): if isinstance(acquisition_geometry, BlockGeometry): log.info("BlockOperator is returned.") K = [] for ag in acquisition_geometry: K.append( ProjectionOperator_ag(image_geometry=image_geometry, acquisition_geometry=ag, \ device=device, **kwargs) ) return BlockOperator(*K) else: log.info("Standard Operator is returned.") return super(ProjectionOperator, cls).__new__(ProjectionOperator_ag)
class ProjectionOperator_ag(ProjectionOperator): """ ProjectionOperator configures and calls appropriate ASTRA Projectors for your dataset. Parameters ---------- image_geometry : ImageGeometry, default used if None A description of the area/volume to reconstruct acquisition_geometry : AcquisitionGeometry A description of the acquisition data device : string, default='gpu' 'gpu' will run on a compatible CUDA capable device using the ASTRA 3D CUDA Projectors, 'cpu' will run on CPU using the ASTRA 2D CPU Projectors Example ------- >>> from cil.plugins.astra import ProjectionOperator >>> PO = ProjectionOperator(image.geometry, data.geometry) >>> forward_projection = PO.direct(image) >>> backward_projection = PO.adjoint(data) Notes ----- For multichannel data the ProjectionOperator will broadcast across all channels. """ def __init__(self, image_geometry=None, acquisition_geometry=None, device='gpu'): if acquisition_geometry is None: raise TypeError( "Please specify an acquisition_geometry to configure this operator" ) if image_geometry is None: image_geometry = acquisition_geometry.get_ImageGeometry() super(ProjectionOperator_ag, self).__init__(domain_geometry=image_geometry, range_geometry=acquisition_geometry) AcquisitionDimension.check_order_for_engine('astra',acquisition_geometry) ImageDimension.check_order_for_engine('astra',image_geometry) self.volume_geometry = image_geometry self.sinogram_geometry = acquisition_geometry sinogram_geometry_sc = acquisition_geometry.get_slice(channel=0) volume_geometry_sc = image_geometry.get_slice(channel=0) if device == 'gpu': operator = AstraProjector3D(volume_geometry_sc, sinogram_geometry_sc) elif AcquisitionType.DIM2 & self.sinogram_geometry.dimension: operator = AstraProjector2D(volume_geometry_sc, sinogram_geometry_sc, device=device) else: raise NotImplementedError("Cannot process 3D data without a GPU") if acquisition_geometry.channels > 1: operator_full = ChannelwiseOperator( operator, self.sinogram_geometry.channels, dimension='prepend') self.operator = operator_full else: self.operator = operator def direct(self, IM, out=None): '''Applies the direct of the operator i.e. the forward projection. Parameters ---------- IM : ImageData The image/volume to be projected. out : DataContainer, optional Fills the referenced DataContainer with the processed data and suppresses the return Returns ------- DataContainer The processed data. Suppressed if `out` is passed ''' return self.operator.direct(IM, out=out) def adjoint(self, DATA, out=None): '''Applies the adjoint of the operator, i.e. the backward projection. Parameters ---------- DATA : AcquisitionData The projections/sinograms to be projected. out : DataContainer, optional Fills the referenced DataContainer with the processed data and suppresses the return Returns ------- DataContainer The processed data. Suppressed if `out` is passed ''' return self.operator.adjoint(DATA, out=out) def calculate_norm(self): return self.operator.norm() def domain_geometry(self): return self.volume_geometry def range_geometry(self): return self.sinogram_geometry