def __init__(self, partition, impl='numpy', **kwargs):
        c = context()
        dtype = c.fType
        fspace = FunctionSpace(partition.set, out_dtype=dtype)
        ds_type = tspace_type(fspace, impl, dtype)
        dtype = ds_type.default_dtype() if dtype is None else dtype 

        exponent = kwargs.pop('exponent', 2.0)
        tspace = ds_type(partition.shape, dtype, exponent=exponent,
                         weighting=1)
        DiscreteLp.__init__(self, fspace, partition, tspace, **kwargs)
        self.coord_vectors = tuple(c.cast(g) for g in self.grid.coord_vectors)
    def generate_projectspace(self, geometry):
        dtype = self.imagespace.dtype
        proj_fspace = FunctionSpace(geometry.params, out_dtype=dtype)

        if not self.imagespace.is_weighted:
            weighting = None
        elif (isinstance(self.imagespace.weighting, ConstWeighting)
              and np.isclose(self.imagespace.weighting.const,
                             self.imagespace.cell_volume)):
            extent = float(geometry.partition.extent.prod())
            size = float(geometry.partition.size)
            weighting = extent / size
        else:
            raise NotImplementedError('unknown weighting of domain')

        proj_tspace = self.imagespace.tspace_type(geometry.partition.shape,
                                                  weighting=weighting,
                                                  dtype=dtype)

        if geometry.motion_partition.ndim == 0:
            angle_labels = []
        if geometry.motion_partition.ndim == 1:
            angle_labels = ['$\\varphi$']
        elif geometry.motion_partition.ndim == 2:
            # TODO: check order
            angle_labels = ['$\\vartheta$', '$\\varphi$']
        elif geometry.motion_partition.ndim == 3:
            # TODO: check order
            angle_labels = ['$\\vartheta$', '$\\varphi$', '$\\psi$']
        else:
            angle_labels = None

        if geometry.det_partition.ndim == 1:
            det_labels = ['$s$']
        elif geometry.det_partition.ndim == 2:
            det_labels = ['$u$', '$v$']
        else:
            det_labels = None

        if angle_labels is None or det_labels is None:
            # Fallback for unknown configuration
            axis_labels = None
        else:
            axis_labels = angle_labels + det_labels

        proj_interp = 'nearest'
        #            proj_interp = kwargs.get('interp', 'nearest')
        return DiscreteLp(proj_fspace,
                          geometry.partition,
                          proj_tspace,
                          interp=proj_interp,
                          axis_labels=axis_labels)
Exemple #3
0
def _resize_discr(discr, newshp, offset, discr_kwargs):
    """Return a space based on ``discr`` and ``newshp``.

    Use the domain of ``discr`` and its partition to create a new
    uniformly discretized space with ``newshp`` as shape. In axes where
    ``offset`` is given, it determines the number of added/removed cells to
    the left. Where ``offset`` is ``None``, the points are distributed
    evenly to left and right. The ``discr_kwargs`` parameter is passed
    to `uniform_discr` for further specification of discretization
    parameters.
    """
    nodes_on_bdry = discr_kwargs.get('nodes_on_bdry', False)
    if np.shape(nodes_on_bdry) == ():
        nodes_on_bdry = ([(bool(nodes_on_bdry), bool(nodes_on_bdry))] *
                         discr.ndim)
    elif discr.ndim == 1 and len(nodes_on_bdry) == 2:
        nodes_on_bdry = [nodes_on_bdry]
    elif len(nodes_on_bdry) != discr.ndim:
        raise ValueError('`nodes_on_bdry` has length {}, expected {}'
                         ''.format(len(nodes_on_bdry), discr.ndim))

    dtype = discr_kwargs.pop('dtype', discr.dtype)
    impl = discr_kwargs.pop('impl', discr.impl)
    exponent = discr_kwargs.pop('exponent', discr.exponent)
    interp = discr_kwargs.pop('interp', discr.interp)
    weighting = discr_kwargs.pop('weighting', discr.weighting)

    affected = np.not_equal(newshp, discr.shape)
    ndim = discr.ndim
    for i in range(ndim):
        if affected[i] and not discr.is_uniform_byaxis[i]:
            raise ValueError('cannot resize in non-uniformly discretized '
                             'axis {}'.format(i))

    grid_min, grid_max = discr.grid.min(), discr.grid.max()
    cell_size = discr.cell_sides
    new_minpt, new_maxpt = [], []

    for axis, (n_orig, n_new, off, on_bdry) in enumerate(
            zip(discr.shape, newshp, offset, nodes_on_bdry)):

        if not affected[axis]:
            new_minpt.append(discr.min_pt[axis])
            new_maxpt.append(discr.max_pt[axis])
            continue

        n_diff = n_new - n_orig
        if off is None:
            num_r = n_diff // 2
            num_l = n_diff - num_r
        else:
            num_r = n_diff - off
            num_l = off

        try:
            on_bdry_l, on_bdry_r = on_bdry
        except TypeError:
            on_bdry_l = on_bdry
            on_bdry_r = on_bdry

        if on_bdry_l:
            new_minpt.append(grid_min[axis] - num_l * cell_size[axis])
        else:
            new_minpt.append(grid_min[axis] - (num_l + 0.5) * cell_size[axis])

        if on_bdry_r:
            new_maxpt.append(grid_max[axis] + num_r * cell_size[axis])
        else:
            new_maxpt.append(grid_max[axis] + (num_r + 0.5) * cell_size[axis])

    fspace = FunctionSpace(IntervalProd(new_minpt, new_maxpt), out_dtype=dtype)
    tspace = tensor_space(newshp,
                          dtype=dtype,
                          impl=impl,
                          exponent=exponent,
                          weighting=weighting)

    # Stack together the (unchanged) nonuniform axes and the (new) uniform
    # axes in the right order
    part = uniform_partition([], [], ())
    for i in range(ndim):
        if discr.is_uniform_byaxis[i]:
            part = part.append(
                uniform_partition(new_minpt[i],
                                  new_maxpt[i],
                                  newshp[i],
                                  nodes_on_bdry=nodes_on_bdry[i]))
        else:
            part = part.append(discr.partition.byaxis[i])

    return DiscreteLp(fspace, part, tspace, interp=interp)
