def frommatrix(cls, apart, dpart, init_matrix): """Create an instance of `Parallel3dAxisGeometry` using a matrix. This alternative constructor uses a matrix to rotate and translate the default configuration. It is most useful when the transformation to be applied is already given as a matrix. Parameters ---------- apart : 1-dim. `RectPartition` Partition of the parameter interval. dpart : 2-dim. `RectPartition` Partition of the detector parameter set. init_matrix : `array_like`, shape ``(3, 3)`` or ``(3, 4)``, optional Transformation matrix whose left ``(3, 3)`` block is multiplied with the default ``det_pos_init`` and ``det_axes_init`` to determine the new vectors. If present, the fourth column acts as a translation after the initial transformation. The resulting ``det_axes_init`` will be normalized. Returns ------- geometry : `Parallel3dAxisGeometry` Examples -------- Map unit vectors ``e_y -> e_z`` and ``e_z -> -e_y``, keeping the right-handedness: >>> apart = odl.uniform_partition(0, np.pi, 10) >>> dpart = odl.uniform_partition([-1, -1], [1, 1], (20, 20)) >>> matrix = np.array([[1, 0, 0], ... [0, 0, -1], ... [0, 1, 0]]) >>> geom = Parallel3dAxisGeometry.frommatrix( ... apart, dpart, init_matrix=matrix) >>> geom.axis array([ 0., -1., 0.]) >>> geom.det_pos_init array([ 0., 0., 1.]) >>> geom.det_axes_init (array([ 1., 0., 0.]), array([ 0., -1., 0.])) Adding a translation with a fourth matrix column: >>> matrix = np.array([[0, 0, -1, 0], ... [0, 1, 0, 1], ... [1, 0, 0, 1]]) >>> geom = Parallel3dAxisGeometry.frommatrix(apart, dpart, matrix) >>> geom.translation array([ 0., 1., 1.]) >>> geom.det_pos_init # (0, 1, 0) + (0, 1, 1) array([ 0., 2., 1.]) """ # Get transformation and translation parts from `init_matrix` init_matrix = np.asarray(init_matrix, dtype=float) if init_matrix.shape not in ((3, 3), (3, 4)): raise ValueError('`matrix` must have shape (3, 3) or (3, 4), ' 'got array with shape {}' ''.format(init_matrix.shape)) trafo_matrix = init_matrix[:, :3] translation = init_matrix[:, 3:].squeeze() # Transform the default vectors default_axis = cls._default_config['axis'] default_det_pos_init = cls._default_config['det_pos_init'] default_det_axes_init = cls._default_config['det_axes_init'] vecs_to_transform = (default_det_pos_init, ) + default_det_axes_init transformed_vecs = transform_system(default_axis, None, vecs_to_transform, matrix=trafo_matrix) # Use the standard constructor with these vectors axis, det_pos, det_axis_0, det_axis_1 = transformed_vecs if translation.size == 0: kwargs = {} else: kwargs = {'translation': translation} return cls(apart, dpart, axis, det_pos_init=det_pos, det_axes_init=[det_axis_0, det_axis_1], **kwargs)
def frommatrix(cls, apart, dpart, init_matrix): """Create an instance of `Parallel2dGeometry` using a matrix. This alternative constructor uses a matrix to rotate and translate the default configuration. It is most useful when the transformation to be applied is already given as a matrix. Parameters ---------- apart : 1-dim. `RectPartition` Partition of the angle interval. dpart : 1-dim. `RectPartition` Partition of the detector parameter interval. init_matrix : `array_like`, shape ``(2, 2)`` or ``(2, 3)``, optional Transformation matrix whose left ``(2, 2)`` block is multiplied with the default ``det_pos_init`` and ``det_axis_init`` to determine the new vectors. If present, the third column acts as a translation after the initial transformation. The resulting ``det_axis_init`` will be normalized. Returns ------- geometry : `Parallel2dGeometry` Examples -------- Mirror the second unit vector, creating a left-handed system: >>> apart = odl.uniform_partition(0, np.pi, 10) >>> dpart = odl.uniform_partition(-1, 1, 20) >>> matrix = np.array([[1, 0], ... [0, -1]]) >>> geom = Parallel2dGeometry.frommatrix(apart, dpart, matrix) >>> e_x, e_y = np.eye(2) # standard unit vectors >>> np.allclose(geom.det_pos_init, -e_y) True >>> np.allclose(geom.det_axis_init, e_x) True >>> np.allclose(geom.translation, (0, 0)) True Adding a translation with a third matrix column: >>> matrix = np.array([[1, 0, 1], ... [0, -1, 1]]) >>> geom = Parallel2dGeometry.frommatrix(apart, dpart, matrix) >>> np.allclose(geom.translation, (1, 1)) True >>> np.allclose(geom.det_pos_init, -e_y + (1, 1)) True """ # Get transformation and translation parts from `init_matrix` init_matrix = np.asarray(init_matrix, dtype=float) if init_matrix.shape not in ((2, 2), (2, 3)): raise ValueError('`matrix` must have shape (2, 2) or (2, 3), ' 'got array with shape {}' ''.format(init_matrix.shape)) trafo_matrix = init_matrix[:, :2] translation = init_matrix[:, 2:].squeeze() # Transform the default vectors default_det_pos_init = cls._default_config['det_pos_init'] default_det_axis_init = cls._default_config['det_axis_init'] vecs_to_transform = [default_det_axis_init] transformed_vecs = transform_system(default_det_pos_init, None, vecs_to_transform, matrix=trafo_matrix) # Use the standard constructor with these vectors det_pos, det_axis = transformed_vecs if translation.size == 0: kwargs = {} else: kwargs = {'translation': translation} return cls(apart, dpart, det_pos, det_axis_init=det_axis, **kwargs)
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))
def __init__(self, apart, dpart, det_pos_init=(0, 1), **kwargs): """Initialize a new instance. Parameters ---------- apart : 1-dim. `RectPartition` Partition of the angle interval. dpart : 1-dim. `RectPartition` Partition of the detector parameter interval. det_pos_init : `array-like`, shape ``(2,)``, optional Initial position of the detector reference point. Other Parameters ---------------- det_axis_init : `array-like` (shape ``(2,)``), optional Initial axis defining the detector orientation. The default depends on ``det_pos_init``, see Notes. translation : `array-like`, shape ``(2,)``, 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 center of rotation. Notes ----- In the default configuration, the initial detector reference point is ``(0, 1)``, and the initial detector axis is ``(1, 0)``. If a different ``det_pos_init`` is chosen, the new default axis is given as a rotation of the original one by a matrix that transforms ``(0, 1)`` to the new (normalized) ``det_pos_init``. This matrix is calculated with the `rotation_matrix_from_to` function. Expressed in code, we have :: init_rot = rotation_matrix_from_to((0, 1), det_pos_init) det_axis_init = init_rot.dot((1, 0)) If ``det_pos_init == (0, 0)``, no rotation is performed. Examples -------- Initialization with default parameters: >>> apart = odl.uniform_partition(0, np.pi, 10) >>> dpart = odl.uniform_partition(-1, 1, 20) >>> geom = Parallel2dGeometry(apart, dpart) >>> np.allclose(geom.det_refpoint(0), (0, 1)) True >>> np.allclose(geom.det_point_position(0, 1), (1, 1)) True Checking the default orientation: >>> e_x, e_y = np.eye(2) # standard unit vectors >>> np.allclose(geom.det_pos_init, e_y) True >>> np.allclose(geom.det_axis_init, e_x) True Specifying an initial detector position by default rotates the standard configuration to this position: >>> geom = Parallel2dGeometry(apart, dpart, det_pos_init=(-1, 0)) >>> np.allclose(geom.det_pos_init, -e_x) True >>> np.allclose(geom.det_axis_init, e_y) True >>> geom = Parallel2dGeometry(apart, dpart, det_pos_init=(0, -1)) >>> np.allclose(geom.det_pos_init, -e_y) True >>> np.allclose(geom.det_axis_init, -e_x) True The initial detector axis can also be set explicitly: >>> geom = Parallel2dGeometry( ... apart, dpart, det_pos_init=(0, -1), det_axis_init=(1, 0)) >>> np.allclose(geom.det_pos_init, -e_y) True >>> np.allclose(geom.det_axis_init, e_x) True """ default_det_pos_init = self._default_config['det_pos_init'] default_det_axis_init = self._default_config['det_axis_init'] # Handle the initial coordinate system. We need to assign `None` to # the vectors first in order to signalize to the `transform_system` # utility that they should be transformed from default since they # were not explicitly given. det_axis_init = kwargs.pop('det_axis_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_axis_init is not None: self._det_axis_init_arg = np.asarray(det_axis_init, dtype=float) else: self._det_axis_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_axis_init is None: vecs_to_transform.append(default_det_axis_init) transformed_vecs = transform_system(det_pos_init, default_det_pos_init, vecs_to_transform) transformed_vecs = list(transformed_vecs) det_pos_init = transformed_vecs.pop(0) if det_axis_init is None: det_axis_init = transformed_vecs.pop(0) assert transformed_vecs == [] # Translate the absolute vectors by the given translation translation = np.asarray(kwargs.pop('translation', (0, 0)), dtype=float) det_pos_init += translation # Initialize stuff. Normalization of the detector axis happens in # the detector class. detector = Flat1dDetector(part=dpart, axis=det_axis_init) super().__init__(ndim=2, apart=apart, detector=detector, det_pos_init=det_pos_init, translation=translation) if self.motion_partition.ndim != 1: raise ValueError('`apart` 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))