Example #1
0
    def rotation_matrix(self, angle):
        """Return the rotation matrix to the system state at ``angle``.

        The matrix is computed according to
        `Rodrigues' rotation formula
        <https://en.wikipedia.org/wiki/Rodrigues'_rotation_formula>`_.

        Parameters
        ----------
        angle : float or `array-like`
            Angle(s) in radians describing the counter-clockwise
            rotation of the system around `axis`.

        Returns
        -------
        rot : `numpy.ndarray`
            The rotation matrix (or matrices) mapping vectors at the
            initial state to the ones in the state defined by ``angle``.
            The rotation is extrinsic, i.e., defined in the "world"
            coordinate system.
            If ``angle`` is a single parameter, the returned array has
            shape ``(3, 3)``, otherwise ``angle.shape + (3, 3)``.
        """
        squeeze_out = (np.shape(angle) == ())
        angle = np.array(angle, dtype=float, copy=False, ndmin=1)
        if (self.check_bounds and
                not is_inside_bounds(angle, self.motion_params)):
            raise ValueError('`angle` {} not in the valid range {}'
                             ''.format(angle, self.motion_params))

        matrix = axis_rotation_matrix(self.axis, angle)
        if squeeze_out:
            matrix = matrix.squeeze()

        return matrix
Example #2
0
    def surface(self, param):
        """Return the detector surface point corresponding to ``param``.

        For parameter value ``p``, the surface point is given by ::

            surf = p * axis

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        point : `numpy.ndarray`
            Vector(s) pointing from the origin to the detector surface
            point at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(0, 1, 10)
        >>> det = Flat1dDetector(part, axis=[1, 0])
        >>> det.surface(0)
        array([ 0.,  0.])
        >>> det.surface(1)
        array([ 1.,  0.])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> det.surface([0, 1])
        array([[ 0.,  0.],
               [ 1.,  0.]])
        >>> det.surface(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        # Create outer product of `params` and `axis`, resulting in shape
        # params.shape + axis.shape
        surf = np.multiply.outer(param, self.axis)
        if squeeze_out:
            surf = surf.squeeze()

        return surf
Example #3
0
    def surface_measure(self, param):
        """Return the arc length measure at ``param``.

        This is a constant function evaluating to `radius` everywhere.

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        measure : float or `numpy.ndarray`
            Constant value(s) of the arc length measure at ``param``.
            If ``param`` is a single parameter, a float is returned,
            otherwise an array of shape ``param.shape``.

        See Also
        --------
        surface
        surface_deriv

        Examples
        --------
        The method works with a single parameter, resulting in a float:

        >>> part = odl.uniform_partition(-np.pi / 2, np.pi / 2, 10)
        >>> det = CircleSectionDetector(part, center=[0, -2])
        >>> det.surface_measure(0)
        2.0
        >>> det.surface_measure(np.pi / 2)
        2.0

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> det.surface_measure([0, np.pi / 2])
        array([ 2.,  2.])
        >>> det.surface_deriv(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        scalar_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        if scalar_out:
            return self.radius
        else:
            return self.radius * np.ones(param.shape)
Example #4
0
    def surface_deriv(self, param):
        """Return the surface derivative at ``param``.

        This is a constant function evaluating to `axis` everywhere.

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        deriv : `numpy.ndarray`
            Array representing the derivative vector(s) at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(0, 1, 10)
        >>> det = Flat1dDetector(part, axis=[1, 0])
        >>> det.surface_deriv(0)
        array([ 1.,  0.])
        >>> det.surface_deriv(1)
        array([ 1.,  0.])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> det.surface_deriv([0, 1])
        array([[ 1.,  0.],
               [ 1.,  0.]])
        >>> det.surface_deriv(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))
        if squeeze_out:
            return self.axis
        else:
            # Produce array of shape `param.shape + (ndim,)` by broadcasting
            axis_slc = (None, ) * param.ndim + (slice(None), )
            # TODO: use broadcast_to from Numpy when v1.10 is required
            zeros = np.zeros(param.shape + (1, ))
            return self.axis[axis_slc] + zeros
Example #5
0
    def surface_deriv(self, param):
        """Return the surface derivative at ``param``.

        The derivative at parameter ``phi`` is given by ::

            deriv = radius * (sin(phi) * center_dir + cos(phi) * tangent_at_0)

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        deriv : `numpy.ndarray`
            Array representing the derivative vector(s) at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        See Also
        --------
        surface

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(-np.pi / 2, np.pi / 2, 10)
        >>> det = CircleSectionDetector(part, center=[0, -2])
        >>> det.surface_deriv(0)
        array([ 2.,  0.])
        >>> np.allclose(det.surface_deriv(-np.pi / 2), [0, 2])
        True
        >>> np.allclose(det.surface_deriv(np.pi / 2), [0, -2])
        True

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> deriv = det.surface_deriv([-np.pi / 2, 0, np.pi / 2])
        >>> np.allclose(deriv, [[0, 2],
        ...                     [2, 0],
        ...                     [0, -2]])
        True
        >>> det.surface_deriv(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        # Compute an outer product of `sin(param)` with `center_dir`
        # and `cos(param)` with `tangent_at_0`, in order
        # to broadcast along all axes
        center_part = np.multiply.outer(np.sin(param), self.center_dir)
        tangent_part = np.multiply.outer(np.cos(param), self.tangent_at_0)
        deriv = self.radius * (center_part + tangent_part)

        if squeeze_out:
            deriv = deriv.squeeze()

        return deriv
Example #6
0
    def surface(self, param):
        """Return the detector surface point corresponding to ``param``.

        The surface point lies on a circle around `center` through the
        origin. More precisely, for a parameter ``phi``, the returned
        point is given by ::

            surf = radius * ((1 - cos(phi)) * center_dir +
                             sin(phi) * tangent_at_0)

        In particular, ``phi=0`` yields the origin ``(0, 0)``.

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        point : `numpy.ndarray`
            Vector(s) pointing from the origin to the detector surface
            point at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(-np.pi / 2, np.pi / 2, 10)
        >>> det = CircleSectionDetector(part, center=[0, -2])
        >>> det.surface(0)
        array([ 0.,  0.])
        >>> det.surface(-np.pi / 2)
        array([-2., -2.])
        >>> det.surface(np.pi / 2)
        array([ 2., -2.])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> det.surface([-np.pi / 2, 0, np.pi / 2])
        array([[-2., -2.],
               [ 0.,  0.],
               [ 2., -2.]])
        >>> det.surface(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        # Compute an outer product of `(1-cos(param))` with `center_dir`
        # and `sin(param)` with `tangent_at_0` and sum both, in order
        # to broadcast along all axes
        center_part = np.multiply.outer(1 - np.cos(param), self.center_dir)
        tangent_part = np.multiply.outer(np.sin(param), self.tangent_at_0)
        surf = self.radius * (center_part + tangent_part)
        if squeeze_out:
            surf = surf.squeeze()

        return surf
Example #7
0
    def surface_deriv(self, param):
        """Return the surface derivative at ``param``.

        This is a constant function evaluating to `axes` everywhere.

        Parameters
        ----------
        param : `array-like` or sequence
            Parameter value(s) at which to evaluate. A sequence of
            parameters must have length 2.

        Returns
        -------
        deriv : `numpy.ndarray`
            Array containing the derivative vectors. The first dimension
            enumerates the axes, i.e., has always length 2.
            If ``param`` is a single parameter, the returned array has
            shape ``(2, 3)``, otherwise
            ``broadcast(*param).shape + (2, 3)``.

        Notes
        -----
        To get an array that enumerates the derivative vectors in the first
        dimension, move the second-to-last axis to the first position::

            deriv = surface_deriv(param)
            axes_enumeration = np.moveaxis(deriv, -2, 0)

        Examples
        --------
        The method works with a single parameter, resulting in a 2-tuple
        of vectors:

        >>> part = odl.uniform_partition([0, 0], [1, 1], (10, 10))
        >>> det = Flat2dDetector(part, axes=[(1, 0, 0), (0, 0, 1)])
        >>> det.surface_deriv([0, 0])
        array([[ 1.,  0.,  0.],
               [ 0.,  0.,  1.]])
        >>> det.surface_deriv([1, 1])
        array([[ 1.,  0.,  0.],
               [ 0.,  0.,  1.]])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or n-dimensional arrays of parameters):

        >>> # 2 pairs of parameters, resulting in 3 vectors for each axis
        >>> deriv = det.surface_deriv([[0, 1],
        ...                            [0, 1]])
        >>> deriv[0]  # first pair of vectors
        array([[ 1.,  0.,  0.],
               [ 0.,  0.,  1.]])
        >>> deriv[1]  # second pair of vectors
        array([[ 1.,  0.,  0.],
               [ 0.,  0.,  1.]])
        >>> # Pairs of parameters in a (4, 5) array each
        >>> param = (np.zeros((4, 5)), np.zeros((4, 5)))  # pairs of params
        >>> det.surface_deriv(param).shape
        (4, 5, 2, 3)
        >>> # Using broadcasting for "outer product" type result
        >>> param = (np.zeros((4, 1)), np.zeros((1, 5)))  # broadcasting
        >>> det.surface_deriv(param).shape
        (4, 5, 2, 3)
        """
        squeeze_out = (np.broadcast(*param).shape == ())
        param_in = param
        param = tuple(
            np.array(p, dtype=float, copy=False, ndmin=1) for p in param)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param_in, self.params))

        if squeeze_out:
            return self.axes
        else:
            # Produce array of shape `broadcast(*param).shape + (2, 3)`
            # by explicit broadcasting.
            # TODO: use broadcast_to from Numpy when v1.10 is required
            axes_slc = ((None, ) * len(np.broadcast(*param).shape) +
                        (slice(None), slice(None)))
            zeros = np.zeros(np.broadcast(*param).shape + (1, 1))
            return self.axes[axes_slc] + zeros
Example #8
0
    def surface(self, param):
        """Return the detector surface point corresponding to ``param``.

        For parameter value ``p``, the surface point is given by ::

            surf = p[0] * axes[0] + p[1] * axes[1]

        Parameters
        ----------
        param : `array-like` or sequence
            Parameter value(s) at which to evaluate. A sequence of
            parameters must have length 2.

        Returns
        -------
        point : `numpy.ndarray`
            Vector(s) pointing from the origin to the detector surface
            point at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(3,)``, otherwise ``broadcast(*param).shape + (3,)``.

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition([0, 0], [1, 1], (10, 10))
        >>> det = Flat2dDetector(part, axes=[(1, 0, 0), (0, 0, 1)])
        >>> det.surface([0, 0])
        array([ 0.,  0.,  0.])
        >>> det.surface([0, 1])
        array([ 0.,  0.,  1.])
        >>> det.surface([1, 1])
        array([ 1.,  0.,  1.])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or n-dimensional arrays of parameters):

        >>> # 3 pairs of parameters, resulting in 3 vectors
        >>> det.surface([[0, 0, 1],
        ...              [0, 1, 1]])
        array([[ 0.,  0.,  0.],
               [ 0.,  0.,  1.],
               [ 1.,  0.,  1.]])
        >>> # Pairs of parameters in a (4, 5) array each
        >>> param = (np.zeros((4, 5)), np.zeros((4, 5)))
        >>> det.surface(param).shape
        (4, 5, 3)
        >>> # Using broadcasting for "outer product" type result
        >>> param = (np.zeros((4, 1)), np.zeros((1, 5)))
        >>> det.surface(param).shape
        (4, 5, 3)
        """
        squeeze_out = (np.broadcast(*param).shape == ())
        param_in = param
        param = tuple(
            np.array(p, dtype=float, copy=False, ndmin=1) for p in param)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param_in, self.params))

        # Compute outer product of the i-th spatial component of the
        # parameter and sum up the contributions
        surf = sum(np.multiply.outer(p, ax) for p, ax in zip(param, self.axes))
        if squeeze_out:
            surf = surf.squeeze()

        return surf
Example #9
0
    def surface_deriv(self, param):
        """Return the surface derivative at ``param``.

        The derivative at parameter ``phi`` is given by ::

            deriv = R * radius * (-sin(phi), -cos(phi))

        where R is a rotation matrix.

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        deriv : `numpy.ndarray`
            Array representing the derivative vector(s) at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        See Also
        --------
        surface

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(-np.pi / 2, np.pi / 2, 10)
        >>> det = CircularDetector(part, axis=[1, 0], radius=2)
        >>> det.surface_deriv(0)
        array([ 2.,  0.])

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> np.round(det.surface_deriv([-np.pi / 2, 0, np.pi / 2]), 10)
        array([[ 0.,  2.],
               [ 2.,  0.],
               [ 0., -2.]])

        >>> det.surface_deriv(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        deriv = np.empty(param.shape + (2, ))
        deriv[..., 0] = -np.sin(param)
        deriv[..., 1] = -np.cos(param)
        deriv *= self.radius
        deriv = np.matmul(deriv, np.transpose(self.rotation_matrix))

        if squeeze_out:
            deriv = deriv.squeeze()

        return deriv
Example #10
0
    def surface(self, param):
        """Return the detector surface point corresponding to ``param``.

        For a parameter ``phi``, the returned point is given by ::

            surf = R * radius * (cos(phi), -sin(phi)) + t

        where ``R`` is a rotation matrix and ``t`` is a translation vector.
        Note that increase of ``phi`` corresponds to rotation
        in the clockwise direction, by analogy to flat detectors.

        Parameters
        ----------
        param : float or `array-like`
            Parameter value(s) at which to evaluate.

        Returns
        -------
        point : `numpy.ndarray`
            Vector(s) pointing from the origin to the detector surface
            point at ``param``.
            If ``param`` is a single parameter, the returned array has
            shape ``(2,)``, otherwise ``param.shape + (2,)``.

        Examples
        --------
        The method works with a single parameter, resulting in a single
        vector:

        >>> part = odl.uniform_partition(-np.pi / 2, np.pi / 2, 10)
        >>> det = CircularDetector(part, axis=[1, 0], radius=2)
        >>> np.allclose(det.surface(0), [0, 0])
        True

        It is also vectorized, i.e., it can be called with multiple
        parameters at once (or an n-dimensional array of parameters):

        >>> np.round(det.surface([-np.pi / 2, 0, np.pi / 2]), 10)
        array([[-2., -2.],
               [ 0.,  0.],
               [ 2., -2.]])

        >>> det.surface(np.zeros((4, 5))).shape
        (4, 5, 2)
        """
        squeeze_out = (np.shape(param) == ())
        param = np.array(param, dtype=float, copy=False, ndmin=1)
        if self.check_bounds and not is_inside_bounds(param, self.params):
            raise ValueError('`param` {} not in the valid range '
                             '{}'.format(param, self.params))

        surf = np.empty(param.shape + (2, ))
        surf[..., 0] = np.cos(param)
        surf[..., 1] = -np.sin(param)
        surf *= self.radius
        surf = np.matmul(surf, np.transpose(self.rotation_matrix))
        surf += self.translation
        if squeeze_out:
            surf = surf.squeeze()

        return surf