Exemple #4
0
    def __init__(self, reco_space, geometry, variant, **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        reco_space : `DiscreteLp`
            Discretized reconstruction space, the domain of the forward
            operator or the range of the adjoint (back-projection).
        geometry : `Geometry`
            Geometry of the transform that contains information about
            the data structure.
        variant : {'forward', 'backward'}
            Variant of the transform, i.e., whether the ray transform
            or its back-projection should be created.

        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 projection
            space. This has no effect if ``proj_space`` is given explicitly.
            Default: ``'nearest'``
        proj_space : `DiscreteLp`, optional
            Discretized projection (sinogram) space, the range of the forward
            operator or the domain of the adjoint (back-projection).
            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 are given with
        ``dtype='float32'`` and storage order 'C'. Otherwise copies will be
        needed.
        """
        variant, variant_in = str(variant).lower(), variant
        if variant not in ('forward', 'backward'):
            raise ValueError('`variant` {!r} not understood'
                             ''.format(variant_in))

        if variant == 'forward':
            reco_name = 'domain'
            proj_name = 'range'
        else:
            reco_name = 'range'
            proj_name = 'domain'

        if not isinstance(reco_space, DiscreteLp):
            raise TypeError('`{}` must be a `DiscreteLp` instance, got '
                            '{!r}'.format(reco_name, reco_space))

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

        # Handle backend choice
        if not _AVAILABLE_IMPLS:
            raise RuntimeError('no ray transform back-end available; '
                               'this requires 3rd party packages, please '
                               'check the install docs')
        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 SKIMAGE_AVAILABLE:
                impl = 'skimage'
            else:
                raise RuntimeError('bad impl')
        else:
            impl, impl_in = str(impl).lower(), impl
            if impl not in _SUPPORTED_IMPL:
                raise ValueError('`impl` {!r} not understood'.format(impl_in))
            if impl not in _AVAILABLE_IMPLS:
                raise ValueError('{!r} back-end not available'.format(impl))

        # Cache for input/output arrays of transforms
        self.use_cache = kwargs.pop('use_cache', True)

        # Sanity checks
        if impl.startswith('astra'):
            if geometry.ndim > 2 and impl.endswith('cpu'):
                raise ValueError('`impl` {!r} only works for 2d'
                                 ''.format(impl_in))

            # Print a warning if the detector midpoint normal vector at any
            # angle is perpendicular to the geometry axis in parallel 3d
            # single-axis geometry -- this is broken in some ASTRA versions
            if (isinstance(geometry, Parallel3dAxisGeometry)
                    and not astra_supports('par3d_det_mid_pt_perp_to_axis')):
                axis = geometry.axis
                mid_pt = geometry.det_params.mid_pt
                for i, angle in enumerate(geometry.angles):
                    if abs(np.dot(axis, geometry.det_to_src(angle,
                                                            mid_pt))) < 1e-4:
                        warnings.warn(
                            'angle {}: detector midpoint normal {} is '
                            'perpendicular to the geometry axis {} in '
                            '`Parallel3dAxisGeometry`; this is broken in '
                            'ASTRA v{}, please upgrade to v1.8 or later'
                            ''.format(i, geometry.det_to_src(angle, mid_pt),
                                      axis, ASTRA_VERSION), RuntimeWarning)
                        break

        elif impl == 'skimage':
            if not isinstance(geometry, Parallel2dGeometry):
                raise TypeError("{!r} backend only supports 2d parallel "
                                'geometries'.format(impl))

            mid_pt = reco_space.domain.mid_pt
            if not np.allclose(mid_pt, [0, 0]):
                raise ValueError('`{}` must be centered at (0, 0), got '
                                 'midpoint {}'.format(reco_name, mid_pt))

            shape = reco_space.shape
            if shape[0] != shape[1]:
                raise ValueError('`{}.shape` must have equal entries, '
                                 'got {}'.format(reco_name, shape))

            extent = reco_space.domain.extent
            if extent[0] != extent[1]:
                raise ValueError('`{}.extent` must have equal entries, '
                                 'got {}'.format(reco_name, extent))

        if reco_space.ndim != geometry.ndim:
            raise ValueError('`{}.ndim` not equal to `geometry.ndim`: '
                             '{} != {}'.format(reco_name, reco_space.ndim,
                                               geometry.ndim))

        self.__geometry = geometry
        self.__impl = impl

        # Generate or check projection space
        proj_space = kwargs.pop('proj_space', None)
        if proj_space is None:
            dtype = reco_space.dtype
            proj_uspace = FunctionSpace(geometry.params, out_dtype=dtype)

            if isinstance(reco_space.weighting, NoWeighting):
                weighting = 1.0
            elif (isinstance(reco_space.weighting, ConstWeighting)
                  and np.isclose(reco_space.weighting.const,
                                 reco_space.cell_volume)):
                # Approximate cell volume
                # TODO: find a way to treat angles and detector differently
                # regarding weighting. While the detector should be uniformly
                # discretized, the angles do not have to and often are not.
                # The needed partition property is available since
                # commit a551190d, but weighting is not adapted yet.
                # See also issue #286
                extent = float(geometry.partition.extent.prod())
                size = float(geometry.partition.size)
                weighting = extent / size
            else:
                raise NotImplementedError('unknown weighting of domain')

            proj_dspace = reco_space.dspace_type(geometry.partition.size,
                                                 weighting=weighting,
                                                 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

            proj_interp = kwargs.get('interp', 'nearest')
            proj_space = DiscreteLp(proj_uspace,
                                    geometry.partition,
                                    proj_dspace,
                                    interp=proj_interp,
                                    order=reco_space.order,
                                    axis_labels=axis_labels)

        else:
            # proj_space was given, checking some stuff
            if not isinstance(proj_space, DiscreteLp):
                raise TypeError('`{}` must be a `DiscreteLp` instance, '
                                'got {!r}'.format(proj_name, proj_space))
            if proj_space.shape != geometry.partition.shape:
                raise ValueError('`{}.shape` not equal to `geometry.shape`: '
                                 '{} != {}'.format(proj_name, proj_space.shape,
                                                   geometry.partition.shape))
            if proj_space.dtype != reco_space.dtype:
                raise ValueError('`{}.dtype` not equal to `{}.dtype`: '
                                 '{} != {}'.format(proj_name, reco_name,
                                                   proj_space.dtype,
                                                   reco_space.dtype))

        # Reserve name for cached properties (used for efficiency reasons)
        self._adjoint = None
        self._astra_wrapper = None

        # Extra kwargs that can be reused for adjoint etc. These must
        # be retrieved with `get` instead of `pop` above.
        self._extra_kwargs = kwargs

        # Finally, initialize the Operator structure
        if variant == 'forward':
            super().__init__(domain=reco_space, range=proj_space, linear=True)
        elif variant == 'backward':
            super().__init__(domain=proj_space, range=reco_space, linear=True)
Exemple #5
0
    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)
Exemple #6
0
    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)