From f0404d0419a67ff7726da025fb52a65c519a914a Mon Sep 17 00:00:00 2001 From: "w.pomp" Date: Thu, 22 Oct 2026 11:47:26 +0200 Subject: [PATCH] - remove center and shape from Transform --- image-registration | 2 +- .../ndbioimage/ndbioimage_rs/transforms.pyi | 37 +----- ndbioimage/src/py/transforms.rs | 107 ++++++------------ ndbioimage/src/transforms.rs | 78 +++++-------- ndbioimage/src/view.rs | 15 +-- 5 files changed, 67 insertions(+), 172 deletions(-) diff --git a/image-registration b/image-registration index dc1d5d8..aac61b9 160000 --- a/image-registration +++ b/image-registration @@ -1 +1 @@ -Subproject commit dc1d5d8364f51f7323ae3aaac4490b9eece6305d +Subproject commit aac61b981f539bbdce5a005706c6b114da80c5a6 diff --git a/ndbioimage/py/ndbioimage/ndbioimage_rs/transforms.pyi b/ndbioimage/py/ndbioimage/ndbioimage_rs/transforms.pyi index 706f971..9df8502 100644 --- a/ndbioimage/py/ndbioimage/ndbioimage_rs/transforms.pyi +++ b/ndbioimage/py/ndbioimage/ndbioimage_rs/transforms.pyi @@ -116,24 +116,6 @@ class Transform: @dparameters.setter def dparameters(self, value: typing.Sequence[builtins.float]) -> None: ... @property - def center(self) -> builtins.list[builtins.float]: ... - @center.setter - def center(self, value: typing.Sequence[builtins.float]) -> None: ... - @property - def origin(self) -> builtins.list[builtins.float]: - r""" - backwards compatibility - """ - @origin.setter - def origin(self, value: typing.Sequence[builtins.float]) -> None: - r""" - backwards compatibility - """ - @property - def shape(self) -> builtins.list[builtins.int]: ... - @shape.setter - def shape(self, value: typing.Sequence[builtins.int]) -> None: ... - @property def ndim(self) -> builtins.int: ... @property def matrix(self) -> numpy.typing.NDArray[numpy.float64]: ... @@ -149,18 +131,12 @@ class Transform: def __new__( cls, parameters: typing.Optional[typing.Sequence[builtins.float]] = None, - shape: typing.Optional[typing.Sequence[builtins.int]] = None, + dparameters: typing.Optional[typing.Sequence[builtins.float]] = None, center: typing.Optional[typing.Sequence[builtins.float]] = None, ) -> Transform: ... def __getnewargs__( self, - ) -> tuple[ - builtins.list[builtins.float], - builtins.list[builtins.int], - typing.Optional[builtins.list[builtins.float]], - ]: ... - def __getstate__(self) -> builtins.list[builtins.float]: ... - def __setstate__(self, state: typing.Sequence[builtins.float]) -> None: ... + ) -> tuple[builtins.list[builtins.float], builtins.list[builtins.float]]: ... def __add__(self, other: Transform) -> Transform: ... def __radd__(self, other: Transform) -> Transform: ... def __sub__(self, other: Transform) -> Transform: ... @@ -168,11 +144,6 @@ class Transform: def __mul__(self, other: Transform | float) -> Transform: ... def __rmul__(self, other: Transform | float) -> Transform: ... def __truediv__(self, other: builtins.float) -> Transform: ... - def adapt( - self, - center: typing.Sequence[builtins.float], - shape: typing.Sequence[builtins.int], - ) -> None: ... @staticmethod def from_scaling(scaling: typing.Sequence[builtins.float]) -> Transform: ... @staticmethod @@ -202,10 +173,10 @@ class Transform: """ def is_unity(self) -> builtins.bool: ... def transform_point( - self, point: numpy.typing.ArrayLike + self, point: numpy.typing.ArrayLike, center: numpy.typing.ArrayLike ) -> numpy.typing.NDArray[numpy.float64]: ... def transform_points( - self, points: numpy.typing.ArrayLike + self, points: numpy.typing.ArrayLike, center: numpy.typing.ArrayLike ) -> numpy.typing.NDArray[numpy.float64]: ... @staticmethod def register( diff --git a/ndbioimage/src/py/transforms.rs b/ndbioimage/src/py/transforms.rs index 58c9e04..c446183 100644 --- a/ndbioimage/src/py/transforms.rs +++ b/ndbioimage/src/py/transforms.rs @@ -1,6 +1,5 @@ use crate::error::Error; use crate::py::imread::PyView; -use crate::readers::Reader; use crate::transforms::Transforms; use image_registration::metric::{SamplingArg, Sigma}; use image_registration::register::{Optimizer, RegistrationStep}; @@ -36,41 +35,31 @@ pub(crate) struct PyTransform { #[pymethods] impl PyTransform { #[new] - #[pyo3(signature = (parameters = None, shape = None, center = None))] + #[pyo3(signature = (parameters = None, dparameters = None, center = None))] fn new( parameters: Option>, - shape: Option>, + dparameters: Option>, center: Option>, ) -> Self { let parameters = parameters.unwrap_or_else(|| vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); - let shape = shape.unwrap_or_else(|| vec![512, 512]); - if let Some(center) = center { - Self { - inner: Transform::new_with_center(parameters, center, shape), - } + let mut transform = if let Some(center) = center { + Transform::new_around_center(parameters, ¢er) } else { - Self { - inner: Transform::new(parameters, shape), - } + Transform::new(parameters) + }; + if let Some(dparameters) = dparameters { + transform.dparameters = dparameters; } + Self { inner: transform } } - fn __getnewargs__(&self) -> (Vec, Vec, Option>) { + fn __getnewargs__(&self) -> (Vec, Vec) { ( self.inner.parameters.clone(), - self.inner.shape.clone(), - Some(self.inner.center.clone()), + self.inner.dparameters.clone(), ) } - fn __getstate__(&self) -> Vec { - self.inner.dparameters.clone() - } - - fn __setstate__(&mut self, state: Vec) { - self.inner.dparameters = state; - } - fn __add__(&self, other: &PyTransform) -> PyTransform { PyTransform { inner: &self.inner + &other.inner, @@ -159,38 +148,6 @@ impl PyTransform { self.inner.dparameters = dparameters; } - #[getter] - fn get_center(&self) -> Vec { - self.inner.center.clone() - } - - #[setter] - fn set_center(&mut self, center: Vec) { - self.inner.center = center; - } - - /// backwards compatibility - #[getter] - fn get_origin(&self) -> Vec { - self.inner.center.clone() - } - - /// backwards compatibility - #[setter] - fn set_origin(&mut self, center: Vec) { - self.inner.center = center; - } - - #[getter] - fn get_shape(&self) -> Vec { - self.inner.shape.clone() - } - - #[setter] - fn set_shape(&mut self, shape: Vec) { - self.inner.shape = shape; - } - #[getter] fn get_ndim(&self) -> usize { self.inner.ndim() @@ -233,10 +190,6 @@ impl PyTransform { }) } - fn adapt(&mut self, center: Vec, shape: Vec) { - self.inner.adapt(center.as_slice(), shape.as_slice()); - } - #[staticmethod] fn from_scaling(scaling: Vec) -> PyTransform { PyTransform { @@ -436,13 +389,19 @@ impl PyTransform { py: Python<'py>, #[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))] point: PyArrayLike1, + #[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))] + center: PyArrayLike1, ) -> Bound<'py, PyArray1> { let point = point.as_array(); - if let Some(slice) = point.as_slice() { - self.inner.transform_point(slice) - } else { - let point = point.to_vec(); - self.inner.transform_point(&point) + let center = center.as_array(); + + match (point.as_slice(), center.as_slice()) { + (Some(point), Some(center)) => self.inner.transform_point(point, center), + (Some(point), None) => self.inner.transform_point(point, ¢er.to_vec()), + (None, Some(center)) => self.inner.transform_point(&point.to_vec(), center), + (None, None) => self + .inner + .transform_point(&point.to_vec(), ¢er.to_vec()), } .into_pyarray(py) } @@ -452,10 +411,19 @@ impl PyTransform { py: Python<'py>, #[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))] points: PyArrayLike2, + #[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))] + center: PyArrayLike1, ) -> PyResult>> { let points = points.as_array(); + let center = center.as_array(); Ok(py - .detach(|| self.inner.transform_points(points)) + .detach(|| { + if let Some(center) = center.as_slice() { + self.inner.transform_points(points, center) + } else { + self.inner.transform_points(points, ¢er.to_vec()) + } + }) .map_err(Error::from)? .into_pyarray(py)) } @@ -796,17 +764,6 @@ impl PyView { main_channel, default_transform, )?; - let new_shape = vec![self.view.raw_shape().y, self.view.raw_shape().x]; - for transform in new.view.transforms.channel.values_mut() { - for (c, (old_s, new_s)) in transform - .center - .iter_mut() - .zip(transform.shape.iter().zip(new_shape.iter())) - { - *c -= (*old_s as f64) / 2.0 - (*new_s as f64) / 2.0; - } - transform.shape = new_shape.clone(); - } Ok(new) } diff --git a/ndbioimage/src/transforms.rs b/ndbioimage/src/transforms.rs index 8c8ae3c..4bcf4c6 100644 --- a/ndbioimage/src/transforms.rs +++ b/ndbioimage/src/transforms.rs @@ -33,15 +33,17 @@ impl TryFrom> for Transforms { channel: t .into_iter() .map(|(n, i)| { + let center = i + .size + .iter() + .zip(i.center_of_rotation_point.iter()) + .map(|(s, c)| c - s / 2.0 + 0.5) + .collect::>(); Ok(( n, - Transform::new_with_center( - i.transform_parameters, - i.center_of_rotation_point, - i.size.into_iter().map(|j| j as usize).collect(), - ) - .with_dparameters(i.dtransform_parameters) - .inverse()?, + Transform::new_around_center(i.transform_parameters, ¢er) + .with_dparameters(i.dtransform_parameters) + .inverse()?, )) }) .collect::>()?, @@ -93,6 +95,7 @@ impl Serialize for Transforms { #[derive(Serialize)] struct Binary<'a> { + interpolation_order: Option, channel: &'a HashMap>, drift: &'a Vec>, } @@ -105,6 +108,7 @@ impl Serialize for Transforms { .serialize(serializer) } else { Binary { + interpolation_order: self.interpolation_order, channel: &self.channel, drift: &self.drift, } @@ -164,8 +168,7 @@ impl Transforms { if let Some(transform_file) = transform_file { Self::load(transform_file) } else { - let default_transform = default_transform - .map(|default_transform| Transform::new(default_transform, vec![512, 0])); + let default_transform = default_transform.map(Transform::new); let mut transforms = Self::default(); if let Some(channels_files) = channels_files { let main_channel = main_channel.unwrap_or(0); @@ -303,8 +306,8 @@ impl Transforms { match (self.channel.get(channel), self.drift.get(t)) { (Some(channel), Some(drift)) => match (channel.ndim(), drift.ndim()) { (2, 2) | (3, 3) => Ok(Some(Cow::Owned(drift * channel))), - (2, 3) => Ok(Some(Cow::Owned(drift.add_dimension(0, 1) * channel))), - (3, 2) => Ok(Some(Cow::Owned(drift * channel.add_dimension(0, 1)))), + (2, 3) => Ok(Some(Cow::Owned(drift.add_dimension(0) * channel))), + (3, 2) => Ok(Some(Cow::Owned(drift * channel.add_dimension(0)))), (2 | 3, n) | (n, _) => Err(Error::TransformationNotImplemented(n)), }, (Some(channel), None) => Ok(Some(Cow::Borrowed(channel))), @@ -348,23 +351,13 @@ impl View { .max_proj(Axis::Z)? .as_array::()?; let mut transforms = HashMap::new(); - let shape = vec![self.shape().y, self.shape().x]; - let center = shape - .iter() - .map(|&i| (i as f64 - 1.0) / 2.0) - .collect::>(); for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() { let name = metadata .name .clone() .unwrap_or_else(|| format!("channel{}", channel)); if channel == main_channel { - transforms.insert( - name, - Transform::default() - .with_shape(shape.clone()) - .with_center(center.clone()), - ); + transforms.insert(name, Transform::default()); } else { let max = self .slice_cztyx(s![channel, .., 0, .., ..])? @@ -387,23 +380,13 @@ impl View { .slice_cztyx(s![main_channel, .., 0, .., ..])? .as_array::()?; let mut transforms = HashMap::new(); - let shape = vec![self.shape().z, self.shape().y, self.shape().x]; - let center = shape - .iter() - .map(|&i| (i as f64 - 1.0) / 2.0) - .collect::>(); for (channel, metadata) in self.metadata()?.image[0].pixels.channel.iter().enumerate() { let name = metadata .name .clone() .unwrap_or_else(|| format!("channel{}", channel)); if channel == main_channel { - transforms.insert( - name, - Transform::default() - .with_shape(shape.clone()) - .with_center(center.clone()), - ); + transforms.insert(name, Transform::default()); } else { let max = self .slice_cztyx(s![channel, .., 0, .., ..])? @@ -479,11 +462,12 @@ impl View { 3 => { let tr = transform.clone().into_dimensionality::()?; let shape = self.shape(); + let center = shape.iter().map(|&i| (i as f64) / 2.0).collect::>(); let zf = z as f64; let yf = shape.y as f64 - 1.0; let xf = shape.x as f64 - 1.0; let corners = array![[zf, 0.0, 0.0], [zf, 0.0, xf], [zf, yf, 0.0], [zf, yf, xf]]; - let extent = tr.transform_points(&corners)?; + let extent = tr.transform_points(&corners, ¢er)?; let z_extent = extent.slice(s![.., 2]); let z_min = (z_extent .iter() @@ -652,26 +636,18 @@ mod tests { let mut channel = HashMap::new(); channel.insert( "TV1".to_string(), - Transform::new_with_center( - vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0], - vec![255.5, 255.5], - vec![512, 512], - ), + Transform::new(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]), ); channel.insert( "TV2".to_string(), - Transform::new_with_center( - vec![ - 0.9995868322142533, - 0.012140955373934502, - 0.012592501668218145, - 0.8571690967587099, - -5.270859902118885, - -5.81118547158953, - ], - vec![255.5, 255.5], - vec![512, 512], - ), + Transform::new(vec![ + 0.9995868322142533, + 0.012140955373934502, + 0.012592501668218145, + 0.8571690967587099, + -5.270859902118885, + -5.81118547158953, + ]), ); let view_t = view.with_transforms(Transforms { interpolation_order: None, diff --git a/ndbioimage/src/view.rs b/ndbioimage/src/view.rs index 7b543cc..7568838 100644 --- a/ndbioimage/src/view.rs +++ b/ndbioimage/src/view.rs @@ -1167,20 +1167,11 @@ impl View { for (ti, t) in slice_out[2].into_iter().zip(&slice[2]) { if let Some(transform) = self.transforms.get_transform(channel_name, t as usize)? { let mut transform = transform.into_owned(); - if !defer_slice_xy { + if !defer_slice_xy && transpose { if transform.ndim() == 2 { - transform.center[0] -= slice[3].start as f64; - transform.center[1] -= slice[4].start as f64; - if transpose { - transform = transform.swap_axes(0, 1) - } + transform = transform.swap_axes(0, 1) } else { - transform.center[0] -= slice[1].start as f64; - transform.center[1] -= slice[3].start as f64; - transform.center[2] -= slice[4].start as f64; - if transpose { - transform = transform.swap_axes(1, 2); - } + transform = transform.swap_axes(1, 2); } }