Skip to content
Snippets Groups Projects
Commit d3fa55f9 authored by Cain, Payton David's avatar Cain, Payton David
Browse files

Refactor QRFactorization

parent 3c914e52
No related branches found
No related tags found
2 merge requests!146Release CMO metadata to production,!58Affine running process
...@@ -8,7 +8,7 @@ from .Transform import Transform ...@@ -8,7 +8,7 @@ from .Transform import Transform
class LeastSq(Transform): class LeastSq(Transform):
"""Intance of Transform. Applies least squares to generate matrices""" """Intance of Transform. Applies least squares to generate matrices"""
def get_stacked_values(self, absolutes, ordinates): def get_stacked_values(self, absolutes, ordinates, weights=None):
# LHS, or dependent variables # LHS, or dependent variables
# [A[0,0], A[1,0], A[2,0], A[0,1], A[1,1], A[2,1], ...] # [A[0,0], A[1,0], A[2,0], A[0,1], A[1,1], A[2,1], ...]
abs_stacked = self.get_stacked_absolutes(absolutes) abs_stacked = self.get_stacked_absolutes(absolutes)
...@@ -54,6 +54,11 @@ class LeastSq(Transform): ...@@ -54,6 +54,11 @@ class LeastSq(Transform):
return ord_stacked return ord_stacked
def valid(self, rank):
if rank < self.ndims:
return False
return True
def get_weighted_values( def get_weighted_values(
self, self,
values: Tuple[List[float], List[float], List[float]], values: Tuple[List[float], List[float], List[float]],
...@@ -83,17 +88,19 @@ class LeastSq(Transform): ...@@ -83,17 +88,19 @@ class LeastSq(Transform):
weights: List[float], weights: List[float],
) -> np.array: ) -> np.array:
"""Calculates affine with no constraints using least squares.""" """Calculates affine with no constraints using least squares."""
abs_stacked, ord_stacked = self.get_stacked_values(absolutes, ordinates) abs_stacked, ord_stacked = self.get_stacked_values(
absolutes, ordinates, weights
)
ord_stacked = self.get_weighted_values(ord_stacked, weights) ord_stacked = self.get_weighted_values(ord_stacked, weights)
abs_stacked = self.get_weighted_values(abs_stacked, weights) abs_stacked = self.get_weighted_values(abs_stacked, weights)
# regression matrix M that minimizes L2 norm # regression matrix M that minimizes L2 norm
matrix, res, rank, sigma = spl.lstsq(ord_stacked.T, abs_stacked.T) matrix, res, rank, sigma = spl.lstsq(ord_stacked.T, abs_stacked.T)
if rank < 3: if self.valid(rank):
print("Poorly conditioned or singular matrix, returning NaNs") return self.get_matrix(matrix, absolutes, ordinates, weights)
return np.nan * np.ones((4, 4)) print("Poorly conditioned or singular matrix, returning NaNs")
return self.format_matrix(matrix) return np.nan * np.ones((4, 4))
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return np.array( return np.array(
[ [
[matrix[0], matrix[1], matrix[2], matrix[3]], [matrix[0], matrix[1], matrix[2], matrix[3]],
......
...@@ -2,15 +2,17 @@ import numpy as np ...@@ -2,15 +2,17 @@ import numpy as np
import scipy.linalg as spl import scipy.linalg as spl
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from .LeastSq import LeastSq
from .SVD import SVD from .SVD import SVD
class QRFactorization(SVD): class QRFactorization(LeastSq):
"""Calculates affine using singular value decomposition with QR factorization""" """Calculates affine using singular value decomposition with QR factorization"""
ndims = 2 ndims = 2
svd = SVD(ndims=ndims)
def get_weighted_values_lstsq( def get_weighted_values(
self, self,
values: Tuple[List[float], List[float], List[float]], values: Tuple[List[float], List[float], List[float]],
weights: Optional[List[float]], weights: Optional[List[float]],
...@@ -19,57 +21,34 @@ class QRFactorization(SVD): ...@@ -19,57 +21,34 @@ class QRFactorization(SVD):
if weights is None: if weights is None:
return values return values
weights = np.sqrt(weights) weights = np.sqrt(weights)
return np.array( return np.array([values[i] * weights for i in range(self.ndims)])
[
values[0] * weights,
values[1] * weights,
]
)
def calculate(
self,
ordinates: Tuple[List[float], List[float], List[float]],
absolutes: Tuple[List[float], List[float], List[float]],
weights: List[float],
) -> np.array:
if weights is None: def get_stacked_values(self, absolutes, ordinates, weights):
weights = np.ones_like(ordinates[0]) weighted_absolutes = self.svd.get_weighted_values(
values=absolutes, weights=weights
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights) )
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights) weighted_ordinates = self.svd.get_weighted_values(
values=ordinates, weights=weights
)
# LHS, or dependent variables # LHS, or dependent variables
abs_stacked = self.get_stacked_values( abs_stacked = self.svd.get_stacked_values(
values=absolutes, values=absolutes,
weighted_values=weighted_absolutes, weighted_values=weighted_absolutes,
) )
# RHS, or independent variables # RHS, or independent variables
ord_stacked = self.get_stacked_values( ord_stacked = self.svd.get_stacked_values(
values=ordinates, values=ordinates,
weighted_values=weighted_ordinates, weighted_values=weighted_ordinates,
) )
return abs_stacked, ord_stacked
abs_stacked = self.get_weighted_values_lstsq( def get_matrix(self, matrix, absolutes, ordinates, weights):
values=abs_stacked,
weights=weights,
)
ord_stacked = self.get_weighted_values_lstsq(
values=ord_stacked,
weights=weights,
)
# regression matrix M that minimizes L2 norm
M_r, res, rank, sigma = spl.lstsq(ord_stacked.T, abs_stacked.T)
if rank < 2:
print("Poorly conditioned or singular matrix, returning NaNs")
return np.nan * np.ones((4, 4))
# QR fatorization # QR fatorization
# NOTE: forcing the diagonal elements of Q to be positive # NOTE: forcing the diagonal elements of Q to be positive
# ensures that the determinant is 1, not -1, and is # ensures that the determinant is 1, not -1, and is
# therefore a rotation, not a reflection # therefore a rotation, not a reflection
Q, R = np.linalg.qr(M_r.T) Q, R = np.linalg.qr(matrix.T)
neg = np.diag(Q) < 0 neg = np.diag(Q) < 0
Q[:, neg] = -1 * Q[:, neg] Q[:, neg] = -1 * Q[:, neg]
R[neg, :] = -1 * R[neg, :] R[neg, :] = -1 * R[neg, :]
...@@ -81,10 +60,11 @@ class QRFactorization(SVD): ...@@ -81,10 +60,11 @@ class QRFactorization(SVD):
# combine shear and rotation # combine shear and rotation
QH = np.dot(Q, H) QH = np.dot(Q, H)
weighted_absolutes = self.svd.get_weighted_values(absolutes, weights)
weighted_ordinates = self.svd.get_weighted_values(ordinates, weights)
# now get translation using weighted centroids and R # now get translation using weighted centroids and R
T = np.array([weighted_absolutes[0], weighted_absolutes[1]]) - np.dot( T = self.svd.get_translation_matrix(QH, weighted_absolutes, weighted_ordinates)
QH, [weighted_ordinates[0], weighted_ordinates[1]]
)
return [ return [
[QH[0, 0], QH[0, 1], 0.0, T[0]], [QH[0, 0], QH[0, 1], 0.0, T[0]],
...@@ -93,7 +73,8 @@ class QRFactorization(SVD): ...@@ -93,7 +73,8 @@ class QRFactorization(SVD):
0.0, 0.0,
0.0, 0.0,
1.0, 1.0,
np.array(weighted_absolutes[2]) - np.array(weighted_ordinates[2]), np.array(weighted_absolutes[self.ndims])
- np.array(weighted_ordinates[self.ndims]),
], ],
[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0],
] ]
...@@ -21,7 +21,7 @@ class Rescale3D(LeastSq): ...@@ -21,7 +21,7 @@ class Rescale3D(LeastSq):
ord_stacked[2, 2::3] = ordinates[2] ord_stacked[2, 2::3] = ordinates[2]
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[matrix[0], 0.0, 0.0, 0.0], [matrix[0], 0.0, 0.0, 0.0],
[0.0, matrix[1], 0.0, 0.0], [0.0, matrix[1], 0.0, 0.0],
......
...@@ -14,7 +14,7 @@ class RotationTranslationXY(SVD): ...@@ -14,7 +14,7 @@ class RotationTranslationXY(SVD):
def get_rotation_matrix(self, U, Vh): def get_rotation_matrix(self, U, Vh):
return np.dot(Vh.T, np.dot(np.diag([1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T)) return np.dot(Vh.T, np.dot(np.diag([1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T))
def format_matrix(self, R, T, weighted_absolutes, weighted_ordinates): def get_matrix(self, R, T, weighted_absolutes, weighted_ordinates):
return [ return [
[R[0, 0], R[0, 1], 0.0, T[0]], [R[0, 0], R[0, 1], 0.0, T[0]],
[R[1, 0], R[1, 1], 0.0, T[1]], [R[1, 0], R[1, 1], 0.0, T[1]],
......
...@@ -7,8 +7,6 @@ from .Transform import Transform ...@@ -7,8 +7,6 @@ from .Transform import Transform
class SVD(Transform): class SVD(Transform):
"""Instance of Transform. Applies singular value decomposition to generate matrices""" """Instance of Transform. Applies singular value decomposition to generate matrices"""
ndims = 3
def get_covariance_matrix(self, absolutes, ordinates, weights): def get_covariance_matrix(self, absolutes, ordinates, weights):
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights) weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights)
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights) weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights)
...@@ -67,6 +65,11 @@ class SVD(Transform): ...@@ -67,6 +65,11 @@ class SVD(Transform):
R, [weighted_ordinates[i] for i in range(self.ndims)] R, [weighted_ordinates[i] for i in range(self.ndims)]
) )
def valid(self, singular_values):
if np.sum(singular_values) < self.ndims:
return False
return True
def calculate( def calculate(
self, self,
ordinates: Tuple[List[float], List[float], List[float]], ordinates: Tuple[List[float], List[float], List[float]],
...@@ -80,21 +83,20 @@ class SVD(Transform): ...@@ -80,21 +83,20 @@ class SVD(Transform):
# Singular value decomposition, then rotation matrix from L&R eigenvectors # Singular value decomposition, then rotation matrix from L&R eigenvectors
# (the determinant guarantees a rotation, and not a reflection) # (the determinant guarantees a rotation, and not a reflection)
U, S, Vh = np.linalg.svd(H) U, S, Vh = np.linalg.svd(H)
if np.sum(S) < 3: if self.valid(S):
print("Poorly conditioned or singular matrix, returning NaNs") R = self.get_rotation_matrix(U, Vh)
return np.nan * np.ones((4, 4)) # now get translation using weighted centroids and R
R = self.get_rotation_matrix(U, Vh) T = self.get_translation_matrix(R, weighted_absolutes, weighted_ordinates)
# now get translation using weighted centroids and R return self.get_matrix(R, T, weighted_absolutes, weighted_ordinates)
T = self.get_translation_matrix(R, weighted_absolutes, weighted_ordinates) print("Poorly conditioned or singular matrix, returning NaNs")
return np.nan * np.ones((4, 4))
return self.format_matrix(R, T, weighted_absolutes, weighted_ordinates)
def format_matrix( def get_matrix(
self, self,
R, R,
T, T,
weighted_absolutes, weighted_absolutes=None,
weighted_ordinates, weighted_ordinates=None,
): ):
return [ return [
[R[0, 0], R[0, 1], R[0, 2], T[0]], [R[0, 0], R[0, 1], R[0, 2], T[0]],
......
...@@ -23,7 +23,7 @@ class ShearYZ(LeastSq): ...@@ -23,7 +23,7 @@ class ShearYZ(LeastSq):
ord_stacked[2, 2::3] = 1.0 ord_stacked[2, 2::3] = 1.0
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0],
[matrix[0], 1.0, 0.0, 0.0], [matrix[0], 1.0, 0.0, 0.0],
......
...@@ -16,6 +16,7 @@ class Transform(BaseModel): ...@@ -16,6 +16,7 @@ class Transform(BaseModel):
acausal: bool = False acausal: bool = False
memory: Optional[float] = None memory: Optional[float] = None
ndims = 3
def get_weights(self, times: UTCDateTime, time: int = None) -> List[float]: def get_weights(self, times: UTCDateTime, time: int = None) -> List[float]:
""" """
......
...@@ -8,7 +8,7 @@ from .LeastSq import LeastSq ...@@ -8,7 +8,7 @@ from .LeastSq import LeastSq
class TranslateOrigins(LeastSq): class TranslateOrigins(LeastSq):
"""Calculates affine using using least squares, constrained to tanslate origins""" """Calculates affine using using least squares, constrained to tanslate origins"""
def get_stacked_values(self, absolutes, ordinates): def get_stacked_values(self, absolutes, ordinates, weights=None):
# LHS, or dependent variables # LHS, or dependent variables
abs_stacked = self.get_stacked_absolutes(absolutes) abs_stacked = self.get_stacked_absolutes(absolutes)
# subtract ords from abs to force simple translation # subtract ords from abs to force simple translation
...@@ -40,7 +40,7 @@ class TranslateOrigins(LeastSq): ...@@ -40,7 +40,7 @@ class TranslateOrigins(LeastSq):
ord_stacked[2, 2::3] = 1.0 ord_stacked[2, 2::3] = 1.0
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[1.0, 0.0, 0.0, matrix[0]], [1.0, 0.0, 0.0, matrix[0]],
[0.0, 1.0, 0.0, matrix[1]], [0.0, 1.0, 0.0, matrix[1]],
......
...@@ -26,7 +26,7 @@ class ZRotationHscale(LeastSq): ...@@ -26,7 +26,7 @@ class ZRotationHscale(LeastSq):
ord_stacked[5, 2::3] = 1.0 ord_stacked[5, 2::3] = 1.0
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[matrix[0], matrix[1], 0.0, matrix[2]], [matrix[0], matrix[1], 0.0, matrix[2]],
[-matrix[1], matrix[0], 0.0, matrix[3]], [-matrix[1], matrix[0], 0.0, matrix[3]],
......
...@@ -9,7 +9,7 @@ class ZRotationHscaleZbaseline(LeastSq): ...@@ -9,7 +9,7 @@ class ZRotationHscaleZbaseline(LeastSq):
"""Calculates affine using least squares, constrained to rotate about the Z axis, """Calculates affine using least squares, constrained to rotate about the Z axis,
apply a uniform horizontal scaling, and apply a baseline shift for the Z axis.""" apply a uniform horizontal scaling, and apply a baseline shift for the Z axis."""
def get_stacked_values(self, absolutes, ordinates): def get_stacked_values(self, absolutes, ordinates, weights=None):
# LHS, or dependent variables # LHS, or dependent variables
abs_stacked = self.get_stacked_absolutes(absolutes) abs_stacked = self.get_stacked_absolutes(absolutes)
# subtract z_o from z_a to force simple z translation # subtract z_o from z_a to force simple z translation
...@@ -34,7 +34,7 @@ class ZRotationHscaleZbaseline(LeastSq): ...@@ -34,7 +34,7 @@ class ZRotationHscaleZbaseline(LeastSq):
ord_stacked[2, 2::3] = 1.0 ord_stacked[2, 2::3] = 1.0
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[matrix[0], matrix[1], 0.0, 0.0], [matrix[0], matrix[1], 0.0, 0.0],
[-matrix[1], matrix[0], 0.0, 0.0], [-matrix[1], matrix[0], 0.0, 0.0],
......
...@@ -22,7 +22,7 @@ class ZRotationShear(LeastSq): ...@@ -22,7 +22,7 @@ class ZRotationShear(LeastSq):
ord_stacked[7, 2::3] = 1.0 ord_stacked[7, 2::3] = 1.0
return ord_stacked return ord_stacked
def format_matrix(self, matrix): def get_matrix(self, matrix, absolutes=None, ordinates=None, weights=None):
return [ return [
[matrix[0], matrix[1], 0.0, matrix[2]], [matrix[0], matrix[1], 0.0, matrix[2]],
[matrix[3], matrix[4], 0.0, matrix[5]], [matrix[3], matrix[4], 0.0, matrix[5]],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment