- some more macro use

- implement reshape for python
- make Reader and View Sync
- register_translation_fft
- calculate_transforms_from and functions it needs
This commit is contained in:
Wim Pomp committed 2026-10-24 11:58:06 +02:00
1 parent 3e33fe18c1
commit 22e8ab1dd2
8 files changed
+479 -373

No files matched your search

@@ -301,7 +301,12 @@ class Imread:
r"""
only remains for backwards compatibility
"""
def reshape(self, order: builtins.str, copy: builtins.bool) -> typing.Any:
def reshape(
self,
*shape: typing.Any,
order: builtins.str = "C",
copy: typing.Optional[builtins.bool] = None,
) -> typing.Any:
r"""
reshape the view with a new axis order
"""
@@ -193,6 +193,10 @@ class Transform:
def register_translation(
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
) -> Transform: ...
@staticmethod
def register_translation_fft(
fixed: numpy.typing.ArrayLike, moving: numpy.typing.ArrayLike
) -> Transform: ...
class Transforms:
@property
+264 -337
View File
@@ -6,7 +6,7 @@ use crate::movie::MovieOptions;
use crate::readers::{DynReader, PixelType, Reader};
use crate::view::{Item, View};
use itertools::Itertools;
use ndarray::{Ix0, Ix1, IxDyn, SliceInfo, SliceInfoElem};
use ndarray::{ArrayD, Ix0, Ix1, IxDyn, SliceInfo, SliceInfoElem};
use numpy::{
AllowTypeChange, IntoPyArray, PyArray, PyArrayDescr, PyArrayLike0, PyArrayLike1,
PyArrayMethods, dtype,
@@ -71,73 +71,30 @@ unsafe impl Sync for PyView {}
impl PyView {
fn item(py: Python, view: View<IxDyn>, dtype: PixelType) -> PyResult<Bound<PyAny>> {
macro_rules! item {
($t:tt) => {
view.into_dimensionality::<Ix0>()?
.item::<$t>()?
.into_pyobject(py)?
.into_any()
};
}
Ok(match dtype {
PixelType::Bool => todo!(),
PixelType::I8 => view
.into_dimensionality::<Ix0>()?
.item::<i8>()?
.into_pyobject(py)?
.into_any(),
PixelType::U8 => view
.into_dimensionality::<Ix0>()?
.item::<u8>()?
.into_pyobject(py)?
.into_any(),
PixelType::I16 => view
.into_dimensionality::<Ix0>()?
.item::<i16>()?
.into_pyobject(py)?
.into_any(),
PixelType::U16 => view
.into_dimensionality::<Ix0>()?
.item::<u16>()?
.into_pyobject(py)?
.into_any(),
PixelType::I32 => view
.into_dimensionality::<Ix0>()?
.item::<i32>()?
.into_pyobject(py)?
.into_any(),
PixelType::U32 => view
.into_dimensionality::<Ix0>()?
.item::<u32>()?
.into_pyobject(py)?
.into_any(),
PixelType::F32 => view
.into_dimensionality::<Ix0>()?
.item::<f32>()?
.into_pyobject(py)?
.into_any(),
PixelType::F64 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I64 => view
.into_dimensionality::<Ix0>()?
.item::<i64>()?
.into_pyobject(py)?
.into_any(),
PixelType::U64 => view
.into_dimensionality::<Ix0>()?
.item::<u64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I128 => view
.into_dimensionality::<Ix0>()?
.item::<i128>()?
.into_pyobject(py)?
.into_any(),
PixelType::U128 => view
.into_dimensionality::<Ix0>()?
.item::<u128>()?
.into_pyobject(py)?
.into_any(),
PixelType::F128 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I8 => item!(i8),
PixelType::U8 => item!(u8),
PixelType::I16 => item!(i16),
PixelType::U16 => item!(u16),
PixelType::I32 => item!(i32),
PixelType::U32 => item!(u32),
PixelType::F32 => item!(f32),
PixelType::F64 => item!(f64),
PixelType::I64 => item!(i64),
PixelType::U64 => item!(u64),
PixelType::I128 => item!(i128),
PixelType::U128 => item!(u128),
PixelType::F128 => item!(f64),
})
}
}
@@ -299,9 +256,45 @@ impl PyView {
}
/// reshape the view with a new axis order
#[expect(unused_variables)]
fn reshape<'py>(&self, order: &str, copy: bool) -> PyResult<Bound<'py, PyAny>> {
todo!()
#[pyo3(signature = (*shape, order="C", copy=None))]
fn reshape<'py>(
&self,
py: Python<'py>,
shape: Vec<isize>,
order: &str,
copy: Option<bool>,
) -> PyResult<Bound<'py, PyAny>> {
// TODO: check shape argument
if order != "C" {
return Err(PyNotImplementedError::new_err(
"order except C is not implemented",
));
}
if let Some(copy) = copy
&& !copy
{
return Err(PyNotImplementedError::new_err(
"copy = False is not implemented",
));
}
let n = self.view.shape().iter().product::<usize>();
let m = shape
.iter()
.fold(1, |i, j| if *j < 0 { i } else { i * (*j as usize) });
let shape = shape
.iter()
.map(|&s| if s < 0 { n / m } else { s as usize })
.collect::<Vec<_>>();
if shape.iter().zip(self.view.shape()).all(|(&i, j)| i == j) {
self.clone().into_bound_py_any(py)
} else {
let vec =
py.detach(|| Ok::<_, Error>(self.view.as_array::<f64>()?.flatten().to_vec()))?;
Ok(ArrayD::from_shape_vec(shape, vec)
.map_err(Error::from)?
.into_pyarray(py)
.into_any())
}
}
#[gen_stub(override_return_type(type_repr="Imread | float | int", imports=("numpy", "numpy.typing")))]
@@ -351,73 +344,30 @@ impl PyView {
self.view.squeeze()?
};
if view.ndim() == 0 {
macro_rules! squeeze {
($t:tt) => {
view.into_dimensionality::<Ix0>()?
.item::<$t>()?
.into_pyobject(py)?
.into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => view
.into_dimensionality::<Ix0>()?
.item::<i8>()?
.into_pyobject(py)?
.into_any(),
PixelType::U8 => view
.into_dimensionality::<Ix0>()?
.item::<u8>()?
.into_pyobject(py)?
.into_any(),
PixelType::I16 => view
.into_dimensionality::<Ix0>()?
.item::<i16>()?
.into_pyobject(py)?
.into_any(),
PixelType::U16 => view
.into_dimensionality::<Ix0>()?
.item::<u16>()?
.into_pyobject(py)?
.into_any(),
PixelType::I32 => view
.into_dimensionality::<Ix0>()?
.item::<i32>()?
.into_pyobject(py)?
.into_any(),
PixelType::U32 => view
.into_dimensionality::<Ix0>()?
.item::<u32>()?
.into_pyobject(py)?
.into_any(),
PixelType::I64 => view
.into_dimensionality::<Ix0>()?
.item::<i64>()?
.into_pyobject(py)?
.into_any(),
PixelType::U64 => view
.into_dimensionality::<Ix0>()?
.item::<u64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I128 => view
.into_dimensionality::<Ix0>()?
.item::<i64>()?
.into_pyobject(py)?
.into_any(),
PixelType::U128 => view
.into_dimensionality::<Ix0>()?
.item::<u64>()?
.into_pyobject(py)?
.into_any(),
PixelType::F32 => view
.into_dimensionality::<Ix0>()?
.item::<f32>()?
.into_pyobject(py)?
.into_any(),
PixelType::F64 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
PixelType::F128 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I8 => squeeze!(i8),
PixelType::U8 => squeeze!(u8),
PixelType::I16 => squeeze!(i16),
PixelType::U16 => squeeze!(u16),
PixelType::I32 => squeeze!(i32),
PixelType::U32 => squeeze!(u32),
PixelType::I64 => squeeze!(i64),
PixelType::U64 => squeeze!(u64),
PixelType::I128 => squeeze!(i128),
PixelType::U128 => squeeze!(u128),
PixelType::F32 => squeeze!(f32),
PixelType::F64 => squeeze!(f64),
PixelType::F128 => squeeze!(f64),
})
} else {
PyView {
@@ -588,7 +538,6 @@ impl PyView {
}
/// convert to a numpy array, optionally with a different dtype
#[expect(unused_variables)]
#[pyo3(signature = (dtype = None, copy = None))]
#[gen_stub(override_return_type(type_repr="numpy.typing.NDArray", imports=("numpy", "numpy.typing")))]
fn __array__<'py>(
@@ -602,6 +551,13 @@ impl PyView {
dtype: Option<Bound<'py, PyAny>>,
copy: Option<bool>,
) -> PyResult<Bound<'py, PyAny>> {
if let Some(copy) = copy
&& !copy
{
return Err(PyNotImplementedError::new_err(
"copy = False is not implemented",
));
}
if let Some(dtype) = dtype {
self.as_type(py, dtype)?.as_array(py)
} else {
@@ -1232,114 +1188,83 @@ impl PyView {
z: isize,
t: isize,
) -> PyResult<Bound<'py, PyAny>> {
macro_rules! get_frame {
($t:tt) => {
self.view
.get_frame::<$t, _>(c, z, t)?
.into_pyarray(py)
.into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self
.view
.get_frame::<i8, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U8 => self
.view
.get_frame::<u8, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I16 => self
.view
.get_frame::<i16, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U16 => self
.view
.get_frame::<u16, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I32 => self
.view
.get_frame::<i32, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U32 => self
.view
.get_frame::<u32, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F32 => self
.view
.get_frame::<f32, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F64 => self
.view
.get_frame::<f64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I64 => self
.view
.get_frame::<i64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U64 => self
.view
.get_frame::<u64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I128 => self
.view
.get_frame::<i64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U128 => self
.view
.get_frame::<u64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F128 => self
.view
.get_frame::<f64, _>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I8 => get_frame!(i8),
PixelType::U8 => get_frame!(u8),
PixelType::I16 => get_frame!(i16),
PixelType::U16 => get_frame!(u16),
PixelType::I32 => get_frame!(i32),
PixelType::U32 => get_frame!(u32),
PixelType::F32 => get_frame!(f32),
PixelType::F64 => get_frame!(f64),
PixelType::I64 => get_frame!(i64),
PixelType::U64 => get_frame!(u64),
PixelType::I128 => get_frame!(i64),
PixelType::U128 => get_frame!(u64),
PixelType::F128 => get_frame!(f64),
})
}
/// flatten the view into a 1D numpy array
#[gen_stub(override_return_type(type_repr="numpy.typing.NDArray | float", imports=("numpy", "numpy.typing")))]
fn flatten<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
macro_rules! flatten {
($t:tt) => {
self.view.flatten::<$t>()?.into_pyarray(py).into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self.view.flatten::<i8>()?.into_pyarray(py).into_any(),
PixelType::U8 => self.view.flatten::<u8>()?.into_pyarray(py).into_any(),
PixelType::I16 => self.view.flatten::<i16>()?.into_pyarray(py).into_any(),
PixelType::U16 => self.view.flatten::<u16>()?.into_pyarray(py).into_any(),
PixelType::I32 => self.view.flatten::<i32>()?.into_pyarray(py).into_any(),
PixelType::U32 => self.view.flatten::<u32>()?.into_pyarray(py).into_any(),
PixelType::F32 => self.view.flatten::<f32>()?.into_pyarray(py).into_any(),
PixelType::F64 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
PixelType::I64 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
PixelType::U64 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
PixelType::I128 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
PixelType::U128 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
PixelType::F128 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
PixelType::I8 => flatten!(i8),
PixelType::U8 => flatten!(u8),
PixelType::I16 => flatten!(i16),
PixelType::U16 => flatten!(u16),
PixelType::I32 => flatten!(i32),
PixelType::U32 => flatten!(u32),
PixelType::F32 => flatten!(f32),
PixelType::F64 => flatten!(f64),
PixelType::I64 => flatten!(i64),
PixelType::U64 => flatten!(u64),
PixelType::I128 => flatten!(i64),
PixelType::U128 => flatten!(u64),
PixelType::F128 => flatten!(f64),
})
}
/// convert the view to bytes
fn to_bytes(&self) -> PyResult<Vec<u8>> {
macro_rules! to_bytes {
($t:tt) => {
self.view.to_bytes::<$t>()?
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self.view.to_bytes::<i8>()?,
PixelType::U8 => self.view.to_bytes::<u8>()?,
PixelType::I16 => self.view.to_bytes::<i16>()?,
PixelType::U16 => self.view.to_bytes::<u16>()?,
PixelType::I32 => self.view.to_bytes::<i32>()?,
PixelType::U32 => self.view.to_bytes::<u32>()?,
PixelType::F32 => self.view.to_bytes::<f32>()?,
PixelType::F64 => self.view.to_bytes::<f64>()?,
PixelType::I64 => self.view.to_bytes::<i64>()?,
PixelType::U64 => self.view.to_bytes::<u64>()?,
PixelType::I128 => self.view.to_bytes::<i64>()?,
PixelType::U128 => self.view.to_bytes::<u64>()?,
PixelType::F128 => self.view.to_bytes::<f64>()?,
PixelType::I8 => to_bytes!(i8),
PixelType::U8 => to_bytes!(u8),
PixelType::I16 => to_bytes!(i16),
PixelType::U16 => to_bytes!(u16),
PixelType::I32 => to_bytes!(i32),
PixelType::U32 => to_bytes!(u32),
PixelType::F32 => to_bytes!(f32),
PixelType::F64 => to_bytes!(f64),
PixelType::I64 => to_bytes!(i64),
PixelType::U64 => to_bytes!(u64),
PixelType::I128 => to_bytes!(i64),
PixelType::U128 => to_bytes!(u64),
PixelType::F128 => to_bytes!(f64),
})
}
@@ -1488,60 +1413,29 @@ impl PyView {
/// collect data into a numpy array
#[gen_stub(override_return_type(type_repr="numpy.typing.NDArray | float", imports=("numpy", "numpy.typing")))]
fn as_array<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
macro_rules! as_array {
($t:tt) => {
py.detach(|| self.view.as_array_dyn::<$t>())?
.into_pyarray(py)
.into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => py
.detach(|| self.view.as_array_dyn::<i8>())?
.into_pyarray(py)
.into_any(),
PixelType::U8 => py
.detach(|| self.view.as_array_dyn::<u8>())?
.into_pyarray(py)
.into_any(),
PixelType::I16 => py
.detach(|| self.view.as_array_dyn::<i16>())?
.into_pyarray(py)
.into_any(),
PixelType::U16 => py
.detach(|| self.view.as_array_dyn::<u16>())?
.into_pyarray(py)
.into_any(),
PixelType::I32 => py
.detach(|| self.view.as_array_dyn::<i32>())?
.into_pyarray(py)
.into_any(),
PixelType::U32 => py
.detach(|| self.view.as_array_dyn::<u32>())?
.into_pyarray(py)
.into_any(),
PixelType::F32 => py
.detach(|| self.view.as_array_dyn::<f32>())?
.into_pyarray(py)
.into_any(),
PixelType::F64 => py
.detach(|| self.view.as_array_dyn::<f64>())?
.into_pyarray(py)
.into_any(),
PixelType::I64 => py
.detach(|| self.view.as_array_dyn::<i64>())?
.into_pyarray(py)
.into_any(),
PixelType::U64 => py
.detach(|| self.view.as_array_dyn::<u64>())?
.into_pyarray(py)
.into_any(),
PixelType::I128 => py
.detach(|| self.view.as_array_dyn::<i64>())?
.into_pyarray(py)
.into_any(),
PixelType::U128 => py
.detach(|| self.view.as_array_dyn::<u64>())?
.into_pyarray(py)
.into_any(),
PixelType::F128 => py
.detach(|| self.view.as_array_dyn::<f64>())?
.into_pyarray(py)
.into_any(),
PixelType::I8 => as_array!(i8),
PixelType::U8 => as_array!(u8),
PixelType::I16 => as_array!(i16),
PixelType::U16 => as_array!(u16),
PixelType::I32 => as_array!(i32),
PixelType::U32 => as_array!(u32),
PixelType::F32 => as_array!(f32),
PixelType::F64 => as_array!(f64),
PixelType::I64 => as_array!(i64),
PixelType::U64 => as_array!(u64),
PixelType::I128 => as_array!(i64),
PixelType::U128 => as_array!(u64),
PixelType::F128 => as_array!(f64),
})
}
@@ -1549,25 +1443,33 @@ impl PyView {
#[gen_stub(override_return_type(type_repr = "numpy.dtype", imports=("numpy")))]
#[getter]
fn get_dtype<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArrayDescr>> {
macro_rules! get_dtype {
($t:tt) => {
Ok(dtype::<$t>(py))
};
}
match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => Ok(dtype::<i8>(py)),
PixelType::U8 => Ok(dtype::<u8>(py)),
PixelType::I16 => Ok(dtype::<i16>(py)),
PixelType::U16 => Ok(dtype::<u16>(py)),
PixelType::I32 => Ok(dtype::<i32>(py)),
PixelType::U32 => Ok(dtype::<u32>(py)),
PixelType::F32 => Ok(dtype::<f32>(py)),
PixelType::F64 => Ok(dtype::<f64>(py)),
PixelType::I64 => Ok(dtype::<i64>(py)),
PixelType::U64 => Ok(dtype::<u64>(py)),
PixelType::Bool => get_dtype!(bool),
PixelType::I8 => get_dtype!(i8),
PixelType::U8 => get_dtype!(u8),
PixelType::I16 => get_dtype!(i16),
PixelType::U16 => get_dtype!(u16),
PixelType::I32 => get_dtype!(i32),
PixelType::U32 => get_dtype!(u32),
PixelType::F32 => get_dtype!(f32),
PixelType::F64 => get_dtype!(f64),
PixelType::I64 => get_dtype!(i64),
PixelType::U64 => get_dtype!(u64),
PixelType::I128 => Err(PyTypeError::new_err(
"type is i128, but this cannot be represented by numpy.dtype",
)),
PixelType::U128 => Err(PyTypeError::new_err(
"type is u128, but this cannot be represented by numpy.dtype",
)),
PixelType::F128 => Ok(dtype::<f64>(py)),
PixelType::F128 => Err(PyTypeError::new_err(
"type is f128, but this is not supported by rust",
)),
}
}
@@ -1619,21 +1521,27 @@ impl PyView {
}
.into_bound_py_any(py)
} else {
macro_rules! max {
($t:tt) => {
self.view.max::<$t>()?.into_pyobject(py)?.into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self.view.max::<i8>()?.into_pyobject(py)?.into_any(),
PixelType::U8 => self.view.max::<u8>()?.into_pyobject(py)?.into_any(),
PixelType::I16 => self.view.max::<i16>()?.into_pyobject(py)?.into_any(),
PixelType::U16 => self.view.max::<u16>()?.into_pyobject(py)?.into_any(),
PixelType::I32 => self.view.max::<i32>()?.into_pyobject(py)?.into_any(),
PixelType::U32 => self.view.max::<u32>()?.into_pyobject(py)?.into_any(),
PixelType::F32 => self.view.max::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.max::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.max::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I8 => max!(i8),
PixelType::U8 => max!(u8),
PixelType::I16 => max!(i16),
PixelType::U16 => max!(u16),
PixelType::I32 => max!(i32),
PixelType::U32 => max!(u32),
PixelType::F32 => max!(f32),
PixelType::F64 => max!(f64),
PixelType::I64 => max!(i64),
PixelType::U64 => max!(u64),
PixelType::I128 => max!(i64),
PixelType::U128 => max!(u64),
PixelType::F128 => max!(f64),
})
}
}
@@ -1674,21 +1582,27 @@ impl PyView {
}
.into_bound_py_any(py)
} else {
macro_rules! min {
($t:tt) => {
self.view.min::<$t>()?.into_pyobject(py)?.into_any()
};
}
Ok(match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self.view.min::<i8>()?.into_pyobject(py)?.into_any(),
PixelType::U8 => self.view.min::<u8>()?.into_pyobject(py)?.into_any(),
PixelType::I16 => self.view.min::<i16>()?.into_pyobject(py)?.into_any(),
PixelType::U16 => self.view.min::<u16>()?.into_pyobject(py)?.into_any(),
PixelType::I32 => self.view.min::<i32>()?.into_pyobject(py)?.into_any(),
PixelType::U32 => self.view.min::<u32>()?.into_pyobject(py)?.into_any(),
PixelType::F32 => self.view.min::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.min::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.min::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I8 => min!(i8),
PixelType::U8 => min!(u8),
PixelType::I16 => min!(i16),
PixelType::U16 => min!(u16),
PixelType::I32 => min!(i32),
PixelType::U32 => min!(u32),
PixelType::F32 => min!(f32),
PixelType::F64 => min!(f64),
PixelType::I64 => min!(i64),
PixelType::U64 => min!(u64),
PixelType::I128 => min!(i64),
PixelType::U128 => min!(u64),
PixelType::F128 => min!(f64),
})
}
}
@@ -1760,7 +1674,7 @@ impl PyView {
))?;
}
let dtype = match self.dtype {
PixelType::Bool => todo!(),
PixelType::Bool => PixelType::U8,
PixelType::I8 => PixelType::I16,
PixelType::U8 => PixelType::U16,
PixelType::I16 => PixelType::I32,
@@ -1784,15 +1698,21 @@ impl PyView {
}
.into_bound_py_any(py)
} else {
macro_rules! sum {
($t:tt) => {
self.view.sum::<$t>()?.into_pyobject(py)?.into_any()
};
}
Ok(match self.dtype {
PixelType::F32 => self.view.sum::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
_ => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::F32 => sum!(f32),
PixelType::F64 => sum!(f64),
PixelType::I64 => sum!(i64),
PixelType::U64 => sum!(u64),
PixelType::I128 => sum!(i128),
PixelType::U128 => sum!(u128),
PixelType::F128 => sum!(f64),
_ => sum!(i64),
})
}
}
@@ -2021,20 +1941,27 @@ impl PyView {
};
let options =
crate::tiffwrite::TiffOptions::new(bar, None, colors.unwrap_or_default(), overwrite)?;
macro_rules! save_as_tiff {
($t:tt) => {
self.view.save_as_tiff_with_type::<$t, _>(file, &options)
};
}
py.detach(|| match self.dtype {
PixelType::Bool => todo!(),
PixelType::I8 => self.view.save_as_tiff_with_type::<i8, _>(file, &options),
PixelType::U8 => self.view.save_as_tiff_with_type::<u8, _>(file, &options),
PixelType::I16 => self.view.save_as_tiff_with_type::<i16, _>(file, &options),
PixelType::U16 => self.view.save_as_tiff_with_type::<u16, _>(file, &options),
PixelType::I32 => self.view.save_as_tiff_with_type::<i32, _>(file, &options),
PixelType::U32 => self.view.save_as_tiff_with_type::<u32, _>(file, &options),
PixelType::I64 => self.view.save_as_tiff_with_type::<i64, _>(file, &options),
PixelType::U64 => self.view.save_as_tiff_with_type::<u64, _>(file, &options),
PixelType::I128 => self.view.save_as_tiff_with_type::<i128, _>(file, &options),
PixelType::U128 => self.view.save_as_tiff_with_type::<u128, _>(file, &options),
PixelType::F32 => self.view.save_as_tiff_with_type::<f32, _>(file, &options),
PixelType::F64 => self.view.save_as_tiff_with_type::<f64, _>(file, &options),
PixelType::I8 => save_as_tiff!(i8),
PixelType::U8 => save_as_tiff!(u8),
PixelType::I16 => save_as_tiff!(i16),
PixelType::U16 => save_as_tiff!(u16),
PixelType::I32 => save_as_tiff!(i32),
PixelType::U32 => save_as_tiff!(u32),
PixelType::I64 => save_as_tiff!(i64),
PixelType::U64 => save_as_tiff!(u64),
PixelType::I128 => save_as_tiff!(i128),
PixelType::U128 => save_as_tiff!(u128),
PixelType::F32 => save_as_tiff!(f32),
PixelType::F64 => save_as_tiff!(f64),
PixelType::F128 => Err(Error::NotImplemented(
"saving as f128 is not implemented".to_string(),
)),
+23
View File
@@ -664,6 +664,29 @@ impl PyTransform {
))),
})
}
#[staticmethod]
fn register_translation_fft<'py>(
py: Python<'py>,
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
fixed: PyArrayLikeDyn<f64, AllowTypeChange>,
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
moving: PyArrayLikeDyn<f64, AllowTypeChange>,
) -> PyResult<PyTransform> {
let fixed = fixed.as_array();
let moving = moving.as_array();
if fixed.shape() != moving.shape() {
return Err(PyErr::from(Error::ShapeMismatch(
fixed.shape().to_vec(),
moving.shape().to_vec(),
)));
}
py.detach(|| {
Ok(PyTransform {
inner: Transform::register_translation_fft(fixed, moving).map_err(Error::from)?,
})
})
}
}
#[gen_stub_pyclass]
+1 -1
View File
@@ -182,7 +182,7 @@ pub enum ArrayT<D: Dimension> {
pub type Frame = ArrayT<Ix2>;
/// trait for reading image files
pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
pub trait Reader: Clone + Sized + Debug + Sync + Send + Hash + Into<DynReader> {
/// create a new reader for the given path, series, and position
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
where
+143 -18
View File
@@ -1,9 +1,11 @@
use rayon::iter::ParallelIterator;
use crate::axes::Axis;
use crate::error::Error;
use crate::readers::{DynReader, Frame, PixelType, Reader};
use crate::view::View;
use image_registration::filter;
pub use image_registration::transform::Transform;
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, array, s};
use ndarray::{array, s, Array2, Dimension, Ix2, Ix3, IxDyn, concatenate};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
@@ -11,6 +13,8 @@ use std::hash::{Hash, Hasher};
use std::io::Seek;
use std::path::Path;
use std::sync::Arc;
use rayon::prelude::IntoParallelIterator;
use tiffwrite::IJTiffFile;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
struct TransformOld {
@@ -158,22 +162,27 @@ impl Transforms {
Ok(yaml_serde::to_writer(file, self)?)
}
pub fn calculate_transforms_from(
transform_file: Option<&Path>,
channels_files: Option<&[&Path]>,
drift_file: Option<&Path>,
pub fn calculate_transforms_from<P, Q, R>(
transform_file: Option<P>,
bead_files: Option<&[Q]>,
drift_file: Option<R>,
main_channel: Option<usize>,
default_transform: Option<Vec<f64>>,
) -> Result<Self, Error> {
if let Some(transform_file) = transform_file {
Self::load(transform_file)
) -> Result<Self, Error>
where
P: AsRef<Path>,
Q: AsRef<Path>,
R: AsRef<Path>,
{
if let Some(transform_file) = transform_file.as_ref() && let Ok(transforms) = Self::load(transform_file) {
Ok(transforms)
} else {
let default_transform = default_transform.map(Transform::new);
let mut transforms = Self::default();
if let Some(channels_files) = channels_files {
if let Some(bead_files) = bead_files {
let main_channel = main_channel.unwrap_or(0);
transforms.channel = Self::calculate_channel_transforms_2d(
channels_files,
bead_files,
main_channel,
default_transform,
)?
@@ -187,20 +196,58 @@ impl Transforms {
.map(|t| t.into_dyn())
.collect();
};
if let Some(transform_file) = transform_file {
let transform_file = transform_file.as_ref();
transforms.save(transform_file)?;
if let Some(bead_files) = bead_files {
transforms.save_channel_transform_tiff(transform_file.with_extension("tif"), bead_files)?;
}
}
Ok(transforms)
}
}
pub fn calculate_drift_transforms_2d(file: &Path) -> Result<Vec<Transform<Ix2>>, Error> {
/// save a tiff file with the uncorrected and corrected frames of the bead files side by side
pub fn save_channel_transform_tiff<P, Q>(&self, file: P, bead_files: &[Q]) -> Result<(), Error>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let mut transforms = self.clone();
transforms.drift = Vec::new();
let mut tif = IJTiffFile::new(file)?;
for (t, bead_file) in bead_files.iter().enumerate() {
let mut view = View::<_, DynReader>::from_path(bead_file)?.into_dyn();
let axes = view.axes();
if axes.contains(&Axis::Z) {
view = view.max_proj(Axis::Z)?;
}
if axes.contains(&Axis::T) {
view = view.max_proj(Axis::T)?;
}
let view_u = view.into_dimensionality::<Ix3>()?;
let view_t = view_u.with_transforms(transforms.clone());
for c in 0..view_u.shape().c {
let a = view_u.slice_cztyx(s![c, 0, 0, .., ..])?.as_array::<u16>()?;
let b = view_t.slice_cztyx(s![c, 0, 0, .., ..])?.as_array::<u16>()?;
let frame = concatenate(ndarray::Axis(1), &[a.view(), b.view()])?;
tif.save(frame, c, 0, t)?;
}
}
Ok(())
}
pub fn calculate_drift_transforms_2d<P: AsRef<Path>>(file: P) -> Result<Vec<Transform<Ix2>>, Error> {
View::<_, DynReader>::from_path(file)?.calculate_drift_transforms_2d()
}
pub fn calculate_drift_transforms_3d(file: &Path) -> Result<Vec<Transform<Ix3>>, Error> {
pub fn calculate_drift_transforms_3d<P: AsRef<Path>>(file: P) -> Result<Vec<Transform<Ix3>>, Error> {
View::<_, DynReader>::from_path(file)?.calculate_drift_transforms_3d()
}
pub fn calculate_channel_transforms_2d(
bead_files: &[&Path],
pub fn calculate_channel_transforms_2d<P: AsRef<Path>>(
bead_files: &[P],
main_channel: usize,
default_transform: Option<Transform<Ix2>>,
) -> Result<HashMap<String, Transform<Ix2>>, Error> {
@@ -249,8 +296,8 @@ impl Transforms {
Ok(average_transforms)
}
pub fn calculate_channel_transforms_3d(
bead_files: &[&Path],
pub fn calculate_channel_transforms_3d<P: AsRef<Path>>(
bead_files: &[P],
main_channel: usize,
default_transform: Option<Transform<Ix3>>,
) -> Result<HashMap<String, Transform<Ix3>>, Error> {
@@ -401,7 +448,85 @@ impl<D: Dimension, R: Reader> View<D, R> {
}
pub fn calculate_drift_transforms_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
todo!()
let mut view = self.clone().into_dyn();
let axes = view.axes();
if axes.contains(&Axis::Z) {
view = view.max_proj(Axis::Z)?;
}
if axes.contains(&Axis::C) {
view = view.max_proj(Axis::C)?;
}
view = view.permute_axes(&[Axis::T, Axis::Y, Axis::X])?;
let n = view.shape().t;
let m = (n as f64).sqrt().round() as usize;
let t_groups = (0..=m)
.map(|i| (((i * n) as f64) / (m as f64)).round() as usize)
.collect::<Vec<_>>();
let t_keys = t_groups
.array_windows()
.map(|[start, end]| (start + end) / 2)
.collect::<Vec<_>>();
let t_pairs = t_keys
.iter()
.zip(t_groups.array_windows())
.flat_map(|(t_key, [start, end])| (*start..*end).map(|i| (*t_key, i)))
.chain(t_keys.array_windows().map(|[start, end]| (*start, *end)))
.collect::<Vec<_>>();
let fmaxz_keys = t_keys
.iter()
.map(|t| {
Ok((
t,
filter::gaussian_smooth(
&view.slice(s![*t, .., ..])?.as_array::<f64>()?,
&[5.0; 2],
)?,
))
})
.collect::<Result<HashMap<_, _>, Error>>()?;
let shifts = t_pairs
.into_par_iter()
.map(|(t_key, t)| {
if t_key == t {
Ok(Transform::<Ix2>::from_translation(&vec![0.0; view.ndim()]))
} else {
if let Some(fmaxz) = fmaxz_keys.get(&t_key) {
Ok(Transform::<Ix2>::register_translation(
fmaxz_keys[&t_key].view(),
fmaxz,
)?)
} else {
let fmaxz = filter::gaussian_smooth(
&view.slice(s![t, .., ..])?.as_array::<f64>()?,
&[5.0; 2],
)?;
Ok(Transform::<Ix2>::register_translation(
fmaxz_keys[&t_key].view(),
fmaxz.view(),
)?)
}
}
})
.collect::<Result<Vec<_>, Error>>()?;
let mut t = Transform::<Ix2>::from_translation(&[0.0; 2]);
let mut key_shifts = vec![t.clone()];
key_shifts.extend(shifts.iter().skip(n).map(|i| {
t = i * &t;
t.clone()
}));
Ok(key_shifts
.iter()
.zip(t_groups.array_windows())
.flat_map(|(s, [start, end])| {
let mut t = s.clone();
(*start..*end)
.map(|i| {
t = &shifts[i] * &t;
t.clone()
})
.collect::<Vec<_>>()
})
.collect())
}
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
@@ -489,7 +614,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
.collect::<Result<Vec<_>, Error>>()?;
// TODO; check these translations
let u0 = tr.as_translation(&[(z_min as f64), 0.0, 0.0]);
let u0 = tr.as_translation(&[z_min as f64, 0.0, 0.0]);
let u1 = tr.as_translation(&[-(z_min as f64), 0.0, 0.0]);
let tr = u0 * tr * u1;
+37 -15
View File
@@ -11,8 +11,8 @@ use image_registration::transform::Transform;
use indexmap::IndexMap;
use itertools::{Itertools, iproduct};
use ndarray::{
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
SliceArg, SliceInfoElem, s,
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1,
Ix2, Ix5, IxDyn, SliceArg, SliceInfoElem, s,
};
use num::traits::{AsPrimitive, ToBytes};
use num::{Bounded, FromPrimitive, One, ToPrimitive, Zero};
@@ -21,7 +21,6 @@ use serde_with::serde_as;
use std::any::type_name;
#[cfg(feature = "transforms")]
use std::borrow::Cow;
use std::cell::OnceCell;
use std::cmp::PartialEq;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Display, Formatter};
@@ -30,7 +29,7 @@ use std::iter::Sum;
use std::marker::PhantomData;
use std::ops::{AddAssign, Deref, Div};
use std::path::Path;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
if idx < -bnd {
@@ -112,7 +111,7 @@ pub struct View<D: Dimension, R: Reader = DynReader> {
axes: Vec<Axis>,
operations: IndexMap<Axis, Operation>,
#[serde(skip)]
channel_names: OnceCell<HashMap<usize, String>>,
channel_names: OnceLock<HashMap<usize, String>>,
#[cfg(feature = "transforms")]
pub(crate) transforms: Transforms,
#[serde(skip)]
@@ -830,8 +829,20 @@ impl<D: Dimension, R: Reader> View<D, R> {
} else {
Default::default()
};
#[cfg(feature = "transforms")]
let mut slice_xy = false;
let mut xy_dim = 0usize;
#[cfg(not(feature = "transforms"))]
for &axis in &self.axes {
if let Axis::Y | Axis::X = axis
&& !op_xy.contains_key(&axis)
{
xy_dim += 1;
}
}
#[cfg(feature = "transforms")]
for (s, &axis) in self.slice.iter().zip(&self.axes) {
if let (Axis::Y | Axis::X, SliceInfoElem::Slice { start, end, step }) = (axis, s) {
if !op_xy.contains_key(&axis) {
@@ -861,7 +872,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
#[cfg(feature = "transforms")]
let defer_slice_xy = kind != TransformKind::None && xy_dim == 2 && slice_xy;
#[cfg(not(feature = "transforms"))]
let defer_slice = false;
let defer_slice_xy = false;
let mut shape_out = Vec::new();
let mut slice_out = Vec::new();
@@ -900,7 +911,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
}
}
}
let mut slice_reader = vec![Slice::empty(); 5];
let mut slice_reader = [Slice::empty(); 5];
let shape_reader = self.reader.raw_shape();
for (s, &axis) in self.slice.iter().zip(&self.axes) {
match (axis, s) {
@@ -1115,14 +1126,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
} else {
1
};
#[cfg(not(feature = "transforms"))]
let array = if n == 1 {
out.take().unwrap()
} else {
let m = T::from_usize(n).unwrap_or_else(|| T::zero());
out.take().unwrap().mapv(|x| x / m)
};
#[cfg(feature = "transforms")]
let mut array = if n == 1 {
out.take().unwrap()
} else {
@@ -1585,6 +1588,7 @@ mod tests {
DynReader::new(&path, 0, 0)
}
#[cfg(feature = "tiffseq")]
#[test]
fn view() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1602,6 +1606,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "tiffseq")]
#[test]
fn view_shape() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1613,6 +1618,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "tiffseq")]
#[test]
fn view_new_axis() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1628,6 +1634,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "tiffseq")]
#[test]
fn view_permute_axes() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1655,6 +1662,7 @@ mod tests {
macro_rules! test_max {
($($name:ident: $b:expr $(,)?)*) => {
$(
#[cfg(feature = "tiffseq")]
#[test]
fn $name() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1683,6 +1691,7 @@ mod tests {
macro_rules! test_index {
($($name:ident: $b:expr $(,)?)*) => {
$(
#[cfg(feature = "tiffseq")]
#[test]
fn $name() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1729,6 +1738,7 @@ mod tests {
index_r: s![0, 0, 0, 0, 0]
}
#[cfg(feature = "tiffseq")]
#[test]
fn dyn_view() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1741,6 +1751,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn item() -> Result<(), Error> {
let file = "czi/1xp53-01-AP1.czi";
@@ -1753,6 +1764,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn slice_cztyx() -> Result<(), Error> {
let file = "czi/1xp53-01-AP1.czi";
@@ -1770,6 +1782,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn reset_axes() -> Result<(), Error> {
let file = "czi/1xp53-01-AP1.czi";
@@ -1782,6 +1795,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn reset_axes2() -> Result<(), Error> {
let file = "czi/Experiment-2029.czi";
@@ -1792,6 +1806,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn reset_axes3() -> Result<(), Error> {
let file = "czi/Experiment-2029.czi";
@@ -1820,6 +1835,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "czi")]
#[test]
fn max() -> Result<(), Error> {
let file = "czi/Experiment-2029.czi";
@@ -1932,6 +1948,7 @@ mod tests {
Ok(())
}
#[cfg(feature = "tiffseq")]
#[test]
fn as_array_caches_frames() -> Result<(), Error> {
let file = "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1";
@@ -1977,6 +1994,11 @@ mod tests {
Ok(())
}
#[cfg(any(
feature = "tiff",
feature = "bioformats_java",
feature = "bioformats_rust"
))]
#[test]
fn serde() -> Result<(), Error> {
let file = "tiff/20251014_20-Pos_000_000_max.tif";