Beispiel #1
0
    def _call(self, x, out=None):
        """Back-project ``x`` and store the result in ``out`` if given."""

        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x, self.geometry, self.range,
                                                out)
            elif data_impl == 'cuda':
                backproj = getattr(self, 'astra_backprojector', None)
                if backproj is None:
                    self.astra_backprojector = AstraCudaBackProjectorImpl(
                        self.geometry,
                        self.range,
                        self.domain,
                        use_cache=self.use_cache)

                return self.astra_backprojector.call_backward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_back_projector(x, self.geometry, self.range,
                                               out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')
Beispiel #2
0
    def _call_real(self, x_real, out_real):
        """Real-space back-projection for the current set-up.

        This method also sets ``self._astra_backprojector`` for
        ``impl='astra_cuda'`` and enabled cache.
        """
        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x_real, self.geometry,
                                                self.range.real_space,
                                                out_real)
            elif data_impl == 'cuda':
                if self._astra_wrapper is None:
                    astra_wrapper = AstraCudaBackProjectorImpl(
                        self.geometry, self.range.real_space,
                        self.domain.real_space)
                    if self.use_cache:
                        self._astra_wrapper = astra_wrapper
                else:
                    astra_wrapper = self._astra_wrapper

                return astra_wrapper.call_backward(x_real, out_real)
            else:
                # Should never happen
                raise RuntimeError('bad `impl` {!r}'.format(self.impl))

        elif self.impl == 'skimage':
            return skimage_radon_back_projector(x_real, self.geometry,
                                                self.range.real_space,
                                                out_real)
        else:
            # Should never happen
            raise RuntimeError('bad `impl` {!r}'.format(self.impl))
    def _call_real(self, x_real, angle_array, out_real):

        geometry = self.spacegeometry(angle_array)
        partialrange = self.range(geometry)
        partialdomain = self.domain(geometry)

        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x_real, geometry,
                                                partialrange.real_space,
                                                out_real)
            elif data_impl == 'cuda':
                # if self._astra_wrapper is None:
                #     astra_wrapper = AstraCudaBackProjectorImpl(
                #         geometry, partialrange.real_space,
                #         partialdomain.real_space)
                #     if self.use_cache:
                #         self._astra_wrapper = astra_wrapper
                #
                # else:
                #     print("ever here?")
                #     astra_wrapper = self._astra_wrapper

                astra_wrapper = AstraCudaBackProjectorImpl(
                    geometry, partialrange.real_space,
                    partialdomain.real_space)
                return astra_wrapper.call_backward(x_real, out_real)
            else:
                # Should never happen
                raise RuntimeError('bad `impl` {!r}'.format(self.impl))

        else:
            # Should never happen
            raise RuntimeError('bad `impl` {!r}'.format(self.impl))
