- return Error, raise Exception when trying to use f128 data
- clean parsing of python types into rust types - finish PyView.reshape
This commit is contained in:
1 parent
8a4c965477
commit
3c7af5da4d
16 files changed
+394
-208
No files matched your search
+1
-1
Submodule image-registration updated: 9257dd1fcf...0049267997.
+1
-1
Submodule image-tiff updated: a6e8ed772d...563b2b11d7.
@@ -237,18 +237,6 @@ class Imread:
|
||||
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||
"""
|
||||
@property
|
||||
def default_interpolation_value(self) -> transforms.DefaultValue:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
@default_interpolation_value.setter
|
||||
def default_interpolation_value(
|
||||
self, value: float | DefaultValue | collections.abc.Sequence[float]
|
||||
) -> None:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
@property
|
||||
def transforms(self) -> transforms.Transforms:
|
||||
r"""
|
||||
get the transformation
|
||||
@@ -322,7 +310,8 @@ class Imread:
|
||||
copy: typing.Optional[builtins.bool] = None,
|
||||
) -> Imread | numpy.NDArray:
|
||||
r"""
|
||||
reshape the view with a new axis order
|
||||
reshape the view with a new axis order, arguments order and copy cannot be used and will
|
||||
raise an exception if order != "C" or copy is False
|
||||
"""
|
||||
def squeeze(
|
||||
self, axes: str | collections.abc.Sequence[int | str] = None
|
||||
@@ -795,8 +784,24 @@ class Imread:
|
||||
the bspline order used for interpolation when using transforms
|
||||
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||
"""
|
||||
def get_default_interpolation_value(
|
||||
self, channel: builtins.int
|
||||
) -> transforms.DefaultValue:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
def set_default_interpolation_value(
|
||||
self,
|
||||
channel: builtins.int,
|
||||
value: float | transforms.DefaultValue | collections.abc.Sequence[float],
|
||||
) -> None:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
def with_default_interpolation_value(
|
||||
self, value: float | DefaultValue | collections.abc.Sequence[float]
|
||||
self,
|
||||
channel: builtins.int,
|
||||
value: float | transforms.DefaultValue | collections.abc.Sequence[float],
|
||||
) -> Imread:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# ruff: noqa: E501, F401, F403, F405
|
||||
|
||||
import builtins
|
||||
import collections.abc
|
||||
import os
|
||||
import pathlib
|
||||
import typing
|
||||
@@ -28,8 +29,11 @@ class DefaultValue:
|
||||
def __new__(
|
||||
cls,
|
||||
values: typing.Sequence[builtins.float],
|
||||
weights: typing.Sequence[builtins.float],
|
||||
weights: typing.Optional[typing.Sequence[builtins.float]] = None,
|
||||
) -> DefaultValue: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[builtins.list[builtins.float], builtins.list[builtins.float]]: ...
|
||||
|
||||
class Optimizer:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
@@ -249,7 +253,18 @@ class Transforms:
|
||||
channel: typing.Mapping[builtins.str, Transform] = {},
|
||||
drift: typing.Sequence[Transform] = [],
|
||||
interpolation_order: typing.Optional[builtins.int] = None,
|
||||
default_value: typing.Optional[
|
||||
typing.Mapping[builtins.str, DefaultValue]
|
||||
] = None,
|
||||
) -> Transforms: ...
|
||||
def __getnewargs__(
|
||||
self,
|
||||
) -> tuple[
|
||||
builtins.dict[builtins.str, Transform],
|
||||
builtins.list[Transform],
|
||||
typing.Optional[builtins.int],
|
||||
builtins.dict[builtins.str, DefaultValue],
|
||||
]: ...
|
||||
def with_interpolation_order(
|
||||
self, order: typing.Optional[builtins.int]
|
||||
) -> Transforms:
|
||||
@@ -257,9 +272,28 @@ class Transforms:
|
||||
the bspline order used for interpolation when using transforms
|
||||
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||
"""
|
||||
def __getnewargs__(
|
||||
def get_default_interpolation_value(
|
||||
self, channel_name: builtins.str
|
||||
) -> DefaultValue:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
def set_default_interpolation_value(
|
||||
self,
|
||||
) -> tuple[builtins.dict[builtins.str, Transform], builtins.list[Transform]]: ...
|
||||
channel_name: builtins.str,
|
||||
value: float | DefaultValue | collections.abc.Sequence[float],
|
||||
) -> None:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
def with_default_interpolation_value(
|
||||
self,
|
||||
channel_name: builtins.str,
|
||||
value: float | DefaultValue | collections.abc.Sequence[float],
|
||||
) -> Transforms:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
@staticmethod
|
||||
def load(path: builtins.str | os.PathLike | pathlib.Path) -> Transforms: ...
|
||||
def save(self, path: builtins.str | os.PathLike | pathlib.Path) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .ndbioimage_rs.transforms import (
|
||||
DefaultValue,
|
||||
Optimizer,
|
||||
RegistrationStep,
|
||||
SamplingArg,
|
||||
@@ -8,6 +9,7 @@ from .ndbioimage_rs.transforms import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DefaultValue",
|
||||
"Optimizer",
|
||||
"RegistrationStep",
|
||||
"SamplingArg",
|
||||
|
||||
@@ -184,6 +184,8 @@ pub enum Error {
|
||||
/// no valid blocks in czi file
|
||||
#[error("czi file has no valid blocks")]
|
||||
CziNoValidBlocks,
|
||||
#[error("f128 is not supported")]
|
||||
F128NotSupported,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//! structure. Additionally, it can automatically calculate an affine transform that corrects for
|
||||
//! chromatic aberrations etc. and apply it on the fly to the image.
|
||||
//!
|
||||
//! Currently, it supports imagej tif files, czi files, micromanager tif sequences and anything
|
||||
//! [bioformats](https://www.openmicroscopy.org/bio-formats/) can handle.
|
||||
//! Currently, it supports imagej and ome tif files, czi files, micromanager tif sequences and
|
||||
//! anything [bioformats](https://www.openmicroscopy.org/bio-formats/) can handle.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use ndarray::Array2;
|
||||
|
||||
+108
-109
@@ -17,7 +17,7 @@ use pyo3::exceptions::{
|
||||
PyIndexError, PyKeyError, PyNotImplementedError, PyTypeError, PyValueError,
|
||||
};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBytes, PyEllipsis, PyInt, PyNone, PySlice, PyString, PyTuple};
|
||||
use pyo3::types::{PyEllipsis, PyNone, PySlice, PyTuple};
|
||||
pub(crate) use pyo3::{
|
||||
Bound, IntoPyObject, IntoPyObjectExt, PyAny, PyRef, PyResult, Python, pyclass, pyfunction,
|
||||
pymethods,
|
||||
@@ -73,14 +73,15 @@ impl PyView {
|
||||
fn item(py: Python, view: View<IxDyn>, dtype: PixelType) -> PyResult<Bound<PyAny>> {
|
||||
macro_rules! item {
|
||||
($t:tt) => {
|
||||
view.into_dimensionality::<Ix0>()?
|
||||
Ok(view
|
||||
.into_dimensionality::<Ix0>()?
|
||||
.item::<$t>()?
|
||||
.into_pyobject(py)?
|
||||
.into_any()
|
||||
.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match dtype {
|
||||
match dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => item!(i8),
|
||||
PixelType::U8 => item!(u8),
|
||||
@@ -94,8 +95,8 @@ impl PyView {
|
||||
PixelType::U64 => item!(u64),
|
||||
PixelType::I128 => item!(i128),
|
||||
PixelType::U128 => item!(u128),
|
||||
PixelType::F128 => item!(f64),
|
||||
})
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,10 +124,10 @@ impl PyView {
|
||||
axes: &str,
|
||||
reader: Option<&str>,
|
||||
) -> PyResult<Self> {
|
||||
if path.is_instance_of::<Self>() {
|
||||
Ok(path.cast_into::<Self>()?.extract::<Self>()?)
|
||||
} else if path.is_instance_of::<PyBytes>() {
|
||||
let mut pyview: Self = from_bytes(&path.extract::<Vec<u8>>()?).map_err(Error::from)?;
|
||||
if let Ok(view) = path.extract::<Self>() {
|
||||
Ok(view)
|
||||
} else if let Ok(bytes) = path.extract::<Vec<u8>>() {
|
||||
let mut pyview: Self = from_bytes(&bytes).map_err(Error::from)?;
|
||||
pyview.ome = pyview.view.metadata()?;
|
||||
Ok(pyview)
|
||||
} else {
|
||||
@@ -135,7 +136,6 @@ impl PyView {
|
||||
builtins
|
||||
.getattr("str")?
|
||||
.call1((path,))?
|
||||
.cast_into::<PyString>()?
|
||||
.extract::<String>()?,
|
||||
);
|
||||
let axes = axes
|
||||
@@ -255,17 +255,17 @@ impl PyView {
|
||||
self.view.reader_name().to_string()
|
||||
}
|
||||
|
||||
/// reshape the view with a new axis order
|
||||
/// reshape the view with a new axis order, arguments order and copy cannot be used and will
|
||||
/// raise an exception if order != "C" or copy is False
|
||||
#[gen_stub(override_return_type(type_repr="Imread | numpy.NDArray", imports=("numpy", "numpy.typing")))]
|
||||
#[pyo3(signature = (*shape, order="C", copy=None))]
|
||||
fn reshape<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
shape: Vec<isize>,
|
||||
shape: Bound<'py, PyAny>,
|
||||
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",
|
||||
@@ -279,6 +279,28 @@ impl PyView {
|
||||
));
|
||||
}
|
||||
let n = self.view.shape().iter().product::<usize>();
|
||||
let shape = if let Ok(shape) = shape.try_iter() {
|
||||
shape
|
||||
.map(|i| {
|
||||
let i = i?;
|
||||
if let Ok(ii) = i.try_iter() {
|
||||
ii.map(|j| {
|
||||
let j = j?;
|
||||
j.extract::<isize>()
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()
|
||||
} else {
|
||||
Ok(vec![i.extract::<isize>()?])
|
||||
}
|
||||
})
|
||||
.collect::<PyResult<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![shape.extract::<isize>()?]
|
||||
};
|
||||
|
||||
let m = shape
|
||||
.iter()
|
||||
.fold(1, |i, j| if *j < 0 { i } else { i * (*j as usize) });
|
||||
@@ -316,14 +338,9 @@ impl PyView {
|
||||
.try_iter()?
|
||||
.map(|a| {
|
||||
let axis = a?;
|
||||
if axis.is_instance_of::<PyString>() {
|
||||
Ok(axis
|
||||
.cast_into::<PyString>()?
|
||||
.extract::<String>()?
|
||||
.parse::<Axis>()
|
||||
.map_err(Error::from)?)
|
||||
} else if axis.is_instance_of::<PyInt>() {
|
||||
let mut index = axis.cast_into::<PyInt>()?.extract::<isize>()?;
|
||||
if let Ok(axis) = axis.extract::<String>() {
|
||||
Ok(axis.parse::<Axis>().map_err(Error::from)?)
|
||||
} else if let Ok(mut index) = axis.extract::<isize>() {
|
||||
if index < 0 {
|
||||
index += view_axes.len() as isize;
|
||||
}
|
||||
@@ -347,14 +364,15 @@ impl PyView {
|
||||
if view.ndim() == 0 {
|
||||
macro_rules! squeeze {
|
||||
($t:tt) => {
|
||||
view.into_dimensionality::<Ix0>()?
|
||||
Ok(view
|
||||
.into_dimensionality::<Ix0>()?
|
||||
.item::<$t>()?
|
||||
.into_pyobject(py)?
|
||||
.into_any()
|
||||
.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => squeeze!(i8),
|
||||
PixelType::U8 => squeeze!(u8),
|
||||
@@ -368,8 +386,8 @@ impl PyView {
|
||||
PixelType::U128 => squeeze!(u128),
|
||||
PixelType::F32 => squeeze!(f32),
|
||||
PixelType::F64 => squeeze!(f64),
|
||||
PixelType::F128 => squeeze!(f64),
|
||||
})
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
} else {
|
||||
PyView {
|
||||
view,
|
||||
@@ -1191,14 +1209,15 @@ impl PyView {
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
macro_rules! get_frame {
|
||||
($t:tt) => {
|
||||
self.view
|
||||
Ok(self
|
||||
.view
|
||||
.get_frame::<$t, _>(c, z, t)?
|
||||
.into_pyarray(py)
|
||||
.into_any()
|
||||
.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => get_frame!(i8),
|
||||
PixelType::U8 => get_frame!(u8),
|
||||
@@ -1210,10 +1229,10 @@ impl PyView {
|
||||
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),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
/// flatten the view into a 1D numpy array
|
||||
@@ -1221,11 +1240,11 @@ impl PyView {
|
||||
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(self.view.flatten::<$t>()?.into_pyarray(py).into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => flatten!(i8),
|
||||
PixelType::U8 => flatten!(u8),
|
||||
@@ -1237,21 +1256,21 @@ impl PyView {
|
||||
PixelType::F64 => flatten!(f64),
|
||||
PixelType::I64 => flatten!(i64),
|
||||
PixelType::U64 => flatten!(u64),
|
||||
PixelType::I128 => flatten!(i64),
|
||||
PixelType::U128 => flatten!(u64),
|
||||
PixelType::F128 => flatten!(f64),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
/// convert the view to bytes
|
||||
fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
||||
macro_rules! to_bytes {
|
||||
($t:tt) => {
|
||||
self.view.to_bytes::<$t>()?
|
||||
Ok(self.view.to_bytes::<$t>()?)
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => to_bytes!(i8),
|
||||
PixelType::U8 => to_bytes!(u8),
|
||||
@@ -1263,10 +1282,10 @@ impl PyView {
|
||||
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),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
/// convert the view to bytes (alias for to_bytes)
|
||||
@@ -1335,19 +1354,15 @@ impl PyView {
|
||||
&self,
|
||||
#[gen_stub(override_type(type_repr = "int | str"))] axis: Bound<PyAny>,
|
||||
) -> PyResult<usize> {
|
||||
if axis.is_instance_of::<PyString>() {
|
||||
let axis = axis
|
||||
.cast_into::<PyString>()?
|
||||
.extract::<String>()?
|
||||
.parse::<Axis>()
|
||||
.map_err(Error::from)?;
|
||||
if let Ok(axis) = axis.extract::<String>() {
|
||||
let axis = axis.parse::<Axis>().map_err(Error::from)?;
|
||||
self.view
|
||||
.axes()
|
||||
.iter()
|
||||
.position(|a| *a == axis)
|
||||
.ok_or_else(|| PyValueError::new_err(format!("cannot find axis {:?}", axis)))
|
||||
} else if axis.is_instance_of::<PyInt>() {
|
||||
Ok(axis.cast_into::<PyInt>()?.extract::<usize>()?)
|
||||
} else if let Ok(axis) = axis.extract::<usize>() {
|
||||
Ok(axis)
|
||||
} else {
|
||||
Err(PyValueError::new_err("cannot convert to axis".to_string()))
|
||||
}
|
||||
@@ -1416,13 +1431,14 @@ impl PyView {
|
||||
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>())?
|
||||
Ok(py
|
||||
.detach(|| self.view.as_array_dyn::<$t>())?
|
||||
.into_pyarray(py)
|
||||
.into_any()
|
||||
.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => as_array!(i8),
|
||||
PixelType::U8 => as_array!(u8),
|
||||
@@ -1434,10 +1450,10 @@ impl PyView {
|
||||
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),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
/// the numpy dtype of the view
|
||||
@@ -1462,15 +1478,9 @@ impl PyView {
|
||||
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 => Err(PyTypeError::new_err(
|
||||
"type is f128, but this is not supported by rust",
|
||||
)),
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1524,11 +1534,11 @@ impl PyView {
|
||||
} else {
|
||||
macro_rules! max {
|
||||
($t:tt) => {
|
||||
self.view.max::<$t>()?.into_pyobject(py)?.into_any()
|
||||
Ok(self.view.max::<$t>()?.into_pyobject(py)?.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => max!(i8),
|
||||
PixelType::U8 => max!(u8),
|
||||
@@ -1540,10 +1550,10 @@ impl PyView {
|
||||
PixelType::F64 => max!(f64),
|
||||
PixelType::I64 => max!(i64),
|
||||
PixelType::U64 => max!(u64),
|
||||
PixelType::I128 => max!(i64),
|
||||
PixelType::U128 => max!(u64),
|
||||
PixelType::F128 => max!(f64),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1585,11 +1595,11 @@ impl PyView {
|
||||
} else {
|
||||
macro_rules! min {
|
||||
($t:tt) => {
|
||||
self.view.min::<$t>()?.into_pyobject(py)?.into_any()
|
||||
Ok(self.view.min::<$t>()?.into_pyobject(py)?.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
PixelType::Bool => todo!(),
|
||||
PixelType::I8 => min!(i8),
|
||||
PixelType::U8 => min!(u8),
|
||||
@@ -1601,10 +1611,10 @@ impl PyView {
|
||||
PixelType::F64 => min!(f64),
|
||||
PixelType::I64 => min!(i64),
|
||||
PixelType::U64 => min!(u64),
|
||||
PixelType::I128 => min!(i64),
|
||||
PixelType::U128 => min!(u64),
|
||||
PixelType::F128 => min!(f64),
|
||||
})
|
||||
PixelType::I128 => Err(PyTypeError::new_err("i128 type is not supported")),
|
||||
PixelType::U128 => Err(PyTypeError::new_err("u128 type is not supported")),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1701,20 +1711,20 @@ impl PyView {
|
||||
} else {
|
||||
macro_rules! sum {
|
||||
($t:tt) => {
|
||||
self.view.sum::<$t>()?.into_pyobject(py)?.into_any()
|
||||
Ok(self.view.sum::<$t>()?.into_pyobject(py)?.into_any())
|
||||
};
|
||||
}
|
||||
|
||||
Ok(match self.dtype {
|
||||
match self.dtype {
|
||||
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),
|
||||
PixelType::F128 => Err(PyTypeError::new_err("f128 type is not supported")),
|
||||
_ => sum!(i64),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1876,16 +1886,14 @@ impl PyView {
|
||||
path: Bound<'py, PyAny>,
|
||||
reader: Option<&str>,
|
||||
) -> PyResult<HashSet<usize>> {
|
||||
let path = if path.is_instance_of::<Self>() {
|
||||
let py_view: Self = path.cast_into::<Self>()?.extract::<Self>()?;
|
||||
py_view.view.path().to_owned()
|
||||
let path = if let Ok(view) = path.extract::<Self>() {
|
||||
view.view.path().to_owned()
|
||||
} else {
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
PathBuf::from(
|
||||
builtins
|
||||
.getattr("str")?
|
||||
.call1((path,))?
|
||||
.cast_into::<PyString>()?
|
||||
.extract::<String>()?,
|
||||
)
|
||||
};
|
||||
@@ -1903,16 +1911,14 @@ impl PyView {
|
||||
series: usize,
|
||||
reader: Option<&str>,
|
||||
) -> PyResult<HashSet<usize>> {
|
||||
let path = if path.is_instance_of::<Self>() {
|
||||
let py_view: Self = path.cast_into::<Self>()?.extract::<Self>()?;
|
||||
py_view.view.path().to_owned()
|
||||
let path = if let Ok(view) = path.extract::<Self>() {
|
||||
view.view.path().to_owned()
|
||||
} else {
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
PathBuf::from(
|
||||
builtins
|
||||
.getattr("str")?
|
||||
.call1((path,))?
|
||||
.cast_into::<PyString>()?
|
||||
.extract::<String>()?,
|
||||
)
|
||||
};
|
||||
@@ -1963,9 +1969,7 @@ impl PyView {
|
||||
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(),
|
||||
)),
|
||||
PixelType::F128 => Err(Error::F128NotSupported),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2077,8 +2081,7 @@ fn parse_slice(
|
||||
) -> PyResult<(Vec<SliceInfoElem>, usize)> {
|
||||
if s.is_none() {
|
||||
Ok((vec![SliceInfoElem::NewAxis], 0))
|
||||
} else if s.is_instance_of::<PyInt>() {
|
||||
let mut index = s.cast::<PyInt>()?.extract::<isize>()?;
|
||||
} else if let Ok(mut index) = s.extract::<isize>() {
|
||||
if index < 0 {
|
||||
index += t as isize;
|
||||
}
|
||||
@@ -2089,8 +2092,8 @@ fn parse_slice(
|
||||
)));
|
||||
}
|
||||
Ok((vec![SliceInfoElem::Index(index)], 1))
|
||||
} else if s.is_instance_of::<PySlice>() {
|
||||
let u = s.cast::<PySlice>()?.indices(t as isize)?;
|
||||
} else if let Ok(slice) = s.cast::<PySlice>() {
|
||||
let u = slice.indices(t as isize)?;
|
||||
Ok((
|
||||
vec![SliceInfoElem::Slice {
|
||||
start: u.start,
|
||||
@@ -2314,10 +2317,8 @@ impl PyShape {
|
||||
let (idx, is_idx): (Vec<usize>, bool) =
|
||||
if idx.is_instance_of::<PyNone>() || idx.is_instance_of::<PyEllipsis>() {
|
||||
((0..self.inner.order.len()).collect(), true)
|
||||
} else if idx.is_instance_of::<PySlice>() {
|
||||
let indices = idx
|
||||
.cast::<PySlice>()?
|
||||
.indices(self.inner.order.len() as isize)?;
|
||||
} else if let Ok(slice) = idx.cast::<PySlice>() {
|
||||
let indices = slice.indices(self.inner.order.len() as isize)?;
|
||||
(
|
||||
if indices.step > 0 {
|
||||
(indices.start..indices.stop)
|
||||
@@ -2332,8 +2333,7 @@ impl PyShape {
|
||||
},
|
||||
true,
|
||||
)
|
||||
} else if idx.is_instance_of::<PyString>() {
|
||||
let s = idx.cast::<PyString>()?.extract::<String>()?;
|
||||
} else if let Ok(s) = idx.extract::<String>() {
|
||||
(
|
||||
s.to_uppercase()
|
||||
.chars()
|
||||
@@ -2368,9 +2368,8 @@ impl PyShape {
|
||||
.collect::<PyResult<_>>()?,
|
||||
true,
|
||||
)
|
||||
} else if idx.is_instance_of::<PyInt>() {
|
||||
} else if let Ok(mut i) = idx.extract::<isize>() {
|
||||
let len = self.inner.order.len() as isize;
|
||||
let mut i = idx.cast::<PyInt>()?.extract::<isize>()?;
|
||||
if i < 0 {
|
||||
i += len
|
||||
};
|
||||
|
||||
+115
-25
@@ -14,7 +14,6 @@ use numpy::{
|
||||
use pyo3::IntoPyObjectExt;
|
||||
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyFloat;
|
||||
use pyo3_stub_gen::derive::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@@ -37,9 +36,10 @@ pub(crate) struct PyDefaultValue {
|
||||
#[pymethods]
|
||||
impl PyDefaultValue {
|
||||
#[new]
|
||||
fn new(values: Vec<f64>, weights: Vec<f64>) -> Self {
|
||||
#[pyo3(signature = (values, weights = None))]
|
||||
fn new(values: Vec<f64>, weights: Option<Vec<f64>>) -> Self {
|
||||
Self {
|
||||
inner: DefaultValue::new(values, weights),
|
||||
inner: DefaultValue::new(values, weights.unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ impl PyDefaultValue {
|
||||
fn weights(&self) -> Vec<f64> {
|
||||
self.inner.weights().to_vec()
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (Vec<f64>, Vec<f64>) {
|
||||
(self.inner.values().to_vec(), self.inner.weights().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
@@ -125,9 +129,9 @@ impl PyTransform {
|
||||
py: Python,
|
||||
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||
) -> PyResult<PyTransform> {
|
||||
if other.is_instance_of::<PyTransform>() {
|
||||
if let Ok(other) = other.extract::<PyTransform>() {
|
||||
Ok(PyTransform {
|
||||
inner: &self.inner * &other.extract::<PyTransform>()?.inner,
|
||||
inner: &self.inner * other.inner,
|
||||
})
|
||||
} else {
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
@@ -143,9 +147,9 @@ impl PyTransform {
|
||||
py: Python,
|
||||
#[gen_stub(override_type(type_repr = "Transform | float"))] other: &Bound<PyAny>,
|
||||
) -> PyResult<PyTransform> {
|
||||
if other.is_instance_of::<PyTransform>() {
|
||||
if let Ok(other) = other.extract::<PyTransform>() {
|
||||
Ok(PyTransform {
|
||||
inner: &other.extract::<PyTransform>()?.inner * &self.inner,
|
||||
inner: &other.inner * &self.inner,
|
||||
})
|
||||
} else {
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
@@ -803,29 +807,33 @@ impl PyView {
|
||||
fn set_default_interpolation_value(
|
||||
&mut self,
|
||||
channel: usize,
|
||||
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
#[gen_stub(override_type(type_repr="float | transforms.DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
value: Bound<PyAny>,
|
||||
) -> PyResult<()> {
|
||||
let channel_name = self.view.channel_name(channel)?.to_string();
|
||||
if value.is_instance_of::<PyTransform>() {
|
||||
if let Ok(value) = value.extract::<PyDefaultValue>() {
|
||||
self.view
|
||||
.transforms
|
||||
.default_value
|
||||
.insert(channel_name, value.extract::<PyDefaultValue>()?.inner);
|
||||
} else if value.is_instance_of::<PyFloat>() {
|
||||
self.view.transforms.default_value.insert(
|
||||
channel_name,
|
||||
value.cast::<PyFloat>()?.extract::<f64>()?.into(),
|
||||
);
|
||||
} else {
|
||||
.insert(channel_name, value.inner);
|
||||
} else if let Ok(value) = value.extract::<f64>() {
|
||||
self.view
|
||||
.transforms
|
||||
.default_value
|
||||
.insert(channel_name, value.into());
|
||||
} else if let Ok(value) = value.try_iter() {
|
||||
self.view.transforms.default_value.insert(
|
||||
channel_name,
|
||||
value
|
||||
.try_iter()?
|
||||
.map(|i| i?.cast::<PyFloat>()?.extract::<f64>())
|
||||
.map(|i| i?.extract::<f64>())
|
||||
.collect::<PyResult<Vec<_>>>()?
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"cannot parse value: {:?} into DefaultValue",
|
||||
value
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -834,7 +842,7 @@ impl PyView {
|
||||
fn with_default_interpolation_value(
|
||||
&self,
|
||||
channel: usize,
|
||||
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
#[gen_stub(override_type(type_repr="float | transforms.DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
value: Bound<PyAny>,
|
||||
) -> PyResult<Self> {
|
||||
let mut new = self.clone();
|
||||
@@ -999,17 +1007,22 @@ impl PyView {
|
||||
#[pymethods]
|
||||
impl PyTransforms {
|
||||
#[new]
|
||||
#[pyo3(signature = (channel = HashMap::new(), drift = Vec::new(), interpolation_order = None))]
|
||||
#[pyo3(signature = (channel = HashMap::new(), drift = Vec::new(), interpolation_order = None, default_value = None))]
|
||||
fn new(
|
||||
channel: HashMap<String, PyTransform>,
|
||||
drift: Vec<PyTransform>,
|
||||
interpolation_order: Option<usize>,
|
||||
default_value: Option<HashMap<String, PyDefaultValue>>,
|
||||
) -> PyResult<PyTransforms> {
|
||||
let channel = channel.into_iter().map(|(n, t)| (n, t.inner)).collect();
|
||||
let drift = drift.into_iter().map(|t| t.inner).collect();
|
||||
Ok(PyTransforms {
|
||||
inner: Transforms {
|
||||
default_value: Default::default(),
|
||||
default_value: default_value
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(k, v)| (k, v.inner))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
interpolation_order,
|
||||
channel,
|
||||
drift,
|
||||
@@ -1017,6 +1030,35 @@ impl PyTransforms {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn __getnewargs__(
|
||||
&self,
|
||||
) -> (
|
||||
HashMap<String, PyTransform>,
|
||||
Vec<PyTransform>,
|
||||
Option<usize>,
|
||||
HashMap<String, PyDefaultValue>,
|
||||
) {
|
||||
(
|
||||
self.inner
|
||||
.channel
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), PyTransform { inner: v.clone() }))
|
||||
.collect(),
|
||||
self.inner
|
||||
.drift
|
||||
.iter()
|
||||
.map(|i| PyTransform { inner: i.clone() })
|
||||
.collect(),
|
||||
self.inner.interpolation_order,
|
||||
self.inner
|
||||
.default_value
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), PyDefaultValue { inner: v.clone() }))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// the bspline order used for interpolation when using transforms,
|
||||
/// None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||
#[getter]
|
||||
@@ -1039,6 +1081,58 @@ impl PyTransforms {
|
||||
new
|
||||
}
|
||||
|
||||
/// the value of the interpolated image outside the data
|
||||
fn get_default_interpolation_value(&self, channel_name: &str) -> PyResult<PyDefaultValue> {
|
||||
Ok(PyDefaultValue {
|
||||
inner: self
|
||||
.inner
|
||||
.default_value
|
||||
.get(channel_name)
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// the value of the interpolated image outside the data
|
||||
fn set_default_interpolation_value(
|
||||
&mut self,
|
||||
channel_name: String,
|
||||
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
value: Bound<PyAny>,
|
||||
) -> PyResult<()> {
|
||||
if let Ok(value) = value.extract::<PyDefaultValue>() {
|
||||
self.inner.default_value.insert(channel_name, value.inner);
|
||||
} else if let Ok(value) = value.extract::<f64>() {
|
||||
self.inner.default_value.insert(channel_name, value.into());
|
||||
} else if let Ok(value) = value.try_iter() {
|
||||
self.inner.default_value.insert(
|
||||
channel_name,
|
||||
value
|
||||
.map(|i| i?.extract::<f64>())
|
||||
.collect::<PyResult<Vec<_>>>()?
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
return Err(PyValueError::new_err(format!(
|
||||
"cannot parse value: {:?} into DefaultValue",
|
||||
value
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// the value of the interpolated image outside the data
|
||||
fn with_default_interpolation_value(
|
||||
&self,
|
||||
channel_name: String,
|
||||
#[gen_stub(override_type(type_repr="float | DefaultValue | collections.abc.Sequence[float]", imports=("collections.abc")))]
|
||||
value: Bound<PyAny>,
|
||||
) -> PyResult<Self> {
|
||||
let mut new = self.clone();
|
||||
new.set_default_interpolation_value(channel_name, value)?;
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_channel_transforms(&self) -> HashMap<String, PyTransform> {
|
||||
self.inner
|
||||
@@ -1069,10 +1163,6 @@ impl PyTransforms {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self) -> (HashMap<String, PyTransform>, Vec<PyTransform>) {
|
||||
(self.get_channel_transforms(), self.get_drift_transforms())
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
pub(crate) fn load(path: PathBuf) -> PyResult<PyTransforms> {
|
||||
Ok(PyTransforms {
|
||||
|
||||
@@ -128,7 +128,7 @@ pub enum PixelType {
|
||||
I128,
|
||||
/// unsigned 128-bit integer
|
||||
U128,
|
||||
/// 128-bit float (emulated)
|
||||
/// 128-bit float (not supported)
|
||||
F128,
|
||||
}
|
||||
|
||||
@@ -170,12 +170,10 @@ pub enum ArrayT<D: Dimension> {
|
||||
I64(Array<i64, D>),
|
||||
/// unsigned 64-bit integer array
|
||||
U64(Array<u64, D>),
|
||||
/// signed 128-bit integer array
|
||||
/// signed 128-bit integer array (not supported by numpy)
|
||||
I128(Array<i128, D>),
|
||||
/// unsigned 128-bit integer array
|
||||
/// unsigned 128-bit integer array (not supported by numpy)
|
||||
U128(Array<u128, D>),
|
||||
/// 128-bit float array (emulated as f64)
|
||||
F128(Array<f64, D>),
|
||||
}
|
||||
|
||||
/// type alias for a single frame (2D image)
|
||||
@@ -365,7 +363,7 @@ macro_rules! try_into_array_body {
|
||||
T::zero()
|
||||
})
|
||||
}),
|
||||
ArrayT::F64(v) | ArrayT::F128(v) => v.$map(|x| {
|
||||
ArrayT::F64(v) => v.$map(|x| {
|
||||
T::from_f64(x).unwrap_or_else(|| {
|
||||
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||
T::zero()
|
||||
|
||||
@@ -521,7 +521,7 @@ impl BioFormatsJavaReader {
|
||||
PixelType::U64 => 8,
|
||||
PixelType::I128 => 16,
|
||||
PixelType::U128 => 16,
|
||||
PixelType::F128 => 8,
|
||||
PixelType::F128 => 16,
|
||||
};
|
||||
Ok(bytes
|
||||
.chunks(chunk_size)
|
||||
@@ -587,7 +587,7 @@ impl BioFormatsJavaReader {
|
||||
(PixelType::U64, true) => get_frame!(u64, <8),
|
||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||
(PixelType::F128, true) => Err(Error::F128NotSupported),
|
||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||
@@ -601,7 +601,7 @@ impl BioFormatsJavaReader {
|
||||
(PixelType::U64, false) => get_frame!(u64, >8),
|
||||
(PixelType::I128, false) => get_frame!(i128, >16),
|
||||
(PixelType::U128, false) => get_frame!(u128, >16),
|
||||
(PixelType::F128, false) => get_frame!(f64, >8),
|
||||
(PixelType::F128, false) => Err(Error::F128NotSupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ impl BioFormatsRustReader {
|
||||
PixelType::U64 => 8,
|
||||
PixelType::I128 => 16,
|
||||
PixelType::U128 => 16,
|
||||
PixelType::F128 => 8,
|
||||
PixelType::F128 => 16,
|
||||
};
|
||||
Ok(bytes
|
||||
.chunks(chunk_size)
|
||||
@@ -206,7 +206,7 @@ impl BioFormatsRustReader {
|
||||
(PixelType::U64, true) => get_frame!(u64, <8),
|
||||
(PixelType::I128, true) => get_frame!(i128, <16),
|
||||
(PixelType::U128, true) => get_frame!(u128, <16),
|
||||
(PixelType::F128, true) => get_frame!(f64, <8),
|
||||
(PixelType::F128, true) => Err(Error::F128NotSupported),
|
||||
(PixelType::Bool, false) => get_frame!(bool, >1),
|
||||
(PixelType::I8, false) => get_frame!(i8, >1),
|
||||
(PixelType::U8, false) => get_frame!(u8, >1),
|
||||
@@ -220,7 +220,7 @@ impl BioFormatsRustReader {
|
||||
(PixelType::U64, false) => get_frame!(u64, >8),
|
||||
(PixelType::I128, false) => get_frame!(i128, >16),
|
||||
(PixelType::U128, false) => get_frame!(u128, >16),
|
||||
(PixelType::F128, false) => get_frame!(f64, >8),
|
||||
(PixelType::F128, false) => Err(Error::F128NotSupported),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1132,7 +1132,7 @@ impl Reader for CziReader {
|
||||
PixelType::U64 => get_frame!(u64, 8),
|
||||
PixelType::I128 => get_frame!(i128, 16),
|
||||
PixelType::U128 => get_frame!(u128, 16),
|
||||
PixelType::F128 => get_frame!(f64, 8),
|
||||
PixelType::F128 => Err(Error::F128NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ where
|
||||
PixelType::U64 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||
PixelType::I128 => self.save_as_tiff_with_type::<i64, P>(path, options)?,
|
||||
PixelType::U128 => self.save_as_tiff_with_type::<u64, P>(path, options)?,
|
||||
PixelType::F128 => self.save_as_tiff_with_type::<f64, P>(path, options)?,
|
||||
PixelType::F128 => Err(Error::F128NotSupported)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::readers::{DynReader, Frame, PixelType, Reader};
|
||||
use crate::view::View;
|
||||
use image_registration::{bspline::DefaultValue, filter, transform::Transform};
|
||||
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, array, concatenate, s};
|
||||
use rayon::iter::{ParallelIterator, IntoParallelIterator};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -64,8 +64,10 @@ pub struct Transforms {
|
||||
pub(crate) interpolation_order: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub(crate) default_value: DefaultValues,
|
||||
/// a transform for each channel by channel name
|
||||
#[serde(default)]
|
||||
pub channel: HashMap<String, Transform<IxDyn>>,
|
||||
/// a transform for each time point
|
||||
#[serde(default)]
|
||||
pub drift: Vec<Transform<IxDyn>>,
|
||||
}
|
||||
@@ -104,6 +106,7 @@ impl Serialize for Transforms {
|
||||
#[derive(Serialize)]
|
||||
struct Binary<'a> {
|
||||
interpolation_order: Option<usize>,
|
||||
default_value: &'a DefaultValues,
|
||||
channel: &'a HashMap<String, Transform<IxDyn>>,
|
||||
drift: &'a Vec<Transform<IxDyn>>,
|
||||
}
|
||||
@@ -117,6 +120,7 @@ impl Serialize for Transforms {
|
||||
} else {
|
||||
Binary {
|
||||
interpolation_order: self.interpolation_order,
|
||||
default_value: &self.default_value,
|
||||
channel: &self.channel,
|
||||
drift: &self.drift,
|
||||
}
|
||||
@@ -125,15 +129,21 @@ impl Serialize for Transforms {
|
||||
}
|
||||
}
|
||||
|
||||
/// the kind of corrections the transforms are meant to correct for
|
||||
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||
pub enum TransformKind {
|
||||
/// no tranforms
|
||||
None,
|
||||
/// correct warp between channels, ex: camera/filter misalignment, chromatic aberrations
|
||||
Channel,
|
||||
/// drift in the miscrocope between time points
|
||||
Drift,
|
||||
/// both warp between channels and drift
|
||||
Both,
|
||||
}
|
||||
|
||||
impl Transforms {
|
||||
/// the kind of corrections the transforms are meant to correct for
|
||||
pub fn kind(&self) -> TransformKind {
|
||||
match (self.channel.is_empty(), self.drift.is_empty()) {
|
||||
(true, true) => TransformKind::None,
|
||||
@@ -143,6 +153,7 @@ impl Transforms {
|
||||
}
|
||||
}
|
||||
|
||||
/// load the transforms from a yml file
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
let mut file = std::fs::File::open(&path)?;
|
||||
Ok(
|
||||
@@ -157,6 +168,7 @@ impl Transforms {
|
||||
)
|
||||
}
|
||||
|
||||
/// save the transforms to a yml file
|
||||
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
@@ -166,6 +178,7 @@ impl Transforms {
|
||||
Ok(yaml_serde::to_writer(file, self)?)
|
||||
}
|
||||
|
||||
/// helper function to eiter load transforms from file or calculate and save them
|
||||
pub fn calculate_transforms_from<P, Q, R>(
|
||||
transform_file: Option<P>,
|
||||
bead_files: Option<&[Q]>,
|
||||
@@ -246,18 +259,21 @@ impl Transforms {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// calculate drift transforms in yx using image file
|
||||
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()
|
||||
}
|
||||
|
||||
/// calculate drift transforms in zyx using image file
|
||||
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()
|
||||
}
|
||||
|
||||
/// calculate channel transforms in yx using image file
|
||||
pub fn calculate_channel_transforms_2d<P: AsRef<Path>>(
|
||||
bead_files: &[P],
|
||||
main_channel: usize,
|
||||
@@ -270,44 +286,54 @@ impl Transforms {
|
||||
let view = View::<_, DynReader>::from_path(file)?;
|
||||
transforms.push(view.calculate_channel_transforms_2d(main_channel)?)
|
||||
}
|
||||
let channels = transforms
|
||||
let transforms = transforms.into_iter().flat_map(|map| map.into_iter()).fold(
|
||||
HashMap::new(),
|
||||
|mut acc, (channel, transform)| {
|
||||
acc.entry(channel)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(transform * &default_transform);
|
||||
acc
|
||||
},
|
||||
);
|
||||
Ok(transforms
|
||||
.iter()
|
||||
.flat_map(|t| t.keys())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut average_transforms = HashMap::new();
|
||||
for channel in channels {
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some((&t[channel] * &default_transform).matrix())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let matrix = s / (n as f64);
|
||||
let (s, n) = transforms
|
||||
.iter()
|
||||
.filter_map(|t| {
|
||||
if t.contains_key(channel) {
|
||||
Some(((&t[channel] * &default_transform).matrix() - &matrix).powi(2))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold((Array2::zeros((3, 3)), 0), |(s, n), t| (s + t, n + 1));
|
||||
let dmatrix = (s / n as f64).sqrt();
|
||||
average_transforms.insert(
|
||||
channel.clone(),
|
||||
Transform::default()
|
||||
.with_matrix(matrix.view())
|
||||
.with_dmatrix(dmatrix.view()),
|
||||
);
|
||||
}
|
||||
Ok(average_transforms)
|
||||
.map(|(channel, ts)| {
|
||||
let n = ts.len();
|
||||
let parameters = ts
|
||||
.iter()
|
||||
.fold(vec![0.0; 6], |mut acc, t| {
|
||||
for (a, p) in acc.iter_mut().zip(&t.parameters) {
|
||||
*a += p;
|
||||
}
|
||||
acc
|
||||
})
|
||||
.iter()
|
||||
.map(|i| i / n as f64)
|
||||
.collect::<Vec<_>>();
|
||||
let dparameters = if n == 1 {
|
||||
ts[0].dparameters.clone()
|
||||
} else {
|
||||
ts.iter()
|
||||
.fold(vec![0.0; 6], |mut acc, t| {
|
||||
for ((a, p), dp) in acc.iter_mut().zip(¶meters).zip(&t.dparameters)
|
||||
{
|
||||
*a += (dp - p).powi(2);
|
||||
}
|
||||
acc
|
||||
})
|
||||
.iter()
|
||||
.map(|i| (i / n as f64).sqrt())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
(
|
||||
channel.to_string(),
|
||||
Transform::new(parameters).with_dparameters(dparameters),
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// calculate channel transforms in zyx using image file
|
||||
pub fn calculate_channel_transforms_3d<P: AsRef<Path>>(
|
||||
bead_files: &[P],
|
||||
main_channel: usize,
|
||||
@@ -357,6 +383,7 @@ impl Transforms {
|
||||
Ok(average_transforms)
|
||||
}
|
||||
|
||||
/// get the combination of channel and drift transforms for a certain channel and time point
|
||||
pub fn get_transform(
|
||||
&self,
|
||||
channel: &str,
|
||||
@@ -385,22 +412,26 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
self.transforms = transforms;
|
||||
}
|
||||
|
||||
/// apply transforms to a new view
|
||||
pub fn with_transforms(&self, transforms: Transforms) -> Self {
|
||||
let mut new = self.clone();
|
||||
new.transforms = transforms;
|
||||
new
|
||||
}
|
||||
|
||||
/// load the transforms from a yml file and apply to a new view
|
||||
pub fn with_transforms_from_yaml(mut self, path: &Path) -> Result<Self, Error> {
|
||||
self.set_transforms(Transforms::load(path)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// load the transforms from a yml file and apply to the view
|
||||
pub fn load_transforms_from_yaml(&mut self, path: &Path) -> Result<(), Error> {
|
||||
self.set_transforms(Transforms::load(path)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// calculate channel transforms in yx using this image view
|
||||
pub fn calculate_channel_transforms_2d(
|
||||
&self,
|
||||
main_channel: usize,
|
||||
@@ -431,6 +462,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
Ok(transforms)
|
||||
}
|
||||
|
||||
/// calculate channel transforms in zyx using this image view
|
||||
pub fn calculate_channel_transforms_3d(
|
||||
&self,
|
||||
main_channel: usize,
|
||||
@@ -459,6 +491,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
Ok(transforms)
|
||||
}
|
||||
|
||||
/// calculate drift transforms in yx using this image view
|
||||
pub fn calculate_drift_transforms_2d(&self) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
let mut view = self.clone().into_dyn();
|
||||
let axes = view.axes();
|
||||
@@ -541,10 +574,12 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// calculate drift transforms in zyx using this image view
|
||||
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// retrieve a frame and warp it according to the transform
|
||||
pub(crate) fn get_transformed_frame(
|
||||
&self,
|
||||
c: usize,
|
||||
@@ -598,7 +633,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
Frame::U128(frame) => interpolate!(frame, U128, u128),
|
||||
Frame::F32(frame) => interpolate!(frame, F32, f32),
|
||||
Frame::F64(frame) => interpolate!(frame, F64, f64),
|
||||
Frame::F128(frame) => interpolate!(frame, F128, f64),
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
@@ -701,7 +735,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
Frame::U64(_) => interpolate!(U64, u64),
|
||||
Frame::I128(_) => interpolate!(I128, i128),
|
||||
Frame::U128(_) => interpolate!(U128, u128),
|
||||
Frame::F128(_) => interpolate!(F128, f64),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -720,7 +753,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
PixelType::U64 => Frame::U64(Array2::zeros((shape.y, shape.x))),
|
||||
PixelType::I128 => Frame::I128(Array2::zeros((shape.y, shape.x))),
|
||||
PixelType::U128 => Frame::U128(Array2::zeros((shape.y, shape.x))),
|
||||
PixelType::F128 => Frame::F128(Array2::zeros((shape.y, shape.x))),
|
||||
PixelType::F128 => Err(Error::F128NotSupported)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2049,4 +2049,27 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transforms")]
|
||||
#[test]
|
||||
fn serde_transforms_data() -> Result<(), Error> {
|
||||
use crate::transforms::Transforms;
|
||||
use image_registration::{bspline::DefaultValue, transform::Transform};
|
||||
use std::collections::HashMap;
|
||||
let t = Transforms {
|
||||
interpolation_order: Some(3),
|
||||
default_value: HashMap::from([(
|
||||
"Cy3".to_string(),
|
||||
DefaultValue::new(vec![1.0, 2.0, 3.0], vec![0.2, 0.5, 0.3]),
|
||||
)]),
|
||||
channel: HashMap::from([(
|
||||
"Cy3".to_string(),
|
||||
Transform::new(vec![1.0, 0.0, 0.0, 1.0, 0.1, 0.2]),
|
||||
)]),
|
||||
drift: vec![Transform::new(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.5])],
|
||||
};
|
||||
let t2: Transforms = from_bytes(&to_stdvec(&t)?)?;
|
||||
assert_eq!(t, t2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user