- some bugfixes

This commit is contained in:
Wim Pomp committed 2026-10-03 17:57:14 +02:00
1 parent ef9e257f75
commit 4306c26690
5 files changed
+108 -39

No files matched your search

+1
View File
@@ -30,6 +30,7 @@ thiserror = "2"
tempfile = "3" tempfile = "3"
tiff = "0.11" tiff = "0.11"
tiffwrite = "2026.6.0" tiffwrite = "2026.6.0"
ndbioimage = { version = "0.2.0", default-features = false, features = ["czi"] }
[profile.release] [profile.release]
debug = true debug = true
+4
View File
@@ -13,4 +13,8 @@ pub enum Error {
/// the dimensionality of the data does not match /// the dimensionality of the data does not match
#[error("dimensionality mismatch: {0} != {0}")] #[error("dimensionality mismatch: {0} != {0}")]
DimensionalityMismatch(usize, usize), DimensionalityMismatch(usize, usize),
#[error("expected fixed_mu length of {0}, got {1}")]
FixedMuLength(usize, usize),
#[error("expected initial guess length of {0}, got {1}")]
InitialGuessLength(usize, usize),
} }
+7 -2
View File
@@ -11,6 +11,7 @@ use num::cast::AsPrimitive;
use rand::RngExt; use rand::RngExt;
use rayon::iter::IntoParallelIterator; use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use serde::{Deserialize, Serialize};
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
@@ -71,6 +72,10 @@ impl FixedMu {
.filter_map(|(i, j)| if i.is_none() { Some(*j) } else { None }) .filter_map(|(i, j)| if i.is_none() { Some(*j) } else { None })
.collect() .collect()
} }
pub fn len_variable(&self) -> usize {
self.len_variable
}
} }
/// first_bin, last_bin: center of bin /// first_bin, last_bin: center of bin
@@ -178,7 +183,7 @@ impl Sampling {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum SamplingArg { pub enum SamplingArg {
Fixed(usize), Fixed(usize),
FixedAt(Vec<Vec<f64>>), FixedAt(Vec<Vec<f64>>),
@@ -452,7 +457,7 @@ where
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Sigma { pub enum Sigma {
None, None,
Absolute(Vec<f64>), Absolute(Vec<f64>),
+52 -33
View File
@@ -6,10 +6,11 @@ use crate::transform::Transform;
use algos::{ObjectiveFunction, OptimizationConfig}; use algos::{ObjectiveFunction, OptimizationConfig};
use ndarray::{AsArray, Dimension}; use ndarray::{AsArray, Dimension};
use num::cast::AsPrimitive; use num::cast::AsPrimitive;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
/// Optimizer type for registration /// Optimizer type for registration
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub enum Optimizer { pub enum Optimizer {
/// L-BFGS optimizer (fast, requires consistent gradients) /// L-BFGS optimizer (fast, requires consistent gradients)
#[default] #[default]
@@ -18,7 +19,7 @@ pub enum Optimizer {
ASGD, ASGD,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegistrationStep { pub struct RegistrationStep {
pub sigma: Sigma, pub sigma: Sigma,
pub samples: SamplingArg, pub samples: SamplingArg,
@@ -32,6 +33,7 @@ pub struct RegistrationStep {
} }
impl RegistrationStep { impl RegistrationStep {
#[expect(clippy::too_many_arguments)]
pub fn new( pub fn new(
sigma: Sigma, sigma: Sigma,
samples: SamplingArg, samples: SamplingArg,
@@ -40,6 +42,7 @@ impl RegistrationStep {
edge: f64, edge: f64,
max_iterations: usize, max_iterations: usize,
learning_rate: f64, learning_rate: f64,
optimizer: Optimizer,
) -> Self { ) -> Self {
Self { Self {
sigma, sigma,
@@ -49,7 +52,7 @@ impl RegistrationStep {
edge, edge,
max_iterations, max_iterations,
learning_rate, learning_rate,
optimizer: Optimizer::default(), optimizer,
} }
} }
@@ -134,13 +137,20 @@ pub struct Registration<D: Dimension> {
} }
impl<D: Dimension> Registration<D> { impl<D: Dimension> Registration<D> {
pub fn new<F: Into<FixedMu>>(fixed_mu: F) -> Self { pub fn new<F: Into<FixedMu>>(fixed_mu: F) -> Result<Self, Error> {
Self { let fixed_mu = fixed_mu.into();
fixed_mu: fixed_mu.into(), if let Some(ndim) = D::NDIM
steps: None, && fixed_mu.len() != (ndim * ndim + ndim)
initial_guess: None, {
optimizer: Optimizer::default(), Err(Error::FixedMuLength(ndim * ndim + ndim, fixed_mu.len()))
dimension: PhantomData, } else {
Ok(Self {
fixed_mu,
steps: None,
initial_guess: None,
optimizer: Optimizer::default(),
dimension: PhantomData,
})
} }
} }
@@ -184,9 +194,16 @@ impl<D: Dimension> Registration<D> {
self self
} }
pub fn with_initial_guess(mut self, initial_guess: Vec<f64>) -> Self { pub fn with_initial_guess(mut self, initial_guess: Vec<f64>) -> Result<Self, Error> {
self.initial_guess = Some(initial_guess); if self.fixed_mu.len_variable() != initial_guess.len() {
self Err(Error::InitialGuessLength(
self.fixed_mu.len(),
initial_guess.len(),
))
} else {
self.initial_guess = Some(initial_guess);
Ok(self)
}
} }
pub fn with_optimizer(mut self, optimizer: Optimizer) -> Self { pub fn with_optimizer(mut self, optimizer: Optimizer) -> Self {
@@ -198,8 +215,16 @@ impl<D: Dimension> Registration<D> {
self.steps = Some(steps); self.steps = Some(steps);
} }
pub fn set_initial_guess(&mut self, initial_guess: Vec<f64>) { pub fn set_initial_guess(&mut self, initial_guess: Vec<f64>) -> Result<(), Error> {
self.initial_guess = Some(initial_guess); if self.fixed_mu.len_variable() != initial_guess.len() {
Err(Error::InitialGuessLength(
self.fixed_mu.len(),
initial_guess.len(),
))
} else {
self.initial_guess = Some(initial_guess);
Ok(())
}
} }
pub fn set_optimizer(&mut self, optimizer: Optimizer) { pub fn set_optimizer(&mut self, optimizer: Optimizer) {
@@ -243,8 +268,7 @@ impl<D: Dimension> Registration<D> {
scales scales
} }
/// find the transform which transforms moving into fixed and return the results of each /// find the transform which transforms moving into fixed
/// optimization step
pub fn register<'a, F, M, T>(&self, fixed: F, moving: M) -> Result<Transform<D>, Error> pub fn register<'a, F, M, T>(&self, fixed: F, moving: M) -> Result<Transform<D>, Error>
where where
F: AsArray<'a, T, D>, F: AsArray<'a, T, D>,
@@ -261,7 +285,7 @@ impl<D: Dimension> Registration<D> {
.cloned() .cloned()
.unwrap_or_else(|| RegistrationStep::default_steps(ndim, n)); .unwrap_or_else(|| RegistrationStep::default_steps(ndim, n));
let mut p = self.initial_guess.as_ref().cloned().unwrap_or_else(|| { let mut p_var = self.initial_guess.as_ref().cloned().unwrap_or_else(|| {
Self::compute_initial_transform(fixed.shape(), moving.shape(), ndim) Self::compute_initial_transform(fixed.shape(), moving.shape(), ndim)
}); });
@@ -290,8 +314,6 @@ impl<D: Dimension> Registration<D> {
let bm = BSpline::<3, _>::new(m.view()); let bm = BSpline::<3, _>::new(m.view());
let metric = MattesMetric::new(bf, bm, samples, n_bins, edge)? let metric = MattesMetric::new(bf, bm, samples, n_bins, edge)?
.with_fixed_mu(self.fixed_mu.clone()); .with_fixed_mu(self.fixed_mu.clone());
let p_var = metric.fixed_mu().extract_variable(&p);
let optimization_result = Self::optimize_metric( let optimization_result = Self::optimize_metric(
&metric, &metric,
&p_var, &p_var,
@@ -305,10 +327,13 @@ impl<D: Dimension> Registration<D> {
if let Some(result) = optimization_result if let Some(result) = optimization_result
&& result.optimal_point.iter().all(|i| i.is_finite()) && result.optimal_point.iter().all(|i| i.is_finite())
{ {
p = self.fixed_mu.combine(&result.optimal_point); p_var = result.optimal_point;
} }
} }
Ok(Transform::<D>::new(p, fixed.shape().to_vec())) Ok(Transform::<D>::new(
self.fixed_mu.combine(p_var.as_slice()),
fixed.shape().to_vec(),
))
} }
fn optimize_metric<M: ObjectiveFunction<f64>>( fn optimize_metric<M: ObjectiveFunction<f64>>(
@@ -366,9 +391,10 @@ impl<D: Dimension> Registration<D> {
.cloned() .cloned()
.unwrap_or_else(|| RegistrationStep::default_steps(ndim, n)); .unwrap_or_else(|| RegistrationStep::default_steps(ndim, n));
let mut p = self.initial_guess.as_ref().cloned().unwrap_or_else(|| { let mut p_var = self.initial_guess.as_ref().cloned().unwrap_or_else(|| {
Self::compute_initial_transform(fixed.shape(), moving.shape(), ndim) Self::compute_initial_transform(fixed.shape(), moving.shape(), ndim)
}); });
let mut p = vec![0.0; n * n + n];
let mut registration_results = Vec::new(); let mut registration_results = Vec::new();
for RegistrationStep { for RegistrationStep {
@@ -412,11 +438,7 @@ impl<D: Dimension> Registration<D> {
tolerance, tolerance,
learning_rate, learning_rate,
}; };
lbfgs_minimize( lbfgs_minimize(&metric, &p_var, &config)
&metric,
metric.fixed_mu().extract_variable(&p).as_slice(),
&config,
)
} }
Optimizer::ASGD => { Optimizer::ASGD => {
let config = AsgdConfig { let config = AsgdConfig {
@@ -428,11 +450,7 @@ impl<D: Dimension> Registration<D> {
scales: Some(scales), scales: Some(scales),
..Default::default() ..Default::default()
}; };
asgd_minimize( asgd_minimize(&metric, &p_var, &config)
&metric,
metric.fixed_mu().extract_variable(&p).as_slice(),
&config,
)
} }
}; };
@@ -441,6 +459,7 @@ impl<D: Dimension> Registration<D> {
.iter() .iter()
.all(|i| i.is_finite()) .all(|i| i.is_finite())
{ {
p_var = optimization_result.optimal_point.clone();
p = metric p = metric
.fixed_mu() .fixed_mu()
.combine(&optimization_result.optimal_point); .combine(&optimization_result.optimal_point);
+44 -4
View File
@@ -100,9 +100,32 @@ fn matrix_inverse(matrix: Array2<f64>) -> Array2<f64> {
inv inv
} }
#[derive(Deserialize)]
struct TransformS {
parameters: Vec<f64>,
dparameters: Vec<f64>,
center: Vec<f64>,
shape: Vec<usize>,
}
impl<D: Dimension> From<TransformS> for Transform<D> {
fn from(t: TransformS) -> Self {
let ndim = t.shape.len();
Self {
parameters: t.parameters,
dparameters: t.dparameters,
center: t.center,
shape: t.shape,
ndim,
dimension: PhantomData::default(),
}
}
}
/// a struct describing the transform /// a struct describing the transform
/// generic parameter N = # image dimensions /// generic parameter N = # image dimensions
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(from = "TransformS")]
pub struct Transform<D: Dimension = Ix2> { pub struct Transform<D: Dimension = Ix2> {
/// flattened NxN rotation matrix + N translation parameters /// flattened NxN rotation matrix + N translation parameters
pub parameters: Vec<f64>, pub parameters: Vec<f64>,
@@ -112,7 +135,9 @@ pub struct Transform<D: Dimension = Ix2> {
pub center: Vec<f64>, pub center: Vec<f64>,
/// the shape of images for which this transform is meant /// the shape of images for which this transform is meant
pub shape: Vec<usize>, pub shape: Vec<usize>,
#[serde(skip)]
ndim: usize, ndim: usize,
#[serde(skip)]
dimension: PhantomData<D>, dimension: PhantomData<D>,
} }
@@ -463,12 +488,12 @@ impl<D: Dimension> Transform<D> {
T: 'a + Clone + AsPrimitive<f64>, T: 'a + Clone + AsPrimitive<f64>,
G: Into<FixedMu>, G: Into<FixedMu>,
{ {
let mut registration = Registration::new(fixed_mu); let mut registration = Registration::new(fixed_mu)?;
if let Some(steps) = steps { if let Some(steps) = steps {
registration.set_steps(steps); registration.set_steps(steps);
} }
if let Some(initial_guess) = initial_guess { if let Some(initial_guess) = initial_guess {
registration.set_initial_guess(initial_guess); registration.set_initial_guess(initial_guess)?;
} }
registration.register(fixed, moving) registration.register(fixed, moving)
} }
@@ -488,12 +513,12 @@ impl<D: Dimension> Transform<D> {
T: 'a + Clone + AsPrimitive<f64>, T: 'a + Clone + AsPrimitive<f64>,
G: Into<FixedMu>, G: Into<FixedMu>,
{ {
let mut registration = Registration::new(fixed_mu); let mut registration = Registration::new(fixed_mu)?;
if let Some(steps) = steps { if let Some(steps) = steps {
registration.set_steps(steps); registration.set_steps(steps);
} }
if let Some(initial_guess) = initial_guess { if let Some(initial_guess) = initial_guess {
registration.set_initial_guess(initial_guess); registration.set_initial_guess(initial_guess)?;
} }
registration.register_debug(fixed, moving) registration.register_debug(fixed, moving)
} }
@@ -571,6 +596,11 @@ impl<D: Dimension> Transform<D> {
Self::from_scaling(scaling) * self Self::from_scaling(scaling) * self
} }
pub fn with_center(mut self, center: Vec<f64>) -> Self {
self.center = center;
self
}
/// read a transform from a file /// read a transform from a file
pub fn from_file(path: PathBuf) -> Result<Self, Error> { pub fn from_file(path: PathBuf) -> Result<Self, Error> {
let file = File::open(path)?; let file = File::open(path)?;
@@ -677,6 +707,16 @@ impl<D: Dimension> Transform<D> {
matrix matrix
} }
pub fn with_parameters(mut self, parameters: Vec<f64>) -> Self {
self.parameters = parameters;
self
}
pub fn with_dparameters(mut self, dparameters: Vec<f64>) -> Self {
self.dparameters = dparameters;
self
}
pub fn with_matrix(mut self, matrix: ArrayView2<f64>) -> Self { pub fn with_matrix(mut self, matrix: ArrayView2<f64>) -> Self {
let mut parameters = matrix let mut parameters = matrix
.slice(s![..self.ndim, ..self.ndim]) .slice(s![..self.ndim, ..self.ndim])