Beispiel #4
0
    def _call(self, x, out=None):
        """Back-project ``x`` and store the result in ``out`` if given."""

        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x, self.geometry,
                                                self.range, out)
            elif data_impl == 'cuda':
                backproj = getattr(self, 'astra_backprojector', None)
                if backproj is None:
                    self.astra_backprojector = AstraCudaBackProjectorImpl(
                        self.geometry, self.range, self.domain,
                        use_cache=self.use_cache)

                return self.astra_backprojector.call_backward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_back_projector(x, self.geometry,
                                               self.range, out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')
Beispiel #5
0
class RayBackProjection(Operator):
    """Adjoint of the discrete Ray transform between L^p spaces."""
    def __init__(self, discr_range, geometry, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        discr_range : `DiscreteLp`
            Reconstruction space, the range of the back-projector
        geometry : `Geometry`
            The geometry of the transform, contains information about
            the operator domain

        Other Parameters
        ----------------
        impl : {'astra_cpu', 'astra_cuda', 'scikit'}, optional
            Implementation back-end for the transform. Supported back-ends:
            * ``'astra_cuda'``: ASTRA toolbox, using CUDA, 2D or 3D
            * ``'astra_cpu'``: ASTRA toolbox using CPU, only 2D
            * ``'scikit'``: scikit-image, only 2D parallel with square domain
            If ``None`` is given, the fastest available back-end is used.
        interp : {'nearest', 'linear'}, optional
            Interpolation type for the discretization of the operator range.
            Default: 'nearest'
        discr_domain : `DiscreteLp`, optional
            Discretized space, the range of the forward projector.
            Default: Infered from parameters.
        use_cache : bool, optional
            If ``True``, data is cached. Note that this causes notable memory
            overhead, both on the GPU and on the CPU since a full volume and
            projection is stored. In the 3D case, some users may want to
            disable this.
        """
        if not isinstance(discr_range, DiscreteLp):
            raise TypeError('`discr_range` {!r} is not a `DiscreteLp`'
                            ' instance'.format(discr_range))

        if not isinstance(geometry, Geometry):
            raise TypeError('`geometry` {!r} is not a `Geometry` instance'
                            ''.format(geometry))

        impl = kwargs.pop('impl', None)
        if impl is None:
            # Select fastest available
            if ASTRA_CUDA_AVAILABLE:
                impl = 'astra_cuda'
            elif ASTRA_AVAILABLE:
                impl = 'astra_cpu'
            elif SCIKIT_IMAGE_AVAILABLE:
                impl = 'scikit'
            else:
                raise ValueError('no valid `impl` installed')

        impl, impl_in = str(impl).lower(), impl
        if impl not in _SUPPORTED_IMPL:
            raise ValueError("`impl` '{}' not supported" ''.format(impl_in))

        if impl.startswith('astra'):
            if not ASTRA_AVAILABLE:
                raise ValueError("'astra' backend not available")
            if impl == 'astra_cuda' and not ASTRA_CUDA_AVAILABLE:
                raise ValueError("'astra_cuda' backend not available")
            if not np.allclose(discr_range.partition.cell_sides[1:],
                               discr_range.partition.cell_sides[:-1],
                               atol=0,
                               rtol=1e-5):
                raise ValueError('ASTRA does not support different voxel '
                                 'sizes per axis, got {}'
                                 ''.format(discr_range.partition.cell_sides))

        self.use_cache = kwargs.pop('use_cache', True)

        self.__geometry = geometry
        self.__impl = impl
        self.kwargs = kwargs

        discr_domain = kwargs.pop('discr_domain', None)
        if discr_domain is None:
            dtype = discr_range.dspace.dtype

            # Create a discretized space (operator domain) with the same
            # data-space type as the range.
            domain_uspace = FunctionSpace(geometry.params, out_dtype=dtype)

            # Approximate cell volume
            extent = float(geometry.partition.extent.prod())
            size = float(geometry.partition.size)
            weight = extent / size

            domain_dspace = discr_range.dspace_type(geometry.partition.size,
                                                    weighting=weight,
                                                    dtype=dtype)

            if geometry.ndim == 2:
                axis_labels = ['$\\theta$', '$s$']
            elif geometry.ndim == 3:
                axis_labels = ['$\\theta$', '$u$', '$v$']
            else:
                # TODO Add this when we add nd ray transform.
                axis_labels = None

            domain_interp = kwargs.get('interp', 'nearest')
            discr_domain = DiscreteLp(domain_uspace,
                                      geometry.partition,
                                      domain_dspace,
                                      interp=domain_interp,
                                      order=discr_range.order,
                                      axis_labels=axis_labels)

        self.ray_trafo = None

        super().__init__(discr_domain, discr_range, linear=True)

    @property
    def impl(self):
        """Implementation back-end for evaluation of this operator."""
        return self.__impl

    @property
    def geometry(self):
        """Geometry of this operator."""
        return self.__geometry

    def _call(self, x, out=None):
        """Back-project ``x`` and store the result in ``out`` if given."""

        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x, self.geometry, self.range,
                                                out)
            elif data_impl == 'cuda':
                backproj = getattr(self, 'astra_backprojector', None)
                if backproj is None:
                    self.astra_backprojector = AstraCudaBackProjectorImpl(
                        self.geometry,
                        self.range,
                        self.domain,
                        use_cache=self.use_cache)

                return self.astra_backprojector.call_backward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_back_projector(x, self.geometry, self.range,
                                               out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')

    @property
    def adjoint(self):
        """Adjoint of this operator.

        Returns
        -------
        adjoint : `RayTransform`
        """
        if self.ray_trafo is not None:
            return self.ray_trafo

        kwargs = self.kwargs.copy()
        kwargs['discr_range'] = self.domain
        self.ray_trafo = RayTransform(self.range,
                                      self.geometry,
                                      impl=self.impl,
                                      use_cache=self.use_cache,
                                      **kwargs)
        return self.ray_trafo
Beispiel #6
0
class RayBackProjection(RayTransformBase):
    """Adjoint of the discrete Ray transform between L^p spaces."""
    def __init__(self, range, geometry, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        range : `DiscreteLp`
            Discretized reconstruction space, the range of the
            backprojection operator.
        geometry : `Geometry`
            Geometry of the transform, containing information about
            the operator domain (projection/sinogram space).

        Other Parameters
        ----------------
        impl : {`None`, 'astra_cuda', 'astra_cpu', 'skimage'}, optional
            Implementation back-end for the transform. Supported back-ends:

            - ``'astra_cuda'``: ASTRA toolbox, using CUDA, 2D or 3D
            - ``'astra_cpu'``: ASTRA toolbox using CPU, only 2D
            - ``'skimage'``: scikit-image, only 2D parallel with square
              reconstruction space.

            For the default ``None``, the fastest available back-end is
            used.
        interp : {'nearest', 'linear'}, optional
            Interpolation type for the discretization of the operator
            domain. This has no effect if ``domain`` is given explicitly.
            Default: ``'nearest'``
        domain : `DiscreteLp`, optional
            Discretized projection (sinogram) space, the domain of the
            backprojection operator.
            Default: Inferred from parameters.
        use_cache : bool, optional
            If ``True``, data is cached. This gives a significant speed-up
            at the expense of a notable memory overhead, both on the GPU
            and on the CPU, since a full volume and a projection dataset
            are stored. That may be prohibitive in 3D.
            Default: True

        Notes
        -----
        The ASTRA backend is faster if data is given with ``dtype`` 'float32'
        and storage order 'C'. Otherwise copies will be needed.
        """
        domain = kwargs.pop('domain', None)
        super().__init__(reco_space=range,
                         proj_space=domain,
                         geometry=geometry,
                         variant='backward',
                         **kwargs)

    def _call_real(self, x_real, out_real):
        """Real-space back-projection for the current set-up.

        This method also sets ``self._astra_backprojector`` for
        ``impl='astra_cuda'`` and enabled cache.
        """
        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x_real, self.geometry,
                                                self.range.real_space,
                                                out_real)
            elif data_impl == 'cuda':
                if self._astra_wrapper is None:
                    self._astra_wrapper = AstraCudaBackProjectorImpl(
                        self.geometry, self.range.real_space,
                        self.domain.real_space, self.use_cache)

                return self._astra_wrapper.call_backward(x_real, out_real)
            else:
                # Should never happen
                raise RuntimeError('bad `impl` {!r}'.format(self.impl))

        elif self.impl == 'skimage':
            return skimage_radon_back_projector(x_real, self.geometry,
                                                self.range.real_space,
                                                out_real)
        else:
            # Should never happen
            raise RuntimeError('bad `impl` {!r}'.format(self.impl))

    @property
    def adjoint(self):
        """Adjoint of this operator.

        Returns
        -------
        adjoint : `RayTransform`
        """
        if self._adjoint is not None:
            return self._adjoint

        kwargs = self._extra_kwargs.copy()
        kwargs['range'] = self.domain
        self._adjoint = RayTransform(self.range,
                                     self.geometry,
                                     impl=self.impl,
                                     use_cache=self.use_cache,
                                     **kwargs)
        return self._adjoint
Beispiel #7
0
class RayBackProjection(Operator):
    """Adjoint of the discrete Ray transform between L^p spaces."""

    def __init__(self, discr_range, geometry, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        discr_range : `DiscreteLp`
            Reconstruction space, the range of the back-projector
        geometry : `Geometry`
            The geometry of the transform, contains information about
            the operator domain

        Other Parameters
        ----------------
        impl : {'astra_cpu', 'astra_cuda', 'scikit'}, optional
            Implementation back-end for the transform. Supported back-ends:
            * ``'astra_cuda'``: ASTRA toolbox, using CUDA, 2D or 3D
            * ``'astra_cpu'``: ASTRA toolbox using CPU, only 2D
            * ``'scikit'``: scikit-image, only 2D parallel with square domain
            If ``None`` is given, the fastest available back-end is used.
        interp : {'nearest', 'linear'}, optional
            Interpolation type for the discretization of the operator range.
            Default: 'nearest'
        discr_domain : `DiscreteLp`, optional
            Discretized space, the range of the forward projector.
            Default: Infered from parameters.
        use_cache : bool, optional
            If ``True``, data is cached. Note that this causes notable memory
            overhead, both on the GPU and on the CPU since a full volume and
            projection is stored. In the 3D case, some users may want to
            disable this.
        """
        if not isinstance(discr_range, DiscreteLp):
            raise TypeError('`discr_range` {!r} is not a `DiscreteLp`'
                            ' instance'.format(discr_range))

        if not isinstance(geometry, Geometry):
            raise TypeError('`geometry` {!r} is not a `Geometry` instance'
                            ''.format(geometry))

        impl = kwargs.pop('impl', None)
        if impl is None:
            # Select fastest available
            if ASTRA_CUDA_AVAILABLE:
                impl = 'astra_cuda'
            elif ASTRA_AVAILABLE:
                impl = 'astra_cpu'
            elif SCIKIT_IMAGE_AVAILABLE:
                impl = 'scikit'
            else:
                raise ValueError('no valid `impl` installed')

        impl, impl_in = str(impl).lower(), impl
        if impl not in _SUPPORTED_IMPL:
            raise ValueError("`impl` '{}' not supported"
                             ''.format(impl_in))

        if impl.startswith('astra'):
            if not ASTRA_AVAILABLE:
                raise ValueError("'astra' backend not available")
            if impl == 'astra_cuda' and not ASTRA_CUDA_AVAILABLE:
                raise ValueError("'astra_cuda' backend not available")
            if not np.allclose(discr_range.partition.cell_sides[1:],
                               discr_range.partition.cell_sides[:-1],
                               atol=0, rtol=1e-5):
                raise ValueError('ASTRA does not support different voxel '
                                 'sizes per axis, got {}'
                                 ''.format(discr_range.partition.cell_sides))

        self.use_cache = kwargs.pop('use_cache', True)

        self.__geometry = geometry
        self.__impl = impl
        self.kwargs = kwargs

        discr_domain = kwargs.pop('discr_domain', None)
        if discr_domain is None:
            dtype = discr_range.dspace.dtype

            # Create a discretized space (operator domain) with the same
            # data-space type as the range.
            domain_uspace = FunctionSpace(geometry.params, out_dtype=dtype)

            # Approximate cell volume
            extent = float(geometry.partition.extent.prod())
            size = float(geometry.partition.size)
            weight = extent / size

            domain_dspace = discr_range.dspace_type(geometry.partition.size,
                                                    weighting=weight,
                                                    dtype=dtype)

            if geometry.ndim == 2:
                axis_labels = ['$\\theta$', '$s$']
            elif geometry.ndim == 3:
                axis_labels = ['$\\theta$', '$u$', '$v$']
            else:
                # TODO Add this when we add nd ray transform.
                axis_labels = None

            domain_interp = kwargs.get('interp', 'nearest')
            discr_domain = DiscreteLp(
                domain_uspace, geometry.partition, domain_dspace,
                interp=domain_interp, order=discr_range.order,
                axis_labels=axis_labels)

        self.ray_trafo = None

        super().__init__(discr_domain, discr_range, linear=True)

    @property
    def impl(self):
        """Implementation back-end for evaluation of this operator."""
        return self.__impl

    @property
    def geometry(self):
        """Geometry of this operator."""
        return self.__geometry

    def _call(self, x, out=None):
        """Back-project ``x`` and store the result in ``out`` if given."""

        if self.impl.startswith('astra'):
            backend, data_impl = self.impl.split('_')
            if data_impl == 'cpu':
                return astra_cpu_back_projector(x, self.geometry,
                                                self.range, out)
            elif data_impl == 'cuda':
                backproj = getattr(self, 'astra_backprojector', None)
                if backproj is None:
                    self.astra_backprojector = AstraCudaBackProjectorImpl(
                        self.geometry, self.range, self.domain,
                        use_cache=self.use_cache)

                return self.astra_backprojector.call_backward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_back_projector(x, self.geometry,
                                               self.range, out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')

    @property
    def adjoint(self):
        """Adjoint of this operator.

        Returns
        -------
        adjoint : `RayTransform`
        """
        if self.ray_trafo is not None:
            return self.ray_trafo

        kwargs = self.kwargs.copy()
        kwargs['discr_range'] = self.domain
        self.ray_trafo = RayTransform(self.range, self.geometry,
                                      impl=self.impl,
                                      use_cache=self.use_cache,
                                      **kwargs)
        return self.ray_trafo