Beispiel #1
0
            def _call(self, f, out=None):
                """Evaluate the gradient in the point ``f``."""
                if functional.impl != 'numpy':
                    raise NotImplementedError(
                        'gradient not implemented for `impl` {!r}'
                        ''.format(functional.impl))
                if isinstance(self.domain, ProductSpace):
                    tmp = self.domain[0].element()
                    pwnorm = PointwiseNorm(self.domain)
                    pwnorm(f, out=tmp)
                else:
                    tmp = out
                    f.ufunc.absolute(out=tmp)

                tmp.ufunc.power(self.exponent - 2, out=tmp)
                with writable_array(tmp) as tmp_arr:
                    tmp_arr[np.isnan(tmp_arr)] = 0  # Handle NaN

                if out is None:
                    out = f.copy()
                else:
                    out.assign(f)

                out *= self.exponent
                return out
 def _call(self, x, out):
     """Implement ``self(x, out)``."""
     with writable_array(out) as out_arr:
         resize_array(x.asarray(), op.domain.shape,
                      offset=op.offset, pad_mode=op.pad_mode,
                      pad_const=0, direction='adjoint',
                      out=out_arr)
Beispiel #3
0
 def _call(self, func, out=None, **kwargs):
     """Return ``self(func[, out, **kwargs])``."""
     mesh = self.grid.meshgrid
     if out is None:
         out = func(mesh, **kwargs)
     else:
         with writable_array(out) as out_arr:
             func(mesh, out=out_arr, **kwargs)
     return out
Beispiel #4
0
 def _call(self, x, out):
     """Implement ``self(x, out)``."""
     with writable_array(out) as out_arr:
         resize_array(x.asarray(),
                      self.range.shape,
                      offset=self.offset,
                      pad_mode=self.pad_mode,
                      pad_const=self.pad_const,
                      direction='forward',
                      out=out_arr)
Beispiel #5
0
 def _call(self, x, out=None):
     """Raw apply method on input, writing to given output."""
     if out is None:
         return self.range.element(self.matrix.dot(x))
     else:
         if self.matrix_issparse:
             # Unfortunately, there is no native in-place dot product for
             # sparse matrices
             out[:] = self.matrix.dot(x)
         else:
             with writable_array(out) as out_arr:
                 self.matrix.dot(x, out=out_arr)
Beispiel #6
0
 def _call(self, x, out=None):
     """Return ``self(x[, out])``."""
     if out is None:
         return self.range.element(self.matrix.dot(x))
     else:
         if self.matrix_issparse:
             # Unfortunately, there is no native in-place dot product for
             # sparse matrices
             out[:] = self.matrix.dot(x)
         else:
             with writable_array(out) as out_arr:
                 self.matrix.dot(x, out=out_arr)
Beispiel #7
0
    def _call(self, x, out):
        """Mask ``x`` and store the result in ``out`` if given."""
        # Find the indices of the mask min and max. The floating point
        # versions are also required for the linear transition.
        idx_min_flt = np.array(self.domain.partition.index(self.min_pt,
                                                           floating=True),
                               ndmin=1)
        idx_max_flt = np.array(self.domain.partition.index(self.max_pt,
                                                           floating=True),
                               ndmin=1)

        # To deal with coinciding boundaries we introduce an epsilon tolerance
        epsilon = 1e-6
        idx_min = np.floor(idx_min_flt - epsilon).astype(int)
        idx_max = np.ceil(idx_max_flt + epsilon).astype(int)

        def coeffs(d):
            return (1.0 - (idx_min_flt[d] - idx_min[d]),
                    1.0 - (idx_max[d] - idx_max_flt[d]))

        # Need an extra level of indirection in order to capture `d` inside
        # the lambda expressions
        def fn_pair(d):
            return (lambda x: x * coeffs(d)[0], lambda x: x * coeffs(d)[1])

        boundary_scale_fns = [fn_pair(d) for d in range(x.ndim)]

        slc = tuple(slice(imin, imax) for imin, imax in zip(idx_min, idx_max))
        slc_inner = tuple(
            slice(imin + 1, imax - 1) for imin, imax in zip(idx_min, idx_max))

        # Make a mask that is 1 outside the masking region, 0 inside
        # and has a linear transition where the region boundary does not
        # coincide with a cell boundary
        mask = np.ones_like(x)
        mask[slc_inner] = 0
        apply_on_boundary(mask[slc],
                          boundary_scale_fns,
                          only_once=False,
                          out=mask[slc])
        apply_on_boundary(mask[slc],
                          lambda x: 1.0 - x,
                          only_once=True,
                          out=mask[slc])

        # out = masked version of x
        out.assign(x)
        with writable_array(out) as out_arr:
            out_arr[slc] = mask[slc] * out_arr[slc]
