コード例 #1
0
    def surface_measure(self, param):
        """Density function of the surface measure.

        This is the default implementation relying on the `surface_deriv`
        method. For a detector with `ndim` equal to 1, the density is given
        by the `Arc length`_, for a surface with `ndim` 2 in a 3D space, it
        is the length of the cross product of the partial derivatives of the
        parametrization, see Wikipedia's `Surface area`_ article.

        Parameters
        ----------
        param : `array-like` or sequence
            Parameter value(s) at which to evaluate.  If ``ndim >= 2``,
            a sequence of length `ndim` must be provided.

        Returns
        -------
        measure : float or `numpy.ndarray`
            The density value(s) at the given parameter(s). If a single
            parameter is provided, a float is returned. Otherwise, an
            array is returned with shape

            - ``param.shape`` if `ndim` is 1,
            - ``broadcast(*param).shape`` otherwise.

        References
        ----------
        .. _Arc length:
            https://en.wikipedia.org/wiki/Curve#Lengths_of_curves
        .. _Surface area:
            https://en.wikipedia.org/wiki/Surface_area
        """
        # Checking is done by `surface_deriv`
        if self.ndim == 1:
            scalar_out = (np.shape(param) == ())
            measure = np.linalg.norm(self.surface_deriv(param), axis=-1)
            if scalar_out:
                measure = float(measure)

            return measure

        elif self.ndim == 2 and self.space_ndim == 3:
            scalar_out = (np.shape(param) == (2, ))
            deriv = self.surface_deriv(param)
            if deriv.ndim > 2:
                # Vectorized, need to reshape (N, 2, 3) to (2, N, 3)
                deriv = moveaxis(deriv, -2, 0)
            cross = np.cross(*deriv, axis=-1)
            measure = np.linalg.norm(cross, axis=-1)
            if scalar_out:
                measure = float(measure)

            return measure

        else:
            raise NotImplementedError(
                'no default implementation of `surface_measure` available '
                'for `ndim={}` and `space_ndim={}`'
                ''.format(self.ndim, self.space_ndim))
コード例 #2
0
    def surface_normal(self, param):
        """Unit vector perpendicular to the detector surface at ``param``.

        The orientation is chosen as follows:

            - In 2D, the system ``(normal, tangent)`` should be
              right-handed.
            - In 3D, the system ``(tangent[0], tangent[1], normal)``
              should be right-handed.

        Here, ``tangent`` is the return value of `surface_deriv` at
        ``param``.

        Parameters
        ----------
        param : `array-like` or sequence
            Parameter value(s) at which to evaluate.  If ``ndim >= 2``,
            a sequence of length `ndim` must be provided.

        Returns
        -------
        normal : `numpy.ndarray`
            Unit vector(s) perpendicular to the detector surface at
            ``param``.
            If ``param`` is a single parameter, an array of shape
            ``(space_ndim,)`` representing a single vector is returned.
            Otherwise the shape of the returned array is

            - ``param.shape + (space_ndim,)`` if `ndim` is 1,
            - ``param.shape[:-1] + (space_ndim,)`` otherwise.
        """
        # Checking is done by `surface_deriv`
        if self.ndim == 1 and self.space_ndim == 2:
            return -perpendicular_vector(self.surface_deriv(param))
        elif self.ndim == 2 and self.space_ndim == 3:
            deriv = self.surface_deriv(param)
            if deriv.ndim > 2:
                # Vectorized, need to reshape (N, 2, 3) to (2, N, 3)
                deriv = moveaxis(deriv, -2, 0)
            normal = np.cross(*deriv, axis=-1)
            normal /= np.linalg.norm(normal, axis=-1, keepdims=True)
            return normal
        else:
            raise NotImplementedError(
                'no default implementation of `surface_normal` available '
                'for `ndim = {}` and `space_ndim = {}`'
                ''.format(self.ndim, self.space_ndim))
コード例 #3
0
def astra_parallel_3d_geom_to_vec(geometry):
    """Create vectors for ASTRA projection geometries from ODL geometry.

    The 3D vectors are used to create an ASTRA projection geometry for
    parallel beam geometries, see ``'parallel3d_vec'`` in the
    `ASTRA projection geometry documentation`_.

    Each row of the returned vectors corresponds to a single projection
    and consists of ::

        (rayX, rayY, rayZ, dX, dY, dZ, uX, uY, uZ, vX, vY, vZ)

    with

        - ``ray``: the ray direction
        - ``d``  : the center of the detector
        - ``u``  : the vector from detector pixel ``(0,0)`` to ``(0,1)``
        - ``v``  : the vector from detector pixel ``(0,0)`` to ``(1,0)``

    Parameters
    ----------
    geometry : `Geometry`
        ODL projection geometry from which to create the ASTRA geometry.

    Returns
    -------
    vectors : `numpy.ndarray`
        Array of shape ``(num_angles, 12)`` containing the vectors.

    References
    ----------
    .. _ASTRA projection geometry documentation:
       http://www.astra-toolbox.com/docs/geom3d.html#projection-geometries
    """
    angles = geometry.angles
    mid_pt = geometry.det_params.mid_pt

    vectors = np.zeros((angles.shape[-1], 12))

    # Ray direction = -(detector-to-source normal vector)
    vectors[:, 0:3] = -geometry.det_to_src(angles, mid_pt)

    # Center of the detector in 3D space
    vectors[:, 3:6] = geometry.det_point_position(angles, mid_pt)

    # Vectors from detector pixel (0, 0) to (1, 0) and (0, 0) to (0, 1)
    # `det_axes` gives shape (N, 2, 3), swap to get (2, N, 3)
    det_axes = moveaxis(geometry.det_axes(angles), -2, 0)
    px_sizes = geometry.det_partition.cell_sides
    # Swap detector axes to have better memory layout in  projection data.
    # ASTRA produces `(v, theta, u)` layout, and to map to ODL layout
    # `(theta, u, v)` a complete roll must be performed, which is the
    # worst case (compeltely discontiguous).
    # Instead we swap `u` and `v`, resulting in the effective ASTRA result
    # `(u, theta, v)`. Here we only need to swap axes 0 and 1, which
    # keeps at least contiguous blocks in `v`.
    vectors[:, 9:12] = det_axes[0] * px_sizes[0]
    vectors[:, 6:9] = det_axes[1] * px_sizes[1]

    # ASTRA has (z, y, x) axis convention, in contrast to (x, y, z) in ODL,
    # so we need to adapt to this by changing the order.
    new_ind = []
    for i in range(4):
        new_ind += [2 + 3 * i, 1 + 3 * i, 0 + 3 * i]
    vectors = vectors[:, new_ind]
    return vectors