Exemple #1
0
    def __init__(self, apart, dpart, axis=[0, 0, 1], **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter interval
        axis : `array-like`, shape ``(3,)``, optional
            Fixed rotation axis defined by a 3-element vector
        det_init_pos : `array-like`, shape ``(3,)``, optional
            Initial position of the detector reference point. The zero
            vector is only allowed if ``det_init_axes`` is explicitly
            given.
            By default, a `perpendicular_vector` to ``axis`` is used.
        det_init_axes : 2-tuple of `array-like`'s (shape ``(3,)``), optional
            Initial axes defining the detector orientation.
            By default, the normalized cross product of ``axis`` and
            ``det_init_pos`` is used as first axis and ``axis`` as second.
        """
        AxisOrientedGeometry.__init__(self, axis)

        det_init_pos = kwargs.pop('det_init_pos', perpendicular_vector(axis))
        det_init_axes = kwargs.pop('det_init_axes', None)

        if det_init_axes is None:
            if np.linalg.norm(det_init_pos) <= 1e-10:
                raise ValueError('initial detector position {} is close to '
                                 'zero. This is only allowed for explicit '
                                 '`det_init_axes`.'.format(det_init_pos))

            det_init_axis_0 = np.cross(self.axis, det_init_pos)
            det_init_axis_0 /= np.linalg.norm(det_init_axis_0)
            det_init_axes = (det_init_axis_0, axis)

        self.det_init_axes = det_init_axes

        detector = Flat2dDetector(part=dpart, axes=det_init_axes)
        super().__init__(ndim=3,
                         apart=apart,
                         detector=detector,
                         det_init_pos=det_init_pos)

        if self.motion_partition.ndim != 1:
            raise ValueError('`apart` has dimension {}, expected 1'
                             ''.format(self.motion_partition.ndim))
Exemple #2
0
    def __init__(self, apart, dpart, axis=[0, 0, 1], **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter interval
        axis : `array-like`, shape ``(3,)``, optional
            Fixed rotation axis defined by a 3-element vector
        det_init_pos : `array-like`, shape ``(3,)``, optional
            Initial position of the detector reference point. The zero
            vector is only allowed if ``det_init_axes`` is explicitly
            given.
            By default, a `perpendicular_vector` to ``axis`` is used.
        det_init_axes : 2-tuple of `array-like`'s (shape ``(3,)``), optional
            Initial axes defining the detector orientation.
            By default, the normalized cross product of ``axis`` and
            ``det_init_pos`` is used as first axis and ``axis`` as second.
        """
        AxisOrientedGeometry.__init__(self, axis)

        det_init_pos = kwargs.pop('det_init_pos', perpendicular_vector(axis))
        det_init_axes = kwargs.pop('det_init_axes', None)

        if det_init_axes is None:
            if np.linalg.norm(det_init_pos) <= 1e-10:
                raise ValueError('initial detector position {} is close to '
                                 'zero. This is only allowed for explicit '
                                 '`det_init_axes`.'.format(det_init_pos))

            det_init_axis_0 = np.cross(self.axis, det_init_pos)
            det_init_axis_0 /= np.linalg.norm(det_init_axis_0)
            det_init_axes = (det_init_axis_0, axis)

        self.det_init_axes = det_init_axes

        detector = Flat2dDetector(part=dpart, axes=det_init_axes)
        super().__init__(ndim=3, apart=apart, detector=detector,
                         det_init_pos=det_init_pos)

        if self.motion_partition.ndim != 1:
            raise ValueError('`apart` has dimension {}, expected 1'
                             ''.format(self.motion_partition.ndim))
Exemple #3
0
    def __init__(self, apart, dpart, src_radius, det_radius, pitch,
                 axis=[0, 0, 1], **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter rectangle
        src_radius : nonnegative float
            Radius of the source circle
        det_radius : nonnegative float
            Radius of the detector circle
        pitch : float
            Constant vertical distance that a point on the helix
            traverses when increasing the angle parameter by ``2 * pi``
        axis : `array-like`, shape ``(3,)``, optional
            Fixed rotation axis, the symmetry axis of the helix

        Other Parameters
        ----------------
        src_to_det_init : `array-like`, shape ``(2,)``, optional
            Initial state of the vector pointing from source to detector
            reference point. The zero vector is not allowed.
            By default, a `perpendicular_vector` to ``axis`` is used.
        det_init_axes : 2-tuple of `array-like`'s (shape ``(2,)``), optional
            Initial axes defining the detector orientation.
            By default, the normalized cross product of ``axis`` and
            ``src_to_det_init`` is used as first axis and ``axis`` as
            second.
        pitch_offset : float, optional
            Offset along the ``axis`` at ``angle=0``
        """
        AxisOrientedGeometry.__init__(self, axis)

        src_to_det_init = kwargs.pop('src_to_det_init',
                                     perpendicular_vector(self.axis))

        if np.linalg.norm(src_to_det_init) <= 1e-10:
            raise ValueError('initial source to detector vector {} is too '
                             'close to zero'.format(src_to_det_init))
        self._src_to_det_init = (np.array(src_to_det_init) /
                                 np.linalg.norm(src_to_det_init))

        det_init_axes = kwargs.pop('det_init_axes', None)
        if det_init_axes is None:
            det_init_axis_0 = np.cross(self.axis, self._src_to_det_init)
            det_init_axes = (det_init_axis_0, axis)

        detector = Flat2dDetector(dpart, det_init_axes)

        super().__init__(ndim=3, motion_part=apart, detector=detector)

        self._pitch = float(pitch)
        self._pitch_offset = float(kwargs.pop('pitch_offset', 0))
        self._src_radius = float(src_radius)
        if self.src_radius < 0:
            raise ValueError('source circle radius {} is negative'
                             ''.format(src_radius))
        self._det_radius = float(det_radius)
        if self.det_radius < 0:
            raise ValueError('detector circle radius {} is negative'
                             ''.format(det_radius))

        if self.src_radius == 0 and self.det_radius == 0:
            raise ValueError('source and detector circle radii cannot both be '
                             '0')
Exemple #4
0
    def __init__(self, apart, dpart, axis=(0, 0, 1), **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval.
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter rectangle.
        axis : `array-like`, shape ``(3,)``, optional
            Vector defining the fixed rotation axis of this geometry.

        Other Parameters
        ----------------
        det_pos_init : `array-like`, shape ``(3,)``, optional
            Initial position of the detector reference point.
            The default depends on ``axis``, see Notes.
        det_axes_init : 2-tuple of `array-like`'s (shape ``(3,)``), optional
            Initial axes defining the detector orientation. The default
            depends on ``axis``, see Notes.
        translation : `array-like`, shape ``(3,)``, optional
            Global translation of the geometry. This is added last in any
            method that computes an absolute vector, e.g., `det_refpoint`,
            and also shifts the axis of rotation.

        Notes
        -----
        In the default configuration, the rotation axis is ``(0, 0, 1)``,
        the initial detector reference point position is ``(0, 1, 0)``,
        and the default detector axes are ``[(1, 0, 0), (0, 0, 1)]``.
        If a different ``axis`` is provided, the new default initial
        position and the new default axes are the computed by rotating
        the original ones by a matrix that transforms ``(0, 0, 1)`` to the
        new (normalized) ``axis``. This matrix is calculated with the
        `rotation_matrix_from_to` function. Expressed in code, we have ::

            init_rot = rotation_matrix_from_to((0, 0, 1), axis)
            det_pos_init = init_rot.dot((0, 1, 0))
            det_axes_init[0] = init_rot.dot((1, 0, 0))
            det_axes_init[1] = init_rot.dot((0, 0, 1))

        Examples
        --------
        Initialization with default parameters:

        >>> apart = odl.uniform_partition(0, np.pi, 10)
        >>> dpart = odl.uniform_partition([-1, -1], [1, 1], (20, 20))
        >>> geom = Parallel3dAxisGeometry(apart, dpart)
        >>> geom.det_refpoint(0)
        array([ 0.,  1.,  0.])
        >>> geom.det_point_position(0, [-1, 1])
        array([-1.,  1.,  1.])

        Checking the default orientation:

        >>> e_x, e_y, e_z = np.eye(3)  # standard unit vectors
        >>> np.allclose(geom.axis, e_z)
        True
        >>> np.allclose(geom.det_pos_init, e_y)
        True
        >>> np.allclose(geom.det_axes_init, (e_x, e_z))
        True

        Specifying an axis by default rotates the standard configuration
        to this position:

        >>> geom = Parallel3dAxisGeometry(apart, dpart, axis=(0, 1, 0))
        >>> np.allclose(geom.axis, e_y)
        True
        >>> np.allclose(geom.det_pos_init, -e_z)
        True
        >>> np.allclose(geom.det_axes_init, (e_x, e_y))
        True
        >>> geom = Parallel3dAxisGeometry(apart, dpart, axis=(1, 0, 0))
        >>> np.allclose(geom.axis, e_x)
        True
        >>> np.allclose(geom.det_pos_init, e_y)
        True
        >>> np.allclose(geom.det_axes_init, (-e_z, e_x))
        True

        The initial detector position and axes can also be set explicitly:

        >>> geom = Parallel3dAxisGeometry(
        ...     apart, dpart, det_pos_init=(-1, 0, 0),
        ...     det_axes_init=((0, 1, 0), (0, 0, 1)))
        >>> np.allclose(geom.axis, e_z)
        True
        >>> np.allclose(geom.det_pos_init, -e_x)
        True
        >>> np.allclose(geom.det_axes_init, (e_y, e_z))
        True
        """
        default_axis = self._default_config['axis']
        default_det_pos_init = self._default_config['det_pos_init']
        default_det_axes_init = self._default_config['det_axes_init']

        # Handle initial coordinate system. We need to assign `None` to
        # the vectors first since we want to check that `init_matrix`
        # is not used together with those other parameters.
        det_pos_init = kwargs.pop('det_pos_init', None)
        det_axes_init = kwargs.pop('det_axes_init', None)

        # Store some stuff for repr
        if det_pos_init is not None:
            self._det_pos_init_arg = np.asarray(det_pos_init, dtype=float)
        else:
            self._det_pos_init_arg = None

        if det_axes_init is not None:
            self._det_axes_init_arg = tuple(
                np.asarray(a, dtype=float) for a in det_axes_init)
        else:
            self._det_axes_init_arg = None

        # Compute the transformed system and the transition matrix. We
        # transform only those vectors that were not explicitly given.
        vecs_to_transform = []
        if det_pos_init is None:
            vecs_to_transform.append(default_det_pos_init)
        if det_axes_init is None:
            vecs_to_transform.extend(default_det_axes_init)

        transformed_vecs = transform_system(axis, default_axis,
                                            vecs_to_transform)
        transformed_vecs = list(transformed_vecs)

        axis = transformed_vecs.pop(0)
        if det_pos_init is None:
            det_pos_init = transformed_vecs.pop(0)
        if det_axes_init is None:
            det_axes_init = (transformed_vecs.pop(0), transformed_vecs.pop(0))

        assert transformed_vecs == []

        # Translate the absolute vectors by the given translation
        translation = np.asarray(kwargs.pop('translation', (0, 0, 0)),
                                 dtype=float)
        det_pos_init += translation

        # Initialize stuff. Normalization of the detector axis happens in
        # the detector class.
        AxisOrientedGeometry.__init__(self, axis)
        detector = Flat2dDetector(dpart, det_axes_init)
        super().__init__(ndim=3,
                         apart=apart,
                         detector=detector,
                         det_pos_init=det_pos_init,
                         translation=translation)

        if self.motion_partition.ndim != 1:
            raise ValueError('`apart` has dimension {}, expected 1'
                             ''.format(self.motion_partition.ndim))

        # Make sure there are no leftover kwargs
        if kwargs:
            raise TypeError('got unexpected keyword arguments {}'
                            ''.format(kwargs))
Exemple #5
0
    def __init__(self, apart, dpart, src_radius, det_radius, pitch,
                 axis=[0, 0, 1], **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter rectangle
        src_radius : nonnegative float
            Radius of the source circle
        det_radius : nonnegative float
            Radius of the detector circle
        pitch : float
            Constant vertical distance that a point on the helix
            traverses when increasing the angle parameter by ``2 * pi``
        axis : `array-like`, shape ``(3,)``, optional
            Fixed rotation axis, the symmetry axis of the helix

        Other Parameters
        ----------------
        src_to_det_init : `array-like`, shape ``(2,)``, optional
            Initial state of the vector pointing from source to detector
            reference point. The zero vector is not allowed.
            By default, a `perpendicular_vector` to ``axis`` is used.
        det_init_axes : 2-tuple of `array-like`'s (shape ``(2,)``), optional
            Initial axes defining the detector orientation.
            By default, the normalized cross product of ``axis`` and
            ``src_to_det_init`` is used as first axis and ``axis`` as
            second.
        pitch_offset : float, optional
            Offset along the ``axis`` at ``angle=0``
        """
        AxisOrientedGeometry.__init__(self, axis)

        src_to_det_init = kwargs.pop('src_to_det_init',
                                     perpendicular_vector(self.axis))

        if np.linalg.norm(src_to_det_init) <= 1e-10:
            raise ValueError('initial source to detector vector {} is too '
                             'close to zero'.format(src_to_det_init))
        self._src_to_det_init = (np.array(src_to_det_init) /
                                 np.linalg.norm(src_to_det_init))

        det_init_axes = kwargs.pop('det_init_axes', None)
        if det_init_axes is None:
            det_init_axis_0 = np.cross(self.axis, self._src_to_det_init)
            det_init_axes = (det_init_axis_0, axis)

        detector = Flat2dDetector(dpart, det_init_axes)

        super().__init__(ndim=3, motion_part=apart, detector=detector)

        self._pitch = float(pitch)
        self._pitch_offset = float(kwargs.pop('pitch_offset', 0))
        self._src_radius = float(src_radius)
        if self.src_radius < 0:
            raise ValueError('source circle radius {} is negative'
                             ''.format(src_radius))
        self._det_radius = float(det_radius)
        if self.det_radius < 0:
            raise ValueError('detector circle radius {} is negative'
                             ''.format(det_radius))

        if self.src_radius == 0 and self.det_radius == 0:
            raise ValueError('source and detector circle radii cannot both be '
                             '0')
Exemple #6
0
    def __init__(self,
                 apart,
                 dpart,
                 src_radius,
                 det_radius,
                 pitch=0,
                 axis=(0, 0, 1),
                 **kwargs):
        """Initialize a new instance.

        Parameters
        ----------
        apart : 1-dim. `RectPartition`
            Partition of the angle interval.
        dpart : 2-dim. `RectPartition`
            Partition of the detector parameter rectangle.
        src_radius : nonnegative float
            Radius of the source circle.
        det_radius : nonnegative float
            Radius of the detector circle. Must be nonzero if ``src_radius``
            is zero.
        pitch : float, optional
            Constant distance along ``axis`` that a point on the helix
            traverses when increasing the angle parameter by ``2 * pi``.
            The default case ``pitch=0`` results in a circular cone
            beam geometry.
        axis : `array-like`, shape ``(3,)``, optional
            Vector defining the fixed rotation axis of this geometry.

        Other Parameters
        ----------------
        offset_along_axis : float, optional
            Scalar offset along the ``axis`` at ``angle=0``, i.e., the
            translation along the axis at angle 0 is
            ``offset_along_axis * axis``.
            Default: 0.
        src_to_det_init : `array-like`, shape ``(2,)``, optional
            Initial state of the vector pointing from source to detector
            reference point. The zero vector is not allowed.
            The default depends on ``axis``, see Notes.
        det_axes_init : 2-tuple of `array-like`'s (shape ``(2,)``), optional
            Initial axes defining the detector orientation. The default
            depends on ``axis``, see Notes.
        translation : `array-like`, shape ``(3,)``, optional
            Global translation of the geometry. This is added last in any
            method that computes an absolute vector, e.g., `det_refpoint`,
            and also shifts the axis of rotation.

        Notes
        -----
        In the default configuration, the rotation axis is ``(0, 0, 1)``,
        the initial source-to-detector direction is ``(0, 1, 0)``,
        and the default detector axes are ``[(1, 0, 0), (0, 0, 1)]``.
        If a different ``axis`` is provided, the new default initial
        position and the new default axes are the computed by rotating
        the original ones by a matrix that transforms ``(0, 0, 1)`` to the
        new (normalized) ``axis``. This matrix is calculated with the
        `rotation_matrix_from_to` function. Expressed in code, we have ::

            init_rot = rotation_matrix_from_to((0, 0, 1), axis)
            src_to_det_init = init_rot.dot((0, 1, 0))
            det_axes_init[0] = init_rot.dot((1, 0, 0))
            det_axes_init[1] = init_rot.dot((0, 0, 1))

        Examples
        --------
        Initialization with default parameters and some (arbitrary)
        choices for pitch and radii:

        >>> apart = odl.uniform_partition(0, 4 * np.pi, 10)
        >>> dpart = odl.uniform_partition([-1, -1], [1, 1], (20, 20))
        >>> geom = ConeFlatGeometry(
        ...     apart, dpart, src_radius=5, det_radius=10, pitch=2)
        >>> geom.src_position(0)
        array([ 0., -5.,  0.])
        >>> geom.det_refpoint(0)
        array([ 0., 10.,  0.])
        >>> np.allclose(geom.src_position(2 * np.pi),
        ...             geom.src_position(0) + (0, 0, 2))  # z shift by pitch
        True

        Checking the default orientation:

        >>> geom.axis
        array([ 0.,  0.,  1.])
        >>> geom.src_to_det_init
        array([ 0.,  1.,  0.])
        >>> geom.det_axes_init
        (array([ 1.,  0.,  0.]), array([ 0.,  0.,  1.]))

        Specifying an axis by default rotates the standard configuration
        to this position:

        >>> e_x, e_y, e_z = np.eye(3)  # standard unit vectors
        >>> geom = ConeFlatGeometry(
        ...     apart, dpart, src_radius=5, det_radius=10, pitch=2,
        ...     axis=(0, 1, 0))
        >>> np.allclose(geom.axis, e_y)
        True
        >>> np.allclose(geom.src_to_det_init, -e_z)
        True
        >>> np.allclose(geom.det_axes_init, (e_x, e_y))
        True
        >>> geom = ConeFlatGeometry(
        ...     apart, dpart, src_radius=5, det_radius=10, pitch=2,
        ...     axis=(1, 0, 0))
        >>> np.allclose(geom.axis, e_x)
        True
        >>> np.allclose(geom.src_to_det_init, e_y)
        True
        >>> np.allclose(geom.det_axes_init, (-e_z, e_x))
        True

        The initial source-to-detector vector and the detector axes can
        also be set explicitly:

        >>> geom = ConeFlatGeometry(
        ...     apart, dpart, src_radius=5, det_radius=10, pitch=2,
        ...     src_to_det_init=(-1, 0, 0),
        ...     det_axes_init=((0, 1, 0), (0, 0, 1)))
        >>> np.allclose(geom.axis, e_z)
        True
        >>> np.allclose(geom.src_to_det_init, -e_x)
        True
        >>> np.allclose(geom.det_axes_init, (e_y, e_z))
        True
        """
        default_axis = self._default_config['axis']
        default_src_to_det_init = self._default_config['src_to_det_init']
        default_det_axes_init = self._default_config['det_axes_init']

        # Handle initial coordinate system. We need to assign `None` to
        # the vectors first since we want to check that `init_matrix`
        # is not used together with those other parameters.
        src_to_det_init = kwargs.pop('src_to_det_init', None)
        det_axes_init = kwargs.pop('det_axes_init', None)

        # Store some stuff for repr
        if src_to_det_init is not None:
            self._src_to_det_init_arg = np.asarray(src_to_det_init,
                                                   dtype=float)
        else:
            self._src_to_det_init_arg = None

        if det_axes_init is not None:
            self._det_axes_init_arg = tuple(
                np.asarray(a, dtype=float) for a in det_axes_init)
        else:
            self._det_axes_init_arg = None

        # Compute the transformed system and the transition matrix. We
        # transform only those vectors that were not explicitly given.
        vecs_to_transform = []
        if src_to_det_init is None:
            vecs_to_transform.append(default_src_to_det_init)
        if det_axes_init is None:
            vecs_to_transform.extend(default_det_axes_init)

        transformed_vecs = transform_system(axis, default_axis,
                                            vecs_to_transform)
        transformed_vecs = list(transformed_vecs)

        axis = transformed_vecs.pop(0)
        if src_to_det_init is None:
            src_to_det_init = transformed_vecs.pop(0)
        if det_axes_init is None:
            det_axes_init = (transformed_vecs.pop(0), transformed_vecs.pop(0))
        assert transformed_vecs == []

        # Check and normalize `src_to_det_init`. Detector axes are
        # normalized in the detector class.
        if np.linalg.norm(src_to_det_init) <= 1e-10:
            raise ValueError('`src_to_det_init` norm {} too close to 0'
                             ''.format(np.linalg.norm(src_to_det_init)))
        else:
            src_to_det_init /= np.linalg.norm(src_to_det_init)

        # Initialize stuff
        self.__src_to_det_init = src_to_det_init
        AxisOrientedGeometry.__init__(self, axis)
        detector = Flat2dDetector(dpart, det_axes_init)
        translation = kwargs.pop('translation', None)
        super().__init__(ndim=3,
                         motion_part=apart,
                         detector=detector,
                         translation=translation)

        self.__pitch = float(pitch)
        self.__offset_along_axis = float(kwargs.pop('offset_along_axis', 0))
        self.__src_radius = float(src_radius)
        if self.src_radius < 0:
            raise ValueError('source circle radius {} is negative'
                             ''.format(src_radius))
        self.__det_radius = float(det_radius)
        if self.det_radius < 0:
            raise ValueError('detector circle radius {} is negative'
                             ''.format(det_radius))

        if self.src_radius == 0 and self.det_radius == 0:
            raise ValueError('source and detector circle radii cannot both be '
                             '0')

        if self.motion_partition.ndim != 1:
            raise ValueError('`apart` has dimension {}, expected 1'
                             ''.format(self.motion_partition.ndim))

        # Make sure there are no leftover kwargs
        if kwargs:
            raise TypeError('got unexpected keyword arguments {}'
                            ''.format(kwargs))