Beispiel #8
0
def skimage_radon_forward_projector(volume, geometry, proj_space, out=None):
    """Calculate forward projection using skimage.

    Parameters
    ----------
    volume : `DiscreteLpElement`
        The volume to project.
    geometry : `Geometry`
        The projection geometry to use.
    proj_space : `DiscreteLp`
        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 _call(self, x, out=None):
        """Apply resampling operator.

        The element ``x`` is resampled using the sampling and interpolation
        operators of the underlying spaces.
        """
        interpolator = per_axis_interpolator(
            x, self.domain.grid.coord_vectors, self.interp
        )

        out_ctx = nullcontext() if out is None else writable_array(out)
        with out_ctx as out_arr:
            return point_collocation(
                interpolator, self.range.meshgrid, out=out_arr
            )
Beispiel #10
0
    def _call(self, x, out=None):
        """Calculate partial derivative of ``x``."""
        if out is None:
            out = self.range.element()

        # TODO: this pipes CUDA arrays through NumPy. Write native operator.
        with writable_array(out) as out_arr:
            finite_diff(x.asarray(),
                        axis=self.axis,
                        dx=self.dx,
                        method=self.method,
                        pad_mode=self.pad_mode,
                        pad_const=self.pad_const,
                        out=out_arr)
        return out
Beispiel #11
0
    def _call(self, x, out=None):
        """Calculate the spatial gradient of ``x``."""
        if out is None:
            out = self.range.element()

        x_arr = x.asarray()
        ndim = self.domain.ndim
        dx = self.domain.cell_sides

        for axis in range(ndim):
            with writable_array(out[axis]) as out_arr:
                finite_diff(x_arr,
                            axis=axis,
                            dx=dx[axis],
                            method=self.method,
                            pad_mode=self.pad_mode,
                            pad_const=self.pad_const,
                            out=out_arr)
        return out
Beispiel #12
0
    def _call(self, x, out=None):
        """Calculate the spatial Laplacian of ``x``."""
        if out is None:
            out = self.range.zero()
        else:
            out.set_zero()

        x_arr = x.asarray()
        out_arr = out.asarray()
        tmp = np.empty(out.shape, out.dtype, order=out.space.default_order)

        ndim = self.domain.ndim
        dx = self.domain.cell_sides

        with writable_array(out) as out_arr:
            for axis in range(ndim):
                # TODO: this can be optimized
                finite_diff(x_arr,
                            axis=axis,
                            dx=dx[axis]**2,
                            method='forward',
                            pad_mode=self.pad_mode,
                            pad_const=self.pad_const,
                            out=tmp)

                out_arr += tmp

                finite_diff(x_arr,
                            axis=axis,
                            dx=dx[axis]**2,
                            method='backward',
                            pad_mode=self.pad_mode,
                            pad_const=self.pad_const,
                            out=tmp)

                out_arr -= tmp

        return out
Beispiel #13
0
    def _call(self, x, out=None):
        """Calculate the divergence of ``x``."""
        if out is None:
            out = self.range.element()

        ndim = self.range.ndim
        dx = self.range.cell_sides

        tmp = np.empty(out.shape, out.dtype, order=out.space.default_order)
        with writable_array(out) as out_arr:
            for axis in range(ndim):
                finite_diff(x[axis],
                            axis=axis,
                            dx=dx[axis],
                            method=self.method,
                            pad_mode=self.pad_mode,
                            pad_const=self.pad_const,
                            out=tmp)
                if axis == 0:
                    out_arr[:] = tmp
                else:
                    out_arr += tmp

        return out
Beispiel #14
0
def astra_cpu_forward_projector(vol_data, geometry, proj_space, out=None):
    """Run an ASTRA forward projection on the given data using the CPU.

    Parameters
    ----------
    vol_data : `DiscreteLpElement`
        Volume data to which the forward 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 vol_data.space.impl != 'numpy':
        raise TypeError('dspace {!r} of the volume is not an '
                        'instance of `NumpyNtuples`'
                        ''.format(vol_data.space.dspace))
    if not isinstance(geometry, Geometry):
        raise TypeError('geometry  {!r} is not a Geometry instance'
                        ''.format(geometry))
    if not isinstance(proj_space, DiscreteLp):
        raise TypeError('projection space {!r} is not a DiscreteLp '
                        'instance.'.format(proj_space))
    if proj_space.impl != 'numpy':
        raise TypeError('data type {!r} of the reconstruction space is not an '
                        'instance of NumpyNtuples'.format(proj_space.dspace))
    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 out is None:
        out = proj_space.element()
    else:
        if out not in proj_space:
            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 projector
    if not all(s == vol_data.space.interp_byaxis[0]
               for s in vol_data.space.interp_byaxis):
        raise ValueError('volume interpolation must be the same in each '
                         'dimension, got {}'.format(vol_data.space.interp))
    vol_interp = vol_data.space.interp
    proj_id = astra_projector(vol_interp,
                              vol_geom,
                              proj_geom,
                              ndim,
                              impl='cpu')

    # Create ASTRA data structures
    vol_id = astra_data(vol_geom,
                        datatype='volume',
                        data=vol_data,
                        allow_copy=True)

    with writable_array(out, dtype='float32', order='C') as arr:
        sino_id = astra_data(proj_geom,
                             datatype='projection',
                             data=arr,
                             ndim=proj_space.ndim)

        # Create algorithm
        algo_id = astra_algorithm('forward',
                                  ndim,
                                  vol_id,
                                  sino_id,
                                  proj_id,
                                  impl='cpu')

        # Run algorithm
        astra.algorithm.run(algo_id)

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
Beispiel #15
0
def astra_cpu_back_projector(proj_data, geometry, reco_space, out=None):
    """Run an ASTRA back-projection on the given data using the CPU.

    Parameters
    ----------
    proj_data : `DiscreteLpElement`
        Projection data to which the back-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 proj_data.space.impl != 'numpy':
        raise TypeError('data type {!r} of the projection space is not an '
                        'instance of NumpyNtuples'
                        ''.format(proj_data.shape.dspace))
    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.impl != 'numpy':
        raise TypeError('data type {!r} of the reconstruction space is not an '
                        'instance of NumpyNtuples'.format(reco_space.dspace))
    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 None:
        out = reco_space.element()
    else:
        if out not in reco_space:
            raise TypeError('`out` {} is neither None nor a '
                            'DiscreteLpElement instance'.format(out))

    ndim = proj_data.ndim

    # Create astra geometries
    vol_geom = astra_volume_geometry(reco_space)
    proj_geom = astra_projection_geometry(geometry)

    # Create ASTRA data structure
    sino_id = astra_data(proj_geom,
                         datatype='projection',
                         data=proj_data,
                         allow_copy=True)

    # Create projector
    # TODO: implement with different schemes for angles and detector
    if not all(s == proj_data.space.interp_byaxis[0]
               for s in proj_data.space.interp_byaxis):
        raise ValueError('data interpolation must be the same in each '
                         'dimension, got {}'
                         ''.format(proj_data.space.interp_byaxis))
    proj_interp = proj_data.space.interp
    proj_id = astra_projector(proj_interp,
                              vol_geom,
                              proj_geom,
                              ndim,
                              impl='cpu')

    # convert out to correct dtype and order 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,
                                  impl='cpu')

        # Run algorithm and delete it
        astra.algorithm.run(algo_id)

    # Weight the adjoint by appropriate weights
    scaling_factor = float(proj_data.space.weighting.const)
    scaling_factor /= float(reco_space.weighting.const)

    out *= scaling_factor

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
Beispiel #16
0
def astra_cpu_back_projector(proj_data,
                             geometry,
                             vol_space,
                             out=None,
                             astra_proj_type=None):
    """Run an ASTRA back-projection on the given data using the CPU.

    Parameters
    ----------
    proj_data : `DiscreteLpElement`
        Projection data to which the back-projector is applied.
    geometry : `Geometry`
        Geometry defining the tomographic setup.
    vol_space : `DiscreteLp`
        Space to which the calling operator maps.
    out : ``vol_space`` element, optional
        Element of the reconstruction space to which the result is written.
        If ``None``, an element in ``vol_space`` is created.
    astra_proj_type : str, optional
        Type of projector that should be used. See `the ASTRA documentation
        <http://www.astra-toolbox.com/docs/proj2d.html>`_ for details.
        By default, a suitable projector type for the given geometry is
        selected, see `default_astra_proj_type`.

    Returns
    -------
    out : ``vol_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 proj_data.space.impl != 'numpy':
        raise TypeError('`proj_data` must be a `numpy.ndarray` based, '
                        "container got `impl` {!r}"
                        "".format(proj_data.space.impl))
    if not isinstance(geometry, Geometry):
        raise TypeError('geometry  {!r} is not a Geometry instance'
                        ''.format(geometry))
    if not isinstance(vol_space, DiscreteLp):
        raise TypeError('volume space {!r} is not a DiscreteLp '
                        'instance'.format(vol_space))
    if vol_space.impl != 'numpy':
        raise TypeError("`vol_space.impl` must be 'numpy', got {!r}"
                        "".format(vol_space.impl))
    if vol_space.ndim != geometry.ndim:
        raise ValueError('dimensions {} of reconstruction space and {} of '
                         'geometry do not match'.format(
                             vol_space.ndim, geometry.ndim))
    if out is None:
        out = vol_space.element()
    else:
        if out not in vol_space:
            raise TypeError('`out` {} is neither None nor a '
                            'DiscreteLpElement instance'.format(out))

    ndim = proj_data.ndim

    # Create astra geometries
    vol_geom = astra_volume_geometry(vol_space)
    proj_geom = astra_projection_geometry(geometry)

    # Create ASTRA data structure
    sino_id = astra_data(proj_geom,
                         datatype='projection',
                         data=proj_data,
                         allow_copy=True)

    # Create projector
    if astra_proj_type is None:
        astra_proj_type = default_astra_proj_type(geometry)
    proj_id = astra_projector(astra_proj_type, vol_geom, proj_geom, ndim)

    # Convert out to correct dtype and order if needed.
    with writable_array(out, dtype='float32', order='C') as out_arr:
        vol_id = astra_data(vol_geom,
                            datatype='volume',
                            data=out_arr,
                            ndim=vol_space.ndim)
        # Create algorithm
        algo_id = astra_algorithm('backward',
                                  ndim,
                                  vol_id,
                                  sino_id,
                                  proj_id,
                                  impl='cpu')

        # Run algorithm
        astra.algorithm.run(algo_id)

    # Weight the adjoint by appropriate weights
    scaling_factor = float(proj_data.space.weighting.const)
    scaling_factor /= float(vol_space.weighting.const)

    out *= scaling_factor

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
def astra_cpu_forward_projector(vol_data, geometry, proj_space, out=None,
                                astra_proj_type=None):
    """Run an ASTRA forward projection on the given data using the CPU.

    Parameters
    ----------
    vol_data : `DiscretizedSpaceElement`
        Volume data to which the forward projector is applied.
    geometry : `Geometry`
        Geometry defining the tomographic setup.
    proj_space : `DiscretizedSpace`
        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.
    astra_proj_type : str, optional
        Type of projector that should be used. See `the ASTRA documentation
        <http://www.astra-toolbox.com/docs/proj2d.html>`_ for details.
        By default, a suitable projector type for the given geometry is
        selected, see `default_astra_proj_type`.

    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, DiscretizedSpaceElement):
        raise TypeError(
            'volume data {!r} is not a `DiscretizedSpaceElement` instance'
            ''.format(vol_data)
        )
    if vol_data.space.impl != 'numpy':
        raise TypeError(
            "`vol_data.space.impl` must be 'numpy', got {!r}"
            "".format(vol_data.space.impl)
        )
    if not isinstance(geometry, Geometry):
        raise TypeError(
            'geometry {!r} is not a Geometry instance'.format(geometry)
        )
    if not isinstance(proj_space, DiscretizedSpace):
        raise TypeError(
            '`proj_space` {!r} is not a DiscretizedSpace instance.'
            ''.format(proj_space)
        )
    if proj_space.impl != 'numpy':
        raise TypeError(
            "`proj_space.impl` must be 'numpy', got {!r}"
            "".format(proj_space.impl)
        )
    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 out is None:
        out = proj_space.element()
    else:
        if out not in proj_space:
            raise TypeError(
                '`out` {} is neither None nor a `DiscretizedSpaceElement` '
                '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 projector
    if astra_proj_type is None:
        astra_proj_type = default_astra_proj_type(geometry)
    proj_id = astra_projector(astra_proj_type, vol_geom, proj_geom, ndim)

    # Create ASTRA data structures
    vol_data_arr = np.asarray(vol_data)
    vol_id = astra_data(vol_geom, datatype='volume', data=vol_data_arr,
                        allow_copy=True)

    with writable_array(out, dtype='float32', order='C') as out_arr:
        sino_id = astra_data(proj_geom, datatype='projection', data=out_arr,
                             ndim=proj_space.ndim)

        # Create algorithm
        algo_id = astra_algorithm('forward', ndim, vol_id, sino_id, proj_id,
                                  impl='cpu')

        # Run algorithm
        astra.algorithm.run(algo_id)

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
Beispiel #18
0
def _varlp_prox_call(f, out, exponent, sigma, impl, num_newton_iter, conj):
    """Implementation of the proximals of the modular and its conjugate.

    Parameters
    ----------
    f : domain element
        Element at which to evaluate the operator.
    out : range element
        Element to which the result is written.
    exponent : base space element
        Variable exponent used in the modular.
    sigma : positive float
        Scaling parameter in the proximal operator.
    impl : string
        Implementation back-end for the proximal operator.
    num_newton_iter : int, optional
        Number of Newton iterations.
    conj : bool
        Apply the proximal of the convex conjugate if ``True``, otherwise
        the proximal of the variable Lp modular.
    """
    num_newton_iter = int(num_newton_iter)
    sigma = float(sigma)

    # Compute the magnitude of the input function
    if isinstance(f.space, ProductSpace):
        pwnorm = PointwiseNorm(f.space)
        prox_fac = pwnorm(f)
    else:
        prox_fac = out
        f.ufuncs.absolute(out=prox_fac)

    if conj:
        prox_npy = _numpy_impl.varlp_cc_prox_factor_npy
        prox_numba = _numba_impl.varlp_cc_prox_factor_numba
        prox_cython = _cython_impl.varlp_cc_prox_factor_cython
        prox_gpuary = _gpuarray_impl.varlp_cc_prox_factor_gpuary
    else:
        prox_npy = _numpy_impl.varlp_prox_factor_npy
        prox_numba = _numba_impl.varlp_prox_factor_numba
        prox_cython = _cython_impl.varlp_prox_factor_cython
        prox_gpuary = _gpuarray_impl.varlp_prox_factor_gpuary

    # Compute the multiplicative factor
    if impl == 'numpy':
        with writable_array(prox_fac) as out_arr:
            prox_npy(prox_fac.asarray(),
                     exponent.asarray(),
                     sigma,
                     num_newton_iter,
                     out=out_arr)

    elif impl.startswith('numba'):
        _, target = impl.split('_')
        prox_fac[:] = prox_numba(prox_fac.asarray(),
                                 exponent.asarray(),
                                 sigma,
                                 num_newton_iter,
                                 target=target)

    elif impl == 'cython':
        if not _cython_impl.CYTHON_EXTENSION_BUILT:
            raise RuntimeError(
                'Cython extension has not been built. '
                'Run `python setup.py build_ext` to build the extension.'
                'For development installations of this package '
                '(`pip install -e`), add the `--inplace` option to the '
                'build command.')
        with writable_array(prox_fac) as out_arr:
            prox_cython(prox_fac.asarray(),
                        exponent.asarray(),
                        sigma,
                        num_newton_iter,
                        out=out_arr)

    elif impl == 'gpuarray':
        if not _gpuarray_impl.PYGPU_AVAILABLE:
            raise RuntimeError(
                '`pygpu` package not installed. You need Anaconda '
                '(or Miniconda) to install it via `conda install pygpu`. '
                'Alternatively, you can build it from source. See '
                'https://github.com/Theano/libgpuarray for more information.')

        # TODO: context manager for GPU arrays
        if isinstance(f.space, ProductSpace):
            dom_impl = f.space[0].impl
        else:
            dom_impl = f.space.impl

        if dom_impl == 'gpuarray':
            out_arr = prox_fac.tensor.data
            prox_fac_arr = prox_fac.tensor.data
        else:
            out_arr = None
            prox_fac_arr = prox_fac.asarray()

        result = prox_gpuary(prox_fac_arr,
                             exponent.asarray(),
                             sigma,
                             num_newton_iter,
                             out=out_arr)

        if out_arr is None:
            prox_fac[:] = result

    else:
        raise RuntimeError('bad impl {!r}'.format(impl))

    # Perform the multiplication
    if isinstance(f.space, ProductSpace):
        # out is empty
        out.assign(f)
        out *= prox_fac
    else:
        # out already stores the factor
        out *= f

    return f
Beispiel #19
0
    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        """Interface to Numpy's ufunc machinery.

        This method is called by Numpy version 1.13 and higher as a single
        point for the ufunc dispatch logic. An object implementing
        ``__array_ufunc__`` takes over control when a `numpy.ufunc` is
        called on it, allowing it to use custom implementations and
        output types.

        This includes handling of in-place arithmetic like
        ``npy_array += custom_obj``. In this case, the custom object's
        ``__array_ufunc__`` takes precedence over the baseline
        `numpy.ndarray` implementation. It will be called with
        ``npy_array`` as ``out`` argument, which ensures that the
        returned object is a Numpy array. For this to work properly,
        ``__array_ufunc__`` has to accept Numpy arrays as ``out`` arguments.

        See the `corresponding NEP`_ and the `interface documentation`_
        for further details. See also the `general documentation on
        Numpy ufuncs`_.

        .. note::
            This basic implementation casts inputs and
            outputs to Numpy arrays and evaluates ``ufunc`` on those.
            For `numpy.ndarray` based data storage, this incurs no
            significant overhead compared to direct usage of Numpy arrays.

            For other (in particular non-local) implementations, e.g.,
            GPU arrays or distributed memory, overhead is significant due
            to copies to CPU main memory. In those classes, the
            ``__array_ufunc__`` mechanism should be overridden in favor of
            a native implementations if possible.

        .. note::
            If no ``out`` parameter is provided, this implementation
            just returns the raw array and does not attempt to wrap the
            result in any kind of space.

        Parameters
        ----------
        ufunc : `numpy.ufunc`
            Ufunc that should be called on ``self``.
        method : str
            Method on ``ufunc`` that should be called on ``self``.
            Possible values:

            ``'__call__'``, ``'accumulate'``, ``'at'``, ``'outer'``,
            ``'reduce'``, ``'reduceat'``

        input1, ..., inputN:
            Positional arguments to ``ufunc.method``.
        kwargs:
            Keyword arguments to ``ufunc.method``.

        Returns
        -------
        ufunc_result : `Tensor`, `numpy.ndarray` or tuple
            Result of the ufunc evaluation. If no ``out`` keyword argument
            was given, the result is a `Tensor` or a tuple
            of such, depending on the number of outputs of ``ufunc``.
            If ``out`` was provided, the returned object or tuple entries
            refer(s) to ``out``.

        References
        ----------
        .. _corresponding NEP:
           https://docs.scipy.org/doc/numpy/neps/ufunc-overrides.html

        .. _interface documentation:
           https://docs.scipy.org/doc/numpy/reference/arrays.classes.html\
