- fix default value
- implement dparameter estimation
This commit is contained in:
1 parent
9257dd1fcf
commit
0049267997
7 files changed
+672
-73
No files matched your search
+34
-16
@@ -48,11 +48,18 @@ where
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Calculate the inverse of a square matrix using LU factorization with partial pivoting.
|
||||
fn matrix_inverse(matrix: Array2<f64>) -> Array2<f64> {
|
||||
/// Calculate the inverse of a square matrix using LU factorization with partial
|
||||
/// pivoting.
|
||||
///
|
||||
/// Returns `None` if the matrix is singular, or numerically rank-deficient.
|
||||
pub(crate) fn try_matrix_inverse(matrix: Array2<f64>) -> Option<Array2<f64>> {
|
||||
let n = matrix.nrows();
|
||||
debug_assert_eq!(n, matrix.ncols(), "matrix must be square");
|
||||
|
||||
// pivot magnitudes must exceed this relative threshold to count as significant
|
||||
let max_abs = matrix.iter().fold(0.0f64, |m, &x| m.max(x.abs()));
|
||||
let threshold = max_abs * (n as f64) * f64::EPSILON;
|
||||
|
||||
// PA = LU factorization (Doolittle with partial pivoting)
|
||||
let mut lu = matrix;
|
||||
let mut perm: Vec<usize> = (0..n).collect();
|
||||
@@ -64,7 +71,9 @@ fn matrix_inverse(matrix: Array2<f64>) -> Array2<f64> {
|
||||
pivot_row = i;
|
||||
}
|
||||
}
|
||||
assert_ne!(lu[[pivot_row, k]], 0.0, "matrix is singular");
|
||||
if lu[[pivot_row, k]].abs() <= threshold || !lu[[pivot_row, k]].is_finite() {
|
||||
return None;
|
||||
}
|
||||
if pivot_row != k {
|
||||
for j in 0..n {
|
||||
lu.swap([k, j], [pivot_row, j]);
|
||||
@@ -102,7 +111,7 @@ fn matrix_inverse(matrix: Array2<f64>) -> Array2<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
inv
|
||||
Some(inv)
|
||||
}
|
||||
|
||||
/// a struct describing the transform
|
||||
@@ -403,8 +412,8 @@ impl<D: Dimension> Transform<D> {
|
||||
}
|
||||
|
||||
pub fn new_around_center(parameters: Vec<f64>, center: &[f64]) -> Self {
|
||||
Self::new(parameters).with_translation(¢er.iter().map(|i| -i).collect::<Vec<_>>())
|
||||
* Self::from_translation(center)
|
||||
Self::new(parameters).with_translation(center)
|
||||
* Self::from_translation(¢er.iter().map(|i| -i).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// find the affine transform which transforms moving into fixed
|
||||
@@ -796,7 +805,7 @@ impl<D: Dimension> Transform<D> {
|
||||
/// get the inverse transform
|
||||
pub fn inverse(&self) -> Result<Self, Error> {
|
||||
let matrix = self.matrix();
|
||||
let inverse = matrix_inverse(matrix);
|
||||
let inverse = try_matrix_inverse(matrix).ok_or(Error::SingularMatrix)?;
|
||||
let parameters = inverse
|
||||
.slice(s![..self.ndim(), ..self.ndim()])
|
||||
.iter()
|
||||
@@ -811,7 +820,11 @@ impl<D: Dimension> Transform<D> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn interpolate<'a, const B: usize, T, A>(&self, image: A, default_value: &DefaultValue) -> Result<Array<f64, D>, Error>
|
||||
pub fn interpolate<'a, const B: usize, T, A>(
|
||||
&self,
|
||||
image: A,
|
||||
default_value: &DefaultValue,
|
||||
) -> Result<Array<f64, D>, Error>
|
||||
where
|
||||
A: AsArray<'a, T, D>,
|
||||
D: Dimension,
|
||||
@@ -994,7 +1007,7 @@ impl Transform<Ix2> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::matrix_inverse;
|
||||
use super::try_matrix_inverse;
|
||||
use crate::julia_image;
|
||||
use crate::transform::File;
|
||||
use crate::transform::Transform;
|
||||
@@ -1011,7 +1024,7 @@ mod tests {
|
||||
|
||||
let a = array![[4.0, 7.0], [2.0, 6.0]];
|
||||
let expected = array![[0.6, -0.7], [-0.2, 0.4]];
|
||||
let got = matrix_inverse(a.clone());
|
||||
let got = try_matrix_inverse(a.clone()).expect("matrix should not be singular");
|
||||
assert!(
|
||||
got.iter()
|
||||
.zip_eq(expected.iter())
|
||||
@@ -1020,7 +1033,7 @@ mod tests {
|
||||
|
||||
let b = array![[0.0, 2.0], [3.0, 4.0]];
|
||||
let expected = array![[-2.0 / 3.0, 1.0 / 3.0], [0.5, 0.0]];
|
||||
let got = matrix_inverse(b.clone());
|
||||
let got = try_matrix_inverse(b.clone()).expect("matrix should not be singular");
|
||||
assert!(
|
||||
got.iter()
|
||||
.zip_eq(expected.iter())
|
||||
@@ -1029,13 +1042,16 @@ mod tests {
|
||||
|
||||
let c = array![[1.0, 2.0, 3.0], [0.0, 1.0, 4.0], [5.0, 6.0, 0.0]];
|
||||
let reference = array![[-24.0, 18.0, 5.0], [20.0, -15.0, -4.0], [-5.0, 4.0, 1.0]];
|
||||
let got = matrix_inverse(c);
|
||||
let got = try_matrix_inverse(c).expect("matrix should not be singular");
|
||||
assert!(
|
||||
got.iter()
|
||||
.zip_eq(reference.iter())
|
||||
.all(|(x, y)| (x - y).abs() < 1e-10)
|
||||
);
|
||||
|
||||
let singular = array![[1.0, 2.0], [2.0, 4.0]];
|
||||
assert!(try_matrix_inverse(singular).is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1186,7 +1202,8 @@ mod tests {
|
||||
)
|
||||
.slice(s![.., 0])
|
||||
.mapv(|i| i as f64);
|
||||
let im_b = Transform::new(vec![0.85, 4.0]).interpolate::<1, _, _>(&im_a, &Default::default())?;
|
||||
let im_b =
|
||||
Transform::new(vec![0.85, 4.0]).interpolate::<1, _, _>(&im_a, &Default::default())?;
|
||||
|
||||
// Smooth both images with sigma=8 like level 0
|
||||
let sigma = Sigma::Absolute(vec![8.0]);
|
||||
@@ -1295,7 +1312,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let q = vec![0.85, 2.0];
|
||||
let moving = Transform::new(q.clone()).interpolate::<3, _, _>(&fixed, &Default::default())?;
|
||||
let moving =
|
||||
Transform::new(q.clone()).interpolate::<3, _, _>(&fixed, &Default::default())?;
|
||||
|
||||
// The registration finds T such that im_b(T(x)) = im_a(x), which is the inverse of q
|
||||
let q_inv = Transform::<ndarray::Ix1>::new(q.clone()).inverse()?;
|
||||
@@ -1520,11 +1538,11 @@ mod tests {
|
||||
let identity = vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
|
||||
let edge = 0.01;
|
||||
|
||||
// Test at full resolution with different sigma levels (matching elastix approach)
|
||||
// Test at full resolution with different sigma levels (matching Elastix approach)
|
||||
// Elastix does NOT downsample — it smooths at full resolution with
|
||||
// sigma = 0.5 * factor where factor is from the pyramid schedule [8, 4, 2, 1]
|
||||
println!(
|
||||
"=== Full resolution (no downsampling, matching elastix FixedSmoothingImagePyramid) ==="
|
||||
"=== Full resolution (no downsampling, matching Elastix FixedSmoothingImagePyramid) ==="
|
||||
);
|
||||
for (level, sigma_val) in [4.0, 2.0, 1.0, 0.5].iter().enumerate() {
|
||||
let f = gaussian_smooth(im_a.view(), &[*sigma_val; 2])?;
|
||||
|
||||
Reference in new issue
Block a user