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

Refactor SVD transforms

parent 08ab7ba2
No related branches found
No related tags found
2 merge requests!146Release CMO metadata to production,!58Affine running process
...@@ -238,7 +238,7 @@ ...@@ -238,7 +238,7 @@
1 1
] ]
], ],
"RotationTranslation3D": [ "SVD": [
[ [
0.8100811050998874, 0.8100811050998874,
-0.3030939677547838, -0.3030939677547838,
......
...@@ -8,6 +8,8 @@ from .SVD import SVD ...@@ -8,6 +8,8 @@ from .SVD import SVD
class QRFactorization(SVD): class QRFactorization(SVD):
"""Calculates affine using singular value decomposition with QR factorization""" """Calculates affine using singular value decomposition with QR factorization"""
ndims = 2
def get_weighted_values_lstsq( def get_weighted_values_lstsq(
self, self,
values: Tuple[List[float], List[float], List[float]], values: Tuple[List[float], List[float], List[float]],
...@@ -40,14 +42,12 @@ class QRFactorization(SVD): ...@@ -40,14 +42,12 @@ class QRFactorization(SVD):
abs_stacked = self.get_stacked_values( abs_stacked = self.get_stacked_values(
values=absolutes, values=absolutes,
weighted_values=weighted_absolutes, weighted_values=weighted_absolutes,
ndims=2,
) )
# RHS, or independent variables # RHS, or independent variables
ord_stacked = self.get_stacked_values( ord_stacked = self.get_stacked_values(
values=ordinates, values=ordinates,
weighted_values=weighted_ordinates, weighted_values=weighted_ordinates,
ndims=2,
) )
abs_stacked = self.get_weighted_values_lstsq( abs_stacked = self.get_weighted_values_lstsq(
......
import numpy as np
import scipy.linalg as spl
from typing import List, Tuple
from .SVD import SVD
class RotationTranslation3D(SVD):
"""Calculates affine using using singular value decomposition,
constrained to 3D scaled rotation and translation(no shear)"""
def calculate(
self,
ordinates: Tuple[List[float], List[float], List[float]],
absolutes: Tuple[List[float], List[float], List[float]],
weights: List[float],
) -> np.array:
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights)
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights)
# generate weighted "covariance" matrix
H = np.dot(
self.get_stacked_values(
values=ordinates,
weighted_values=weighted_ordinates,
ndims=3,
),
np.dot(
np.diag(weights),
self.get_stacked_values(
values=absolutes,
weighted_values=weighted_absolutes,
ndims=3,
).T,
),
)
# Singular value decomposition, then rotation matrix from L&R eigenvectors
# (the determinant guarantees a rotation, and not a reflection)
U, S, Vh = np.linalg.svd(H)
if np.sum(S) < 3:
print("Poorly conditioned or singular matrix, returning NaNs")
return np.nan * np.ones((4, 4))
R = np.dot(Vh.T, np.dot(np.diag([1, 1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T))
# now get translation using weighted centroids and R
T = np.array(
[weighted_absolutes[0], weighted_absolutes[1], weighted_absolutes[2]]
) - np.dot(
R, [weighted_ordinates[0], weighted_ordinates[1], weighted_ordinates[2]]
)
return [
[R[0, 0], R[0, 1], R[0, 2], T[0]],
[R[1, 0], R[1, 1], R[1, 2], T[1]],
[R[2, 0], R[2, 1], R[2, 2], T[2]],
[0.0, 0.0, 0.0, 1.0],
]
...@@ -9,47 +9,12 @@ class RotationTranslationXY(SVD): ...@@ -9,47 +9,12 @@ class RotationTranslationXY(SVD):
constrained to rotation and translation in XY(no scale or shear), constrained to rotation and translation in XY(no scale or shear),
and only translation in Z""" and only translation in Z"""
def calculate( ndims = 2
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:
weights = np.ones_like(ordinates[0])
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights)
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights)
# generate weighted "covariance" matrix
H = np.dot(
self.get_stacked_values(
values=ordinates,
weighted_values=weighted_ordinates,
ndims=2,
),
np.dot(
np.diag(weights),
self.get_stacked_values(
values=absolutes,
weighted_values=weighted_absolutes,
ndims=2,
).T,
),
)
# Singular value decomposition, then rotation matrix from L&R eigenvectors def get_rotation_matrix(self, U, Vh):
# (the determinant guarantees a rotation, and not a reflection) return np.dot(Vh.T, np.dot(np.diag([1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T))
U, S, Vh = np.linalg.svd(H)
if np.sum(S) < 2:
print("Poorly conditioned or singular matrix, returning NaNs")
return np.nan * np.ones((4, 4))
R = np.dot(Vh.T, np.dot(np.diag([1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T))
# now get translation using weighted centroids and R
T = np.array([weighted_absolutes[0], weighted_absolutes[1]]) - np.dot(
R, [weighted_ordinates[0], weighted_ordinates[1]]
)
def format_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]],
...@@ -57,7 +22,8 @@ class RotationTranslationXY(SVD): ...@@ -57,7 +22,8 @@ class RotationTranslationXY(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],
] ]
...@@ -7,7 +7,28 @@ from .Transform import Transform ...@@ -7,7 +7,28 @@ 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"""
def get_stacked_values(self, values, weighted_values, ndims=3) -> np.array: ndims = 3
def get_covariance_matrix(self, absolutes, ordinates, weights):
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights)
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights)
# generate weighted "covariance" matrix
H = np.dot(
self.get_stacked_values(
values=ordinates,
weighted_values=weighted_ordinates,
),
np.dot(
np.diag(weights),
self.get_stacked_values(
values=absolutes,
weighted_values=weighted_absolutes,
).T,
),
)
return H
def get_stacked_values(self, values, weighted_values) -> np.array:
"""Supports intermediate mathematical steps by differencing and shaping values for SVD """Supports intermediate mathematical steps by differencing and shaping values for SVD
Attributes Attributes
...@@ -20,7 +41,7 @@ class SVD(Transform): ...@@ -20,7 +41,7 @@ class SVD(Transform):
------- -------
Stacked and differenced values from their weighted counterparts Stacked and differenced values from their weighted counterparts
""" """
return np.vstack([[values[i] - weighted_values[i]] for i in range(ndims)]) return np.vstack([[values[i] - weighted_values[i]] for i in range(self.ndims)])
def get_weighted_values( def get_weighted_values(
self, self,
...@@ -35,3 +56,49 @@ class SVD(Transform): ...@@ -35,3 +56,49 @@ class SVD(Transform):
np.average(values[1], weights=weights), np.average(values[1], weights=weights),
np.average(values[2], weights=weights), np.average(values[2], weights=weights),
) )
def get_rotation_matrix(self, U, Vh):
return np.dot(
Vh.T, np.dot(np.diag([1, 1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T)
)
def get_translation_matrix(self, R, weighted_absolutes, weighted_ordinates):
return np.array([weighted_absolutes[i] for i in range(self.ndims)]) - np.dot(
R, [weighted_ordinates[i] for i in range(self.ndims)]
)
def calculate(
self,
ordinates: Tuple[List[float], List[float], List[float]],
absolutes: Tuple[List[float], List[float], List[float]],
weights: List[float],
) -> np.array:
weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights)
weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights)
# generate weighted "covariance" matrix
H = self.get_covariance_matrix(absolutes, ordinates, weights)
# Singular value decomposition, then rotation matrix from L&R eigenvectors
# (the determinant guarantees a rotation, and not a reflection)
U, S, Vh = np.linalg.svd(H)
if np.sum(S) < 3:
print("Poorly conditioned or singular matrix, returning NaNs")
return np.nan * np.ones((4, 4))
R = self.get_rotation_matrix(U, Vh)
# now get translation using weighted centroids and R
T = self.get_translation_matrix(R, weighted_absolutes, weighted_ordinates)
return self.format_matrix(R, T, weighted_absolutes, weighted_ordinates)
def format_matrix(
self,
R,
T,
weighted_absolutes,
weighted_ordinates,
):
return [
[R[0, 0], R[0, 1], R[0, 2], T[0]],
[R[1, 0], R[1, 1], R[1, 2], T[1]],
[R[2, 0], R[2, 1], R[2, 2], T[2]],
[0.0, 0.0, 0.0, 1.0],
]
from .LeastSq import LeastSq from .LeastSq import LeastSq
from .QRFactorization import QRFactorization from .QRFactorization import QRFactorization
from .Rescale3D import Rescale3D from .Rescale3D import Rescale3D
from .RotationTranslation3D import RotationTranslation3D
from .RotationTranslationXY import RotationTranslationXY from .RotationTranslationXY import RotationTranslationXY
from .ShearYZ import ShearYZ from .ShearYZ import ShearYZ
from .Transform import Transform from .Transform import Transform
from .TranslateOrigins import TranslateOrigins from .TranslateOrigins import TranslateOrigins
from .SVD import SVD
from .ZRotationHScale import ZRotationHscale from .ZRotationHScale import ZRotationHscale
from .ZRotationHScaleZBaseline import ZRotationHscaleZbaseline from .ZRotationHScaleZBaseline import ZRotationHscaleZbaseline
from .ZRotationShear import ZRotationShear from .ZRotationShear import ZRotationShear
...@@ -19,6 +19,7 @@ __all__ = [ ...@@ -19,6 +19,7 @@ __all__ = [
"ShearYZ", "ShearYZ",
"Transform", "Transform",
"TranslateOrigins", "TranslateOrigins",
"SVD",
"ZRotationHscale", "ZRotationHscale",
"ZRotationHscaleZbaseline", "ZRotationHscaleZbaseline",
"ZRotationShear", "ZRotationShear",
......
...@@ -10,7 +10,7 @@ from geomagio.adjusted.transform import ( ...@@ -10,7 +10,7 @@ from geomagio.adjusted.transform import (
LeastSq, LeastSq,
QRFactorization, QRFactorization,
Rescale3D, Rescale3D,
RotationTranslation3D, SVD,
RotationTranslationXY, RotationTranslationXY,
ShearYZ, ShearYZ,
TranslateOrigins, TranslateOrigins,
...@@ -79,15 +79,15 @@ def test_Rescale3D_synthetic(): ...@@ -79,15 +79,15 @@ def test_Rescale3D_synthetic():
) )
def test_RotationTranslation3D_synthetic(): def test_SVD_synthetic():
ordinates, absolutes, weights = get_sythetic_variables() ordinates, absolutes, weights = get_sythetic_variables()
assert_array_almost_equal( assert_array_almost_equal(
RotationTranslation3D().calculate( SVD().calculate(
ordinates=ordinates, ordinates=ordinates,
absolutes=absolutes, absolutes=absolutes,
weights=weights, weights=weights,
), ),
get_expected_synthetic_result("RotationTranslation3D"), get_expected_synthetic_result("SVD"),
decimal=3, decimal=3,
) )
......
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