Esempio n. 1
0
    def _call_real(self, x_real, out_real):
        """Real-space forward projection for the current set-up.

        This method also sets ``self._astra_projector`` 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_forward_projector(x_real, self.geometry,
                                                   self.range.real_space,
                                                   out_real)

            elif data_impl == 'cuda':
                if self._astra_wrapper is None:
                    astra_wrapper = AstraCudaProjectorImpl(
                        self.geometry, self.domain.real_space,
                        self.range.real_space)
                    if self.use_cache:
                        self._astra_wrapper = astra_wrapper
                else:
                    astra_wrapper = self._astra_wrapper

                return astra_wrapper.call_forward(x_real, out_real)
            else:
                # Should never happen
                raise RuntimeError('bad `impl` {!r}'.format(self.impl))
        elif self.impl == 'skimage':
            return skimage_radon_forward(x_real, self.geometry,
                                         self.range.real_space, out_real)
        else:
            # Should never happen
            raise RuntimeError('bad `impl` {!r}'.format(self.impl))
Esempio n. 2
0
    def _call(self, x, out=None):
        """Forward 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_forward_projector(x, self.geometry,
                                                   self.range, out)
            elif data_impl == 'cuda':
                proj = getattr(self, 'astra_projector', None)
                if proj is None:
                    self.astra_projector = AstraCudaProjectorImpl(
                        self.geometry,
                        self.domain,
                        self.range,
                        use_cache=self.use_cache)

                return self.astra_projector.call_forward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_forward(x, self.geometry, self.range, out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')
    def _call_real(self, x_real, angle_array, out_real):
        """Real-space forward projection for the current set-up."""
        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_forward_projector(x_real, geometry,
                                                   partialrange.real_space,
                                                   out_real)

            elif data_impl == 'cuda':
                # if self._astra_wrapper is None:
                #     astra_wrapper = AstraCudaProjectorImpl(
                #         geometry, partialdomain.real_space,
                #         partialrange.real_space)
                #     if self.use_cache:
                #         self._astra_wrapper = astra_wrapper
                # else:
                #     print("ever here?")
                #     astra_wrapper = self._astra_wrapper
                astra_wrapper = AstraCudaProjectorImpl(
                    geometry, partialdomain.real_space,
                    partialrange.real_space)

                return astra_wrapper.call_forward(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))
Esempio n. 4
0
    def _call(self, x, out=None):
        """Forward 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_forward_projector(x, self.geometry,
                                                   self.range, out)
            elif data_impl == 'cuda':
                proj = getattr(self, 'astra_projector', None)
                if proj is None:
                    self.astra_projector = AstraCudaProjectorImpl(
                        self.geometry, self.domain, self.range,
                        use_cache=self.use_cache)

                return self.astra_projector.call_forward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_forward(x, self.geometry, self.range, out)
        else:  # Should never happen
            raise RuntimeError('implementation info is inconsistent')
Esempio n. 5
0
class RayTransform(Operator):
    """Discrete Ray transform between L^p spaces."""
    def __init__(self, discr_domain, geometry, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        discr_domain : `DiscreteLp`
            Discretized space, the domain of the forward projector
        geometry : `Geometry`
            Geometry of the transform, containing information about
            the operator range

        Other Parameters
        ----------------
        impl : {`None`, 'astra_cuda', 'astra_cpu', '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_range : `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.
            Default: True

        Notes
        -----
        The ASTRA backend is faster if data is given with ``dtype`` 'float32'
        and storage order 'C'. Otherwise copies will be needed.
        """
        if not isinstance(discr_domain, DiscreteLp):
            raise TypeError('`discr_domain` {!r} is not a `DiscreteLp`'
                            ' instance'.format(discr_domain))

        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` {!r} not supported' ''.format(impl_in))

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

        # TODO: sanity checks between impl and discretization impl
        if impl.startswith('astra'):
            # TODO: these should be moved somewhere else
            if not ASTRA_AVAILABLE:
                raise ValueError("'astra' back-end not available")
            if impl == 'astra_cuda' and not ASTRA_CUDA_AVAILABLE:
                raise ValueError("'astra_cuda' back-end not available")
            if not np.allclose(discr_domain.partition.cell_sides[1:],
                               discr_domain.partition.cell_sides[:-1]):
                raise ValueError('ASTRA does not support different voxel '
                                 'sizes per axis, got {}'
                                 ''.format(discr_domain.partition.cell_sides))
            if geometry.ndim > 2 and impl.endswith('cpu'):
                raise ValueError('`impl` {}, only works for 2d geometries'
                                 ' got {}-d'.format(impl_in, geometry))
        elif impl == 'scikit':
            if not isinstance(geometry, Parallel2dGeometry):
                raise TypeError("'scikit' backend only supports 2d parallel "
                                'geometries')

            mid_pt = discr_domain.domain.mid_pt
            if not all(mid_pt == [0, 0]):
                raise ValueError('`discr_domain.domain` needs to be '
                                 'centered on [0, 0], got {}'.format(mid_pt))

            shape = discr_domain.shape
            if shape[0] != shape[1]:
                raise ValueError('`discr_domain.shape` needs to be square '
                                 'got {}'.format(shape))

            extent = discr_domain.domain.extent
            if extent[0] != extent[1]:
                raise ValueError('`discr_domain.extent` needs to be square '
                                 'got {}'.format(extent))

        # TODO: sanity checks between domain and geometry (ndim, ...)
        self.__geometry = geometry
        self.__impl = impl
        self.kwargs = kwargs

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

            # Create a discretized space (operator range) with the same
            # data-space type as the domain.
            # TODO: use a ProductSpace structure or find a way to treat
            # different dimensions differently in DiscreteLp
            # (i.e. in partitions).
            range_uspace = FunctionSpace(geometry.params, out_dtype=dtype)

            # Approximate cell volume
            # TODO: angles and detector must be handled separately. While the
            # detector should be uniformly discretized, the angles do not have
            # to and often are not.
            extent = float(geometry.partition.extent.prod())
            size = float(geometry.partition.size)
            weight = extent / size

            range_dspace = discr_domain.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

            range_interp = kwargs.get('interp', 'nearest')
            discr_range = DiscreteLp(range_uspace,
                                     geometry.partition,
                                     range_dspace,
                                     interp=range_interp,
                                     order=discr_domain.order,
                                     axis_labels=axis_labels)

        self.backproj = 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):
        """Forward 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_forward_projector(x, self.geometry,
                                                   self.range, out)
            elif data_impl == 'cuda':
                proj = getattr(self, 'astra_projector', None)
                if proj is None:
                    self.astra_projector = AstraCudaProjectorImpl(
                        self.geometry,
                        self.domain,
                        self.range,
                        use_cache=self.use_cache)

                return self.astra_projector.call_forward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_forward(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 : `RayBackProjection`
        """
        if self.backproj is not None:
            return self.backproj

        kwargs = self.kwargs.copy()
        kwargs['discr_domain'] = self.range
        self.backproj = RayBackProjection(self.domain,
                                          self.geometry,
                                          impl=self.impl,
                                          use_cache=self.use_cache,
                                          **kwargs)
        return self.backproj
Esempio n. 6
0
class RayTransform(RayTransformBase):
    """Discrete Ray transform between L^p spaces."""
    def __init__(self, domain, geometry, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        domain : `DiscreteLp`
            Discretized reconstruction space, the domain of the forward
            projector.
        geometry : `Geometry`
            Geometry of the transform, containing information about
            the operator range (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
            range. This has no effect if ``range`` is given explicitly.
            Default: ``'nearest'``
        range : `DiscreteLp`, optional
            Discretized projection (sinogram) space, the range of the
            forward projector.
            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.
        """
        range = kwargs.pop('range', None)
        super().__init__(reco_space=domain,
                         proj_space=range,
                         geometry=geometry,
                         variant='forward',
                         **kwargs)

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

        This method also sets ``self._astra_projector`` 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_forward_projector(x_real, self.geometry,
                                                   self.range.real_space,
                                                   out_real)

            elif data_impl == 'cuda':
                if self._astra_wrapper is None:
                    self._astra_wrapper = AstraCudaProjectorImpl(
                        self.geometry, self.domain.real_space,
                        self.range.real_space, self.use_cache)

                return self._astra_wrapper.call_forward(x_real, out_real)
            else:
                # Should never happen
                raise RuntimeError('bad `impl` {!r}'.format(self.impl))
        elif self.impl == 'skimage':
            return skimage_radon_forward(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 : `RayBackProjection`
        """
        if self._adjoint is not None:
            return self._adjoint

        kwargs = self._extra_kwargs.copy()
        kwargs['domain'] = self.range
        self._adjoint = RayBackProjection(self.domain,
                                          self.geometry,
                                          impl=self.impl,
                                          use_cache=self.use_cache,
                                          **kwargs)
        return self._adjoint
Esempio n. 7
0
class RayTransform(Operator):

    """Discrete Ray transform between L^p spaces."""

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

        Parameters
        ----------
        discr_domain : `DiscreteLp`
            Discretized space, the domain of the forward projector
        geometry : `Geometry`
            Geometry of the transform, containing information about
            the operator range

        Other Parameters
        ----------------
        impl : {`None`, 'astra_cuda', 'astra_cpu', '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_range : `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.
            Default: True

        Notes
        -----
        The ASTRA backend is faster if data is given with ``dtype`` 'float32'
        and storage order 'C'. Otherwise copies will be needed.
        """
        if not isinstance(discr_domain, DiscreteLp):
            raise TypeError('`discr_domain` {!r} is not a `DiscreteLp`'
                            ' instance'.format(discr_domain))

        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` {!r} not supported'
                             ''.format(impl_in))

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

        # TODO: sanity checks between impl and discretization impl
        if impl.startswith('astra'):
            # TODO: these should be moved somewhere else
            if not ASTRA_AVAILABLE:
                raise ValueError("'astra' back-end not available")
            if impl == 'astra_cuda' and not ASTRA_CUDA_AVAILABLE:
                raise ValueError("'astra_cuda' back-end not available")
            if not np.allclose(discr_domain.partition.cell_sides[1:],
                               discr_domain.partition.cell_sides[:-1]):
                raise ValueError('ASTRA does not support different voxel '
                                 'sizes per axis, got {}'
                                 ''.format(discr_domain.partition.cell_sides))
            if geometry.ndim > 2 and impl.endswith('cpu'):
                raise ValueError('`impl` {}, only works for 2d geometries'
                                 ' got {}-d'.format(impl_in, geometry))
        elif impl == 'scikit':
            if not isinstance(geometry, Parallel2dGeometry):
                raise TypeError("'scikit' backend only supports 2d parallel "
                                'geometries')

            mid_pt = discr_domain.domain.mid_pt
            if not all(mid_pt == [0, 0]):
                raise ValueError('`discr_domain.domain` needs to be '
                                 'centered on [0, 0], got {}'.format(mid_pt))

            shape = discr_domain.shape
            if shape[0] != shape[1]:
                raise ValueError('`discr_domain.shape` needs to be square '
                                 'got {}'.format(shape))

            extent = discr_domain.domain.extent
            if extent[0] != extent[1]:
                raise ValueError('`discr_domain.extent` needs to be square '
                                 'got {}'.format(extent))

        # TODO: sanity checks between domain and geometry (ndim, ...)
        self.__geometry = geometry
        self.__impl = impl
        self.kwargs = kwargs

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

            # Create a discretized space (operator range) with the same
            # data-space type as the domain.
            # TODO: use a ProductSpace structure or find a way to treat
            # different dimensions differently in DiscreteLp
            # (i.e. in partitions).
            range_uspace = FunctionSpace(geometry.params,
                                         out_dtype=dtype)

            # Approximate cell volume
            # TODO: angles and detector must be handled separately. While the
            # detector should be uniformly discretized, the angles do not have
            # to and often are not.
            extent = float(geometry.partition.extent.prod())
            size = float(geometry.partition.size)
            weight = extent / size

            range_dspace = discr_domain.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

            range_interp = kwargs.get('interp', 'nearest')
            discr_range = DiscreteLp(
                range_uspace, geometry.partition, range_dspace,
                interp=range_interp, order=discr_domain.order,
                axis_labels=axis_labels)

        self.backproj = 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):
        """Forward 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_forward_projector(x, self.geometry,
                                                   self.range, out)
            elif data_impl == 'cuda':
                proj = getattr(self, 'astra_projector', None)
                if proj is None:
                    self.astra_projector = AstraCudaProjectorImpl(
                        self.geometry, self.domain, self.range,
                        use_cache=self.use_cache)

                return self.astra_projector.call_forward(x, out)
            else:
                # Should never happen
                raise RuntimeError('implementation info is inconsistent')
        elif self.impl == 'scikit':
            return scikit_radon_forward(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 : `RayBackProjection`
        """
        if self.backproj is not None:
            return self.backproj

        kwargs = self.kwargs.copy()
        kwargs['discr_domain'] = self.range
        self.backproj = RayBackProjection(self.domain, self.geometry,
                                          impl=self.impl,
                                          use_cache=self.use_cache,
                                          **kwargs)
        return self.backproj