#numpy.class.__array_ufunc__

        .. _general documentation on Numpy ufuncs:
           https://docs.scipy.org/doc/numpy/reference/ufuncs.html

        .. _reduceat documentation:
           https://docs.scipy.org/doc/numpy/reference/generated/\
numpy.ufunc.reduceat.html
        """
        # --- Process `out` --- #

        # Unwrap out if provided. The output parameters are all wrapped
        # in one tuple, even if there is only one.
        out_tuple = kwargs.pop('out', ())

        # Check number of `out` args, depending on `method`
        if method == '__call__' and len(out_tuple) not in (0, ufunc.nout):
            raise ValueError("ufunc {}: need 0 or {} `out` arguments for "
                             "`method='__call__'`, got {}"
                             ''.format(ufunc.__name__, ufunc.nout,
                                       len(out_tuple)))
        elif method != '__call__' and len(out_tuple) not in (0, 1):
            raise ValueError(
                'ufunc {}: need 0 or 1 `out` arguments for `method={!r}`, '
                'got {}'.format(ufunc.__name__, method, len(out_tuple)))

        # We allow our own tensors, the data container type and
        # `numpy.ndarray` objects as `out` (see docs for reason for the
        # latter)
        valid_types = (type(self), type(self.data), np.ndarray)
        if not all(isinstance(o, valid_types) or o is None for o in out_tuple):
            return NotImplemented

        # Assign to `out` or `out1` and `out2`, respectively
        out = out1 = out2 = None
        if len(out_tuple) == 1:
            out = out_tuple[0]
        elif len(out_tuple) == 2:
            out1 = out_tuple[0]
            out2 = out_tuple[1]

        # --- Process `inputs` --- #

        # Convert inputs that are ODL tensors or their data containers to
        # Numpy arrays so that the native Numpy ufunc is called later
        inputs = tuple(
            np.asarray(inp) if isinstance(inp, (type(self),
                                                type(self.data))) else inp
            for inp in inputs)

        # --- Get some parameters for later --- #

        # Arguments for `writable_array` and/or space constructors
        out_dtype = kwargs.get('dtype', None)
        if out_dtype is None:
            array_kwargs = {}
        else:
            array_kwargs = {'dtype': out_dtype}

        # --- Evaluate ufunc --- #

        if method == '__call__':
            if ufunc.nout == 1:
                # Make context for output (trivial one returns `None`)
                if out is None:
                    out_ctx = nullcontext()
                else:
                    out_ctx = writable_array(out, **array_kwargs)

                # Evaluate ufunc
                with out_ctx as out_arr:
                    kwargs['out'] = out_arr
                    res = ufunc(*inputs, **kwargs)

                # Return result (may be a raw array or a space element)
                return res

            elif ufunc.nout == 2:
                # Make contexts for outputs (trivial ones return `None`)
                if out1 is not None:
                    out1_ctx = writable_array(out1, **array_kwargs)
                else:
                    out1_ctx = nullcontext()
                if out2 is not None:
                    out2_ctx = writable_array(out2, **array_kwargs)
                else:
                    out2_ctx = nullcontext()

                # Evaluate ufunc
                with out1_ctx as out1_arr, out2_ctx as out2_arr:
                    kwargs['out'] = (out1_arr, out2_arr)
                    res1, res2 = ufunc(*inputs, **kwargs)

                # Return results (may be raw arrays or space elements)
                return res1, res2

            else:
                raise NotImplementedError('nout = {} not supported'
                                          ''.format(ufunc.nout))

        else:  # method != '__call__'
            # Make context for output (trivial one returns `None`)
            if out is None:
                out_ctx = nullcontext()
            else:
                out_ctx = writable_array(out, **array_kwargs)

            # Evaluate ufunc method
            if method == 'at':
                with writable_array(inputs[0]) as inp_arr:
                    res = ufunc.at(inp_arr, *inputs[1:], **kwargs)
            else:
                with out_ctx as out_arr:
                    kwargs['out'] = out_arr
                    res = getattr(ufunc, method)(*inputs, **kwargs)

            # Return result (may be scalar, raw array or space element)
            return res
Beispiel #20
0
def astra_cpu_back_projector(proj_data, geometry, reco_space, out=None):
    """Run an ASTRA back-projection on the given data using the CPU.

    Parameters
    ----------
    proj_data : `DiscreteLpElement`
        Projection data to which the back-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 proj_data.space.impl != 'numpy':
        raise TypeError('data type {!r} of the projection space is not an '
                        'instance of NumpyNtuples'
                        ''.format(proj_data.shape.dspace))
    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.impl != 'numpy':
        raise TypeError('data type {!r} of the reconstruction space is not an '
                        'instance of NumpyNtuples'.format(reco_space.dspace))
    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 None:
        out = reco_space.element()
    else:
        if out not in reco_space:
            raise TypeError('`out` {} is neither None nor a '
                            'DiscreteLpElement instance'.format(out))

    ndim = proj_data.ndim

    # Create astra geometries
    vol_geom = astra_volume_geometry(reco_space)
    proj_geom = astra_projection_geometry(geometry)

    # Create ASTRA data structure
    sino_id = astra_data(proj_geom, datatype='projection', data=proj_data,
                         allow_copy=True)

    # Create projector
    # TODO: implement with different schemes for angles and detector
    if not all(s == proj_data.space.interp_by_axis[0]
               for s in proj_data.space.interp_by_axis):
        raise ValueError('data interpolation must be the same in each '
                         'dimension, got {}'
                         ''.format(proj_data.space.interp_by_axis))
    proj_interp = proj_data.space.interp
    proj_id = astra_projector(proj_interp, vol_geom, proj_geom, ndim,
                              impl='cpu')

    # convert out to correct dtype and order 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,
                                  impl='cpu')

        # Run algorithm and delete it
        astra.algorithm.run(algo_id)

    # Angular integration weighting factor
    # angle interval weight by approximate cell volume
    extent = float(geometry.motion_partition.extent)
    size = float(geometry.motion_partition.size)
    scaling_factor = extent / size

    # Fix inconsistent scaling: parallel2d & fanflat scale with (voxel
    # stride)**2 / (pixel stride), currently only square voxels are supported
    scaling_factor *= float(geometry.det_partition.cell_sides[0])
    scaling_factor /= float(reco_space.partition.cell_sides[0]) ** 2

    out *= scaling_factor

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
Beispiel #21
0
def skimage_radon_back_projector(sinogram, geometry, vol_space, out=None):
    """Calculate forward projection using skimage.

    Parameters
    ----------
    sinogram : `DiscreteLpElement`
        Sinogram (projections) to backproject.
    geometry : `Geometry`
        The projection geometry to use.
    vol_space : `DiscreteLp`
        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
Beispiel #22
0
def astra_cpu_forward_projector(vol_data, geometry, proj_space, out=None):
    """Run an ASTRA forward projection on the given data using the CPU.

    Parameters
    ----------
    vol_data : `DiscreteLpElement`
        Volume data to which the forward 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 vol_data.space.impl != 'numpy':
        raise TypeError('dspace {!r} of the volume is not an '
                        'instance of `NumpyNtuples`'
                        ''.format(vol_data.space.dspace))
    if not isinstance(geometry, Geometry):
        raise TypeError('geometry  {!r} is not a Geometry instance'
                        ''.format(geometry))
    if not isinstance(proj_space, DiscreteLp):
        raise TypeError('projection space {!r} is not a DiscreteLp '
                        'instance.'.format(proj_space))
    if proj_space.impl != 'numpy':
        raise TypeError('data type {!r} of the reconstruction space is not an '
                        'instance of NumpyNtuples'.format(proj_space.dspace))
    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 out is None:
        out = proj_space.element()
    else:
        if out not in proj_space:
            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 projector
    if not all(s == vol_data.space.interp_by_axis[0]
               for s in vol_data.space.interp_by_axis):
        raise ValueError('volume interpolation must be the same in each '
                         'dimension, got {}'.format(vol_data.space.interp))
    vol_interp = vol_data.space.interp
    proj_id = astra_projector(vol_interp, vol_geom, proj_geom, ndim,
                              impl='cpu')

    # Create ASTRA data structures
    vol_id = astra_data(vol_geom, datatype='volume', data=vol_data,
                        allow_copy=True)

    with writable_array(out, dtype='float32', order='C') as arr:
        sino_id = astra_data(proj_geom, datatype='projection', data=arr,
                             ndim=proj_space.ndim)

        # Create algorithm
        algo_id = astra_algorithm('forward', ndim, vol_id, sino_id, proj_id,
                                  impl='cpu')

        # Run algorithm
        astra.algorithm.run(algo_id)

    # Delete ASTRA objects
    astra.algorithm.delete(algo_id)
    astra.data2d.delete((vol_id, sino_id))
    astra.projector.delete(proj_id)

    return out
