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
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
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)
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
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
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
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
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
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
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