Source code for giant.calibration.estimators.alignment.temperature_dependent
from typing import Iterable, NamedTuple
import numpy as np
from giant.rotations import Rotation, euler_to_quaternion
from giant._typing import EULER_ORDERS
[docs]
class TemperatureDependentResults(NamedTuple):
"""
Named tuple to make the results clear
"""
order: EULER_ORDERS
"""
The order of the angles.
This is the same as the argument provided by the user and is included for awareness.
"""
angle_m_offset: float
"""
The estimated constant angle offset for the m (first) rotation axis in radians.
"""
angle_m_slope: float
"""
The estimated angle temperature slope for the m (first) rotation axis in radians.
"""
angle_n_offset: float
"""
The estimated constant angle offset for the n (second) rotation axis in radians.
"""
angle_n_slope: float
"""
The estimated angle temperature slope for the n (second) rotation axis in radians.
"""
angle_p_offset: float
"""
The estimated constant angle offset for the p (third) rotation axis in radians.
"""
angle_p_slope: float
"""
The estimated angle temperature slope for the p (third) rotation axis in radians.
"""
[docs]
def temperature_dependent_alignment_estimator(frame_1_rotations: Iterable[Rotation],
frame_2_rotations: Iterable[Rotation],
temperatures: Iterable[float],
order: EULER_ORDERS = 'xyz') -> TemperatureDependentResults:
r"""
This function estimates a temperature dependent attitude alignment between one frame and another.
The temperature dependent alignment is found by fitting linear temperature dependent euler angles (or
Tait-Bryan angles) to transform from the first frame to the second. That is
.. math::
\mathbf{T}_B=\mathbf{R}_m(\theta_m(t))\mathbf{R}_n(\theta_n(t))\mathbf{R}_p(\theta_p(t))\mathbf{T}_A
where :math:`\mathbf{T}_B` is the target frame, :math:`\mathbf{R}_i` is the rotation matrix about the :math:`i^{th}`
axis, :math:`\mathbf{T}_A` is the base frame, and :math:`\theta_i(t)` are the linear angles.
This fit is done in a least squares sense by computing the values for :math:`\theta_i(t)` across a range of
temperatures (by estimating the attitude for multiple single images) and then solving the system
.. math::
\left[\begin{array}{cc} 1 & t_1 \\ 1 & t_2 \\ \vdots & \vdots \\ 1 & t_n \end{array}\right]
\left[\begin{array}{ccc} \theta_{m0} & \theta_{n0} & \theta_{p0} \\
\theta_{m1} & \theta_{n1} & \theta_{p1}\end{array}\right] =
\left[\begin{array}{ccc}\vphantom{\theta}^0\theta_m &\vphantom{\theta}^0\theta_n &\vphantom{\theta}^0\theta_p\\
\vdots & \vdots & \vdots \\
\vphantom{\theta}^k\theta_m &\vphantom{\theta}^k\theta_n &\vphantom{\theta}^k\theta_p\end{array}\right]
where :math:`\vphantom{\theta}^k\theta_i` is the measured Euler/Tait-Bryan angle for the :math:`k^{th}` image.
Internally, we take each provided pair of rotations, compute the difference (`r2*r1.inv()`), convert the
difference to Euler/Tait-Bryan angles, and then perform the linear regression on the results (which yes, is
somewhat a case of castcading estimators...).
In general a user should not use this function and instead the
:meth:`.Calibration.estimate_temperature_dependent_alignment` should be used which handles the proper setup.
:param frame_1_rotations: the base frame rotations (normally common to base frame)
:param frame_2_rotations: the target frame rotations (normally common to target frame)
:param temperatures: the temperature of the image for each corresponding rotation
:param order: the order of the Euler/Tait-Bryan angles you want to use
"""
relative_euler_angles = []
# get the independent euler angles
for f1, f2 in zip(frame_1_rotations, frame_2_rotations):
relative_euler_angles.append(list((f2*f1.inv()).as_euler_angles(order=order)))
# make the coefficient matrix
temperatures = list(temperatures)
coef_mat = np.vstack([np.ones(len(temperatures)), temperatures]).T
# solve for the solution
solution = np.linalg.lstsq(coef_mat, relative_euler_angles)[0]
# return the solution
return TemperatureDependentResults(order, solution[0, 0], solution[1, 0], solution[0, 1], solution[1, 1], solution[0, 2], solution[1, 2])
[docs]
def evaluate_temperature_dependent_alignment(alignment: TemperatureDependentResults, temperature: float) -> Rotation:
"""
This function takes a fit temperature dependent alignment solution and evaluates what the
alignment rotation is at a specified temperature.
Essentially, we compute the euler angles specified by the alignment for the requested temperature
and then convert the angles into a :class:`.Rotation` object using the specified order.
"""
euler_angles = (np.array(list(alignment)[1:]).reshape(3, 2) @ [1, temperature]).ravel()
return Rotation(euler_to_quaternion(euler_angles, alignment.order))