Beispiel #23
0
    def _call(self, f):
        """Return ``self(f)``."""
        # Compute the magnitude of the input function
        if isinstance(self.domain, ProductSpace):
            pwnorm = PointwiseNorm(self.domain)
            integrand = pwnorm(f)
        else:
            integrand = f.ufuncs.absolute()

        # Compute the integrand
        if self.impl == 'numpy':
            with writable_array(integrand) as out_arr:
                _numpy_impl.varlp_cc_integrand_npy(integrand.asarray(),
                                                   self.exponent.asarray(),
                                                   out=out_arr)

        elif self.impl.startswith('numba'):
            _, target = self.impl.split('_')
            integrand[:] = _numba_impl.varlp_cc_integrand_numba(
                integrand.asarray(), self.exponent.asarray(), target=target)

        elif self.impl == 'cython':
            if not _cython_impl.CYTHON_EXTENSION_BUILT:
                raise RuntimeError(
                    'Cython extension has not been built. '
                    'Run `python setup.py build_ext` to build the extension.'
                    'For development installations of this package '
                    '(`pip install -e`), add the `--inplace` option to the '
                    'build command.')

            with writable_array(integrand) as out_arr:
                _cython_impl.varlp_cc_integrand_cython(integrand.asarray(),
                                                       self.exponent.asarray(),
                                                       out=out_arr)

        elif self.impl == 'gpuarray':
            if not _gpuarray_impl.PYGPU_AVAILABLE:
                raise RuntimeError(
                    '`pygpu` package not installed. You need Anaconda '
                    '(or Miniconda) to install it via `conda install pygpu`. '
                    'Alternatively, you can build it from source. See '
                    'https://github.com/Theano/libgpuarray for more '
                    'information.')

            # TODO: context manager for GPU arrays
            if isinstance(self.domain, ProductSpace):
                dom_impl = self.domain[0].impl
            else:
                dom_impl = self.domain.impl

            if dom_impl == 'gpuarray':
                out_arr = integrand.data
                integrand_arr = integrand.data
            else:
                out_arr = None
                integrand_arr = integrand.asarray()

            result = _gpuarray_impl.varlp_cc_integrand_gpuary(
                integrand_arr, self.exponent.asarray(), out=out_arr)

            if out_arr is None:
                integrand[:] = result

        else:
            raise RuntimeError('bad impl {!r}'.format(self.impl))

        # Integrate
        if isinstance(self.domain, ProductSpace):
            return integrand.inner(self.domain[0].one())
        else:
            return integrand.inner(self.domain.one())