def det_to_src(self, angle, dparam):
        """Direction from a detector location to the source.

        The direction vector is computed as follows::

            dir = rotation_matrix(angle).dot(detector.surface_normal(dparam))

        Note that for flat detectors, ``surface_normal`` does not depend
        on the parameter ``dparam``, hence this function is constant in
        that variable.

        Parameters
        ----------
        angle : `array-like` or sequence
            One or several (Euler) angles in radians at which to
            evaluate. If ``motion_params.ndim >= 2``, a sequence of that
            length must be provided.
        dparam : `array-like` or sequence
            Detector parameter(s) at which to evaluate. If
            ``det_params.ndim >= 2``, a sequence of that length must be
            provided.

        Returns
        -------
        det_to_src : `numpy.ndarray`
            Vector(s) pointing from a detector point to the source (at
            infinity).
            The shape of the returned array is obtained from the
            (broadcast) shapes of ``angle`` and ``dparam``, and
            broadcasting is supported within both parameters and between
            them. The precise definition of the shape is
            ``broadcast(bcast_angle, bcast_dparam).shape + (ndim,)``,
            where ``bcast_angle`` is

            - ``angle`` if `motion_params` is 1D,
            - ``broadcast(*angle)`` otherwise,

            and ``bcast_dparam`` defined analogously.

        Examples
        --------
        The method works with single parameter values, in which case
        a single vector is returned:

        >>> apart = odl.uniform_partition(0, np.pi, 10)
        >>> dpart = odl.uniform_partition(-1, 1, 20)
        >>> geom = odl.tomo.Parallel2dGeometry(apart, dpart)
        >>> geom.det_to_src(0, 0)
        array([ 0., -1.])
        >>> geom.det_to_src(0, 1)
        array([ 0., -1.])
        >>> dir = geom.det_to_src(np.pi / 2, 0)
        >>> np.allclose(dir, [1, 0])
        True
        >>> dir = geom.det_to_src(np.pi / 2, 1)
        >>> np.allclose(dir, [1, 0])
        True

        Both variables support vectorized calls, i.e., stacks of
        parameters can be provided. The order of axes in the output (left
        of the ``ndim`` axis for the vector dimension) corresponds to the
        order of arguments:

        >>> dirs = geom.det_to_src(0, [-1, 0, 0.5, 1])
        >>> dirs
        array([[ 0., -1.],
               [ 0., -1.],
               [ 0., -1.],
               [ 0., -1.]])
        >>> dirs.shape  # (num_dparams, ndim)
        (4, 2)
        >>> dirs = geom.det_to_src([0, np.pi / 2, np.pi], 0)
        >>> np.allclose(dirs, [[0, -1],
        ...                    [1, 0],
        ...                    [0, 1]])
        True
        >>> dirs.shape  # (num_angles, ndim)
        (3, 2)
        >>> # Providing 3 pairs of parameters, resulting in 3 vectors
        >>> dirs = geom.det_to_src([0, np.pi / 2, np.pi], [-1, 0, 1])
        >>> dirs[0]  # Corresponds to angle = 0, dparam = -1
        array([ 0., -1.])
        >>> dirs.shape
        (3, 2)
        >>> # Pairs of parameters arranged in arrays of same size
        >>> geom.det_to_src(np.zeros((4, 5)), np.zeros((4, 5))).shape
        (4, 5, 2)
        >>> # "Outer product" type evaluation using broadcasting
        >>> geom.det_to_src(np.zeros((4, 1)), np.zeros((1, 5))).shape
        (4, 5, 2)
        """
        # Always call the downstream methods with vectorized arguments
        # to be able to reliably manipulate the final axes of the result
        if self.motion_params.ndim == 1:
            squeeze_angle = (np.shape(angle) == ())
            angle = np.array(angle, dtype=float, copy=False, ndmin=1)
            matrix = self.rotation_matrix(angle)  # shape (m, ndim, ndim)
        else:
            squeeze_angle = (np.broadcast(*angle).shape == ())
            angle = tuple(np.array(a, dtype=float, copy=False, ndmin=1)
                          for a in angle)
            matrix = self.rotation_matrix(angle)  # shape (m, ndim, ndim)

        if self.det_params.ndim == 1:
            squeeze_dparam = (np.shape(dparam) == ())
            dparam = np.array(dparam, dtype=float, copy=False, ndmin=1)
        else:
            squeeze_dparam = (np.broadcast(*dparam).shape == ())
            dparam = tuple(np.array(p, dtype=float, copy=False, ndmin=1)
                           for p in dparam)

        normal = self.detector.surface_normal(dparam)  # shape (d, ndim)

        # Perform matrix-vector multiplication along the last axis of both
        # `matrix` and `normal` while "zipping" all axes that do not
        # participate in the matrix-vector product. In other words, the axes
        # are labelled
        # [0, 1, ..., r-1, r, r+1] for `matrix` and
        # [0, 1, ..., r-1, r+1] for `normal`, and the output axes are set to
        # [0, 1, ..., r-1, r]. This automatically supports broadcasting
        # along the axes 0, ..., r-1.
        matrix_axes = list(range(matrix.ndim))
        normal_axes = list(range(matrix.ndim - 2)) + [matrix_axes[-1]]
        out_axes = list(range(matrix.ndim - 1))
        det_to_src = np.einsum(matrix, matrix_axes, normal, normal_axes,
                               out_axes)
        if squeeze_angle and squeeze_dparam:
            det_to_src = det_to_src.squeeze()

        return det_to_src
def det_to_src ( self , angle , dparam ) : if self . motion_params . ndim == 1 : squeeze_angle = ( np . shape ( angle ) == ( ) ) angle = np . array ( angle , dtype = float , copy = False , ndmin = 1 ) matrix = self . rotation_matrix ( angle ) else : squeeze_angle = ( np . broadcast ( * angle ) . shape == ( ) ) angle = tuple ( np . array ( a , dtype = float , copy = False , ndmin = 1 ) for a in angle ) matrix = self . rotation_matrix ( angle ) if self . det_params . ndim == 1 : squeeze_dparam = ( np . shape ( dparam ) == ( ) ) dparam = np . array ( dparam , dtype = float , copy = False , ndmin = 1 ) else : squeeze_dparam = ( np . broadcast ( * dparam ) . shape == ( ) ) dparam = tuple ( np . array ( p , dtype = float , copy = False , ndmin = 1 ) for p in dparam ) normal = self . detector . surface_normal ( dparam ) matrix_axes = list ( range ( matrix . ndim ) ) normal_axes = list ( range ( matrix . ndim - 2 ) ) + [ matrix_axes [ - 1 ] ] out_axes = list ( range ( matrix . ndim - 1 ) ) det_to_src = np . einsum ( matrix , matrix_axes , normal , normal_axes , out_axes ) if squeeze_angle and squeeze_dparam : det_to_src = det_to_src . squeeze ( ) return det_to_src
Direction from a detector location to the source.

        The direction vector is computed as follows::

            dir = rotation_matrix(angle).dot(detector.surface_normal(dparam))

        Note that for flat detectors, ``surface_normal`` does not depend
        on the parameter ``dparam``, hence this function is constant in
        that variable.

        Parameters
        ----------
        angle : `array-like` or sequence
            One or several (Euler) angles in radians at which to
            evaluate. If ``motion_params.ndim >= 2``, a sequence of that
            length must be provided.
        dparam : `array-like` or sequence
            Detector parameter(s) at which to evaluate. If
            ``det_params.ndim >= 2``, a sequence of that length must be
            provided.

        Returns
        -------
        det_to_src : `numpy.ndarray`
            Vector(s) pointing from a detector point to the source (at
            infinity).
            The shape of the returned array is obtained from the
            (broadcast) shapes of ``angle`` and ``dparam``, and
            broadcasting is supported within both parameters and between
            them. The precise definition of the shape is
            ``broadcast(bcast_angle, bcast_dparam).shape + (ndim,)``,
            where ``bcast_angle`` is

            - ``angle`` if `motion_params` is 1D,
            - ``broadcast(*angle)`` otherwise,

            and ``bcast_dparam`` defined analogously.

        Examples
        --------
        The method works with single parameter values, in which case
        a single vector is returned:

        >>> apart = odl.uniform_partition(0, np.pi, 10)
        >>> dpart = odl.uniform_partition(-1, 1, 20)
        >>> geom = odl.tomo.Parallel2dGeometry(apart, dpart)
        >>> geom.det_to_src(0, 0)
        array([ 0., -1.])
        >>> geom.det_to_src(0, 1)
        array([ 0., -1.])
        >>> dir = geom.det_to_src(np.pi / 2, 0)
        >>> np.allclose(dir, [1, 0])
        True
        >>> dir = geom.det_to_src(np.pi / 2, 1)
        >>> np.allclose(dir, [1, 0])
        True

        Both variables support vectorized calls, i.e., stacks of
        parameters can be provided. The order of axes in the output (left
        of the ``ndim`` axis for the vector dimension) corresponds to the
        order of arguments:

        >>> dirs = geom.det_to_src(0, [-1, 0, 0.5, 1])
        >>> dirs
        array([[ 0., -1.],
               [ 0., -1.],
               [ 0., -1.],
               [ 0., -1.]])
        >>> dirs.shape  # (num_dparams, ndim)
        (4, 2)
        >>> dirs = geom.det_to_src([0, np.pi / 2, np.pi], 0)
        >>> np.allclose(dirs, [[0, -1],
        ...                    [1, 0],
        ...                    [0, 1]])
        True
        >>> dirs.shape  # (num_angles, ndim)
        (3, 2)
        >>> # Providing 3 pairs of parameters, resulting in 3 vectors
        >>> dirs = geom.det_to_src([0, np.pi / 2, np.pi], [-1, 0, 1])
        >>> dirs[0]  # Corresponds to angle = 0, dparam = -1
        array([ 0., -1.])
        >>> dirs.shape
        (3, 2)
        >>> # Pairs of parameters arranged in arrays of same size
        >>> geom.det_to_src(np.zeros((4, 5)), np.zeros((4, 5))).shape
        (4, 5, 2)
        >>> # "Outer product" type evaluation using broadcasting
        >>> geom.det_to_src(np.zeros((4, 1)), np.zeros((1, 5))).shape
        (4, 5, 2)