- default value argument for interpolation
This commit is contained in:
1 parent
22e8ab1dd2
commit
8a4c965477
9 files changed
+342
-94
No files matched your search
+1
-1
Submodule image-registration updated: b12982da48...9257dd1fcf.
@@ -2,6 +2,8 @@
|
||||
# ruff: noqa: E501, F401, F403, F405
|
||||
|
||||
import builtins
|
||||
import collections
|
||||
import collections.abc
|
||||
import os
|
||||
import pathlib
|
||||
import typing
|
||||
@@ -235,6 +237,18 @@ 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
|
||||
@@ -306,12 +320,12 @@ class Imread:
|
||||
*shape: typing.Any,
|
||||
order: builtins.str = "C",
|
||||
copy: typing.Optional[builtins.bool] = None,
|
||||
) -> typing.Any:
|
||||
) -> Imread | numpy.NDArray:
|
||||
r"""
|
||||
reshape the view with a new axis order
|
||||
"""
|
||||
def squeeze(
|
||||
self, axes: str | typing.Sequence[int | str] = None
|
||||
self, axes: str | collections.abc.Sequence[int | str] = None
|
||||
) -> Imread | float | int: ...
|
||||
def close(self) -> None:
|
||||
r"""
|
||||
@@ -635,7 +649,7 @@ class Imread:
|
||||
swap two axes
|
||||
"""
|
||||
def transpose(
|
||||
self, axes: typing.Optional[typing.Sequence[int | str] | str] = None
|
||||
self, axes: typing.Optional[collections.abc.Sequence[int | str] | str] = None
|
||||
) -> Imread:
|
||||
r"""
|
||||
permute the order of the axes
|
||||
@@ -781,6 +795,12 @@ class Imread:
|
||||
the bspline order used for interpolation when using transforms
|
||||
None -> 0 (nearest neighbor) for int dtypes, 3 (cubic) for float dtypes
|
||||
"""
|
||||
def with_default_interpolation_value(
|
||||
self, value: float | DefaultValue | collections.abc.Sequence[float]
|
||||
) -> Imread:
|
||||
r"""
|
||||
the value of the interpolated image outside the data
|
||||
"""
|
||||
def with_transform(
|
||||
self,
|
||||
channels: builtins.bool = True,
|
||||
|
||||
@@ -10,6 +10,7 @@ import numpy
|
||||
import numpy.typing
|
||||
|
||||
__all__ = [
|
||||
"DefaultValue",
|
||||
"Optimizer",
|
||||
"RegistrationStep",
|
||||
"SamplingArg",
|
||||
@@ -18,6 +19,18 @@ __all__ = [
|
||||
"Transforms",
|
||||
]
|
||||
|
||||
class DefaultValue:
|
||||
@property
|
||||
def values(self) -> builtins.list[builtins.float]: ...
|
||||
@property
|
||||
def weights(self) -> builtins.list[builtins.float]: ...
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(
|
||||
cls,
|
||||
values: typing.Sequence[builtins.float],
|
||||
weights: typing.Sequence[builtins.float],
|
||||
) -> DefaultValue: ...
|
||||
|
||||
class Optimizer:
|
||||
def __eq__(self, other: builtins.object, /) -> builtins.bool: ...
|
||||
def __new__(cls, optimizer: builtins.str) -> Optimizer: ...
|
||||
@@ -160,13 +173,22 @@ class Transform:
|
||||
self, theta: builtins.float, center: typing.Sequence[builtins.float]
|
||||
) -> Transform: ...
|
||||
def interpolate(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
self,
|
||||
order: builtins.int,
|
||||
image: numpy.typing.ArrayLike,
|
||||
default_value: typing.Optional[DefaultValue] = None,
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def interpolate_par(
|
||||
self, order: builtins.int, image: numpy.typing.ArrayLike
|
||||
self,
|
||||
order: builtins.int,
|
||||
image: numpy.typing.ArrayLike,
|
||||
default_value: typing.Optional[DefaultValue] = None,
|
||||
) -> numpy.typing.NDArray[numpy.float64]: ...
|
||||
def frame(
|
||||
self, image: numpy.typing.ArrayLike, default: typing.Optional[typing.Any] = None
|
||||
self,
|
||||
image: numpy.typing.ArrayLike,
|
||||
default: typing.Optional[typing.Any] = None,
|
||||
default_value: typing.Optional[DefaultValue] = None,
|
||||
) -> numpy.ndarray:
|
||||
r"""
|
||||
backwards compatibility, default parameter is not implemented
|
||||
|
||||
+39
-11
@@ -1,7 +1,7 @@
|
||||
use crate::axes::{Axis, Operation};
|
||||
use crate::readers::Frame;
|
||||
#[cfg(feature = "transforms")]
|
||||
use crate::transforms::Transforms;
|
||||
use crate::transforms::{DefaultValues, Transforms};
|
||||
#[cfg(feature = "transforms")]
|
||||
use image_registration::transform::Transform;
|
||||
use indexmap::{Equivalent, IndexMap};
|
||||
@@ -140,14 +140,32 @@ impl ReaderKeyRef<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct FrameKey {
|
||||
pub(crate) reader_key: ReaderKey,
|
||||
pub(crate) reader: ReaderKey,
|
||||
pub(crate) c: usize,
|
||||
pub(crate) z: usize,
|
||||
pub(crate) t: usize,
|
||||
#[cfg(feature = "transforms")]
|
||||
pub(crate) transform: Option<(Transform<IxDyn>, usize)>,
|
||||
pub(crate) transform: Option<(Transform<IxDyn>, usize, DefaultValues)>,
|
||||
}
|
||||
|
||||
impl Hash for FrameKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.c.hash(state);
|
||||
self.z.hash(state);
|
||||
self.t.hash(state);
|
||||
#[cfg(feature = "transforms")]
|
||||
if let Some(transform) = self.transform.as_ref() {
|
||||
transform.0.hash(state);
|
||||
transform.1.hash(state);
|
||||
transform.2.iter().for_each(|(k, v)| {
|
||||
k.hash(state);
|
||||
v.hash(state);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||
@@ -158,7 +176,7 @@ pub(crate) struct FrameKeyRef<'a> {
|
||||
pub(crate) z: usize,
|
||||
pub(crate) t: usize,
|
||||
#[cfg(feature = "transforms")]
|
||||
pub(crate) transform: Option<(Cow<'a, Transform<IxDyn>>, usize)>,
|
||||
pub(crate) transform: Option<(Cow<'a, Transform<IxDyn>>, usize, Cow<'a, DefaultValues>)>,
|
||||
}
|
||||
|
||||
impl Hash for FrameKeyRef<'_> {
|
||||
@@ -168,14 +186,21 @@ impl Hash for FrameKeyRef<'_> {
|
||||
self.z.hash(state);
|
||||
self.t.hash(state);
|
||||
#[cfg(feature = "transforms")]
|
||||
self.transform.hash(state);
|
||||
if let Some(transform) = self.transform.as_ref() {
|
||||
transform.0.hash(state);
|
||||
transform.1.hash(state);
|
||||
transform.2.iter().for_each(|(k, v)| {
|
||||
k.hash(state);
|
||||
v.hash(state);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||
let FrameKey {
|
||||
reader_key,
|
||||
reader: reader_key,
|
||||
c,
|
||||
z,
|
||||
t,
|
||||
@@ -188,8 +213,11 @@ impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
&& self.c == *c
|
||||
&& self.z == *z
|
||||
&& self.t == *t
|
||||
&& self.transform.as_ref().map(|(t, i)| (t.as_ref(), i))
|
||||
== transform.as_ref().map(|(t, i)| (t, i))
|
||||
&& self
|
||||
.transform
|
||||
.as_ref()
|
||||
.map(|(t, i, d)| (t.as_ref(), i, d.as_ref()))
|
||||
== transform.as_ref().map(|(t, i, d)| (t, i, d))
|
||||
}
|
||||
#[cfg(not(feature = "transforms"))]
|
||||
{
|
||||
@@ -201,7 +229,7 @@ impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
impl FrameKeyRef<'_> {
|
||||
pub(crate) fn to_owned(&self) -> FrameKey {
|
||||
FrameKey {
|
||||
reader_key: self.reader.to_owned(),
|
||||
reader: self.reader.to_owned(),
|
||||
c: self.c,
|
||||
z: self.z,
|
||||
t: self.t,
|
||||
@@ -209,7 +237,7 @@ impl FrameKeyRef<'_> {
|
||||
transform: self
|
||||
.transform
|
||||
.as_ref()
|
||||
.map(|(t, i)| (t.clone().into_owned(), *i)),
|
||||
.map(|(t, i, d)| (t.clone().into_owned(), *i, d.clone().into_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@ mod ndbioimage_rs {
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PyTransforms;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PyDefaultValue;
|
||||
|
||||
#[pymodule_export]
|
||||
use super::super::transforms::PyRegistrationStep;
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ impl PyView {
|
||||
}
|
||||
|
||||
/// reshape the view with a new axis order
|
||||
#[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,
|
||||
@@ -297,13 +298,13 @@ impl PyView {
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub(override_return_type(type_repr="Imread | float | int", imports=("numpy", "numpy.typing")))]
|
||||
#[gen_stub(override_return_type(type_repr = "Imread | float | int"))]
|
||||
#[pyo3(signature = (axes = None))]
|
||||
fn squeeze<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
#[gen_stub(
|
||||
override_type(type_repr="str | typing.Sequence[int | str]", imports=("typing"))
|
||||
override_type(type_repr="str | collections.abc.Sequence[int | str]", imports=("collections"))
|
||||
)]
|
||||
axes: Option<Bound<'py, PyAny>>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
@@ -1382,7 +1383,7 @@ impl PyView {
|
||||
#[pyo3(signature = (axes = None))]
|
||||
fn transpose(
|
||||
&self,
|
||||
#[gen_stub(override_type(type_repr="typing.Optional[typing.Sequence[int | str] | str]", imports=("typing")
|
||||
#[gen_stub(override_type(type_repr="typing.Optional[collections.abc.Sequence[int | str] | str]", imports=("typing", "collections.abc")
|
||||
))]
|
||||
axes: Option<Bound<PyAny>>,
|
||||
) -> PyResult<Self> {
|
||||
|
||||
+153
-37
@@ -1,6 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::py::imread::PyView;
|
||||
use crate::transforms::Transforms;
|
||||
use image_registration::bspline::DefaultValue;
|
||||
use image_registration::metric::{SamplingArg, Sigma};
|
||||
use image_registration::register::{Optimizer, RegistrationStep};
|
||||
use image_registration::transform::Transform;
|
||||
@@ -13,11 +14,46 @@ 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;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
from_py_object,
|
||||
eq,
|
||||
name = "DefaultValue",
|
||||
module = "ndbioimage.transforms"
|
||||
)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct PyDefaultValue {
|
||||
inner: DefaultValue,
|
||||
}
|
||||
|
||||
#[gen_stub_pymethods]
|
||||
#[pymethods]
|
||||
impl PyDefaultValue {
|
||||
#[new]
|
||||
fn new(values: Vec<f64>, weights: Vec<f64>) -> Self {
|
||||
Self {
|
||||
inner: DefaultValue::new(values, weights),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn values(&self) -> Vec<f64> {
|
||||
self.inner.values().to_vec()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn weights(&self) -> Vec<f64> {
|
||||
self.inner.weights().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[gen_stub_pyclass]
|
||||
#[pyclass(
|
||||
subclass,
|
||||
@@ -230,68 +266,86 @@ impl PyTransform {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyo3(signature = (order, image, default_value = None))]
|
||||
fn interpolate<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
order: usize,
|
||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||
default_value: Option<PyDefaultValue>,
|
||||
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||
let image = image.as_array();
|
||||
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||
Ok(py
|
||||
.detach(|| match order {
|
||||
0 => Ok(self
|
||||
.inner
|
||||
.interpolate::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
1 => Ok(self
|
||||
.inner
|
||||
.interpolate::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
2 => Ok(self
|
||||
.inner
|
||||
.interpolate::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
3 => Ok(self
|
||||
.inner
|
||||
.interpolate::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
4 => Ok(self
|
||||
.inner
|
||||
.interpolate::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
5 => Ok(self
|
||||
.inner
|
||||
.interpolate::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
0 => Ok(self.inner.interpolate::<0, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
1 => Ok(self.inner.interpolate::<1, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
2 => Ok(self.inner.interpolate::<2, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
3 => Ok(self.inner.interpolate::<3, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
4 => Ok(self.inner.interpolate::<4, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
5 => Ok(self.inner.interpolate::<5, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||
})?
|
||||
.map_err(Error::from)?
|
||||
.into_pyarray(py))
|
||||
}
|
||||
|
||||
#[pyo3(signature = (order, image, default_value = None))]
|
||||
fn interpolate_par<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
order: usize,
|
||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||
image: PyArrayLikeDyn<f64, AllowTypeChange>,
|
||||
default_value: Option<PyDefaultValue>,
|
||||
) -> PyResult<Bound<'py, PyArrayDyn<f64>>> {
|
||||
let image = image.as_array();
|
||||
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||
Ok(py
|
||||
.detach(|| match order {
|
||||
0 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<0, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
1 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<1, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
2 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<2, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
3 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<3, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
4 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<4, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
5 => Ok(self
|
||||
.inner
|
||||
.interpolate_par::<5, _, _>(image.into_dimensionality().map_err(Error::from)?)),
|
||||
0 => Ok(self.inner.interpolate_par::<0, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
1 => Ok(self.inner.interpolate_par::<1, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
2 => Ok(self.inner.interpolate_par::<2, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
3 => Ok(self.inner.interpolate_par::<3, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
4 => Ok(self.inner.interpolate_par::<4, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
5 => Ok(self.inner.interpolate_par::<5, _, _>(
|
||||
image.into_dimensionality().map_err(Error::from)?,
|
||||
&default_value,
|
||||
)),
|
||||
_ => Err(PyValueError::new_err("order must be 0 <= order < 6")),
|
||||
})?
|
||||
.map_err(Error::from)?
|
||||
@@ -300,13 +354,14 @@ impl PyTransform {
|
||||
|
||||
/// backwards compatibility, default parameter is not implemented
|
||||
#[gen_stub(override_return_type(type_repr="numpy.ndarray", imports=("numpy")))]
|
||||
#[pyo3(signature = (image, default = None))]
|
||||
#[pyo3(signature = (image, default = None, default_value = None))]
|
||||
fn frame<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
#[gen_stub(override_type(type_repr="numpy.typing.ArrayLike", imports=("numpy.typing")))]
|
||||
image: Bound<'py, PyUntypedArray>,
|
||||
default: Option<Bound<'py, PyAny>>,
|
||||
default_value: Option<PyDefaultValue>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
if default.is_some() {
|
||||
return Err(PyNotImplementedError::new_err(
|
||||
@@ -320,6 +375,7 @@ impl PyTransform {
|
||||
self.inner.ndim()
|
||||
)));
|
||||
}
|
||||
let default_value = default_value.map(|i| i.inner).unwrap_or_default();
|
||||
|
||||
macro_rules! interp {
|
||||
($order:expr, $t:tt) => {
|
||||
@@ -330,6 +386,7 @@ impl PyTransform {
|
||||
.into_dimensionality()
|
||||
.map_err(Error::from)?
|
||||
.view(),
|
||||
&default_value,
|
||||
)
|
||||
.map_err(Error::from)?
|
||||
.mapv(|i| i as $t)
|
||||
@@ -347,6 +404,7 @@ impl PyTransform {
|
||||
.into_dimensionality()
|
||||
.map_err(Error::from)?
|
||||
.view(),
|
||||
&default_value,
|
||||
)
|
||||
.map_err(Error::from)?
|
||||
.mapv(|i| i > 0.5)
|
||||
@@ -727,6 +785,63 @@ impl PyView {
|
||||
new
|
||||
}
|
||||
|
||||
/// the value of the interpolated image outside the data
|
||||
fn get_default_interpolation_value(&self, channel: usize) -> PyResult<PyDefaultValue> {
|
||||
let channel_name = self.view.channel_name(channel)?.to_string();
|
||||
Ok(PyDefaultValue {
|
||||
inner: self
|
||||
.view
|
||||
.transforms
|
||||
.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: usize,
|
||||
#[gen_stub(override_type(type_repr="float | 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>() {
|
||||
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 {
|
||||
self.view.transforms.default_value.insert(
|
||||
channel_name,
|
||||
value
|
||||
.try_iter()?
|
||||
.map(|i| i?.cast::<PyFloat>()?.extract::<f64>())
|
||||
.collect::<PyResult<Vec<_>>>()?
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// the value of the interpolated image outside the data
|
||||
fn with_default_interpolation_value(
|
||||
&self,
|
||||
channel: usize,
|
||||
#[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, value)?;
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||
/// backward compatibility
|
||||
#[cfg(feature = "transforms")]
|
||||
@@ -894,6 +1009,7 @@ impl PyTransforms {
|
||||
let drift = drift.into_iter().map(|t| t.inner).collect();
|
||||
Ok(PyTransforms {
|
||||
inner: Transforms {
|
||||
default_value: Default::default(),
|
||||
interpolation_order,
|
||||
channel,
|
||||
drift,
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
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::{array, s, Array2, Dimension, Ix2, Ix3, IxDyn, concatenate};
|
||||
use image_registration::{bspline::DefaultValue, filter, transform::Transform};
|
||||
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, array, concatenate, s};
|
||||
use rayon::iter::{ParallelIterator, IntoParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
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)]
|
||||
@@ -33,6 +32,7 @@ impl TryFrom<HashMap<String, TransformOld>> for Transforms {
|
||||
|
||||
fn try_from(t: HashMap<String, TransformOld>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
default_value: Default::default(),
|
||||
interpolation_order: None,
|
||||
channel: t
|
||||
.into_iter()
|
||||
@@ -56,11 +56,15 @@ impl TryFrom<HashMap<String, TransformOld>> for Transforms {
|
||||
}
|
||||
}
|
||||
|
||||
pub type DefaultValues = HashMap<String, DefaultValue>;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
||||
pub struct Transforms {
|
||||
#[serde(default)]
|
||||
pub(crate) interpolation_order: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub(crate) default_value: DefaultValues,
|
||||
#[serde(default)]
|
||||
pub channel: HashMap<String, Transform<IxDyn>>,
|
||||
#[serde(default)]
|
||||
pub drift: Vec<Transform<IxDyn>>,
|
||||
@@ -174,7 +178,9 @@ impl Transforms {
|
||||
Q: AsRef<Path>,
|
||||
R: AsRef<Path>,
|
||||
{
|
||||
if let Some(transform_file) = transform_file.as_ref() && let Ok(transforms) = Self::load(transform_file) {
|
||||
if let Some(transform_file) = transform_file.as_ref()
|
||||
&& let Ok(transforms) = Self::load(transform_file.as_ref().with_extension("yml"))
|
||||
{
|
||||
Ok(transforms)
|
||||
} else {
|
||||
let default_transform = default_transform.map(Transform::new);
|
||||
@@ -198,11 +204,13 @@ impl Transforms {
|
||||
};
|
||||
if let Some(transform_file) = transform_file {
|
||||
let transform_file = transform_file.as_ref();
|
||||
transforms.save(transform_file)?;
|
||||
transforms.save(transform_file.with_extension("yml"))?;
|
||||
if let Some(bead_files) = bead_files {
|
||||
transforms.save_channel_transform_tiff(transform_file.with_extension("tif"), bead_files)?;
|
||||
transforms.save_channel_transform_tiff(
|
||||
transform_file.with_extension("tif"),
|
||||
bead_files,
|
||||
)?;
|
||||
}
|
||||
|
||||
}
|
||||
Ok(transforms)
|
||||
}
|
||||
@@ -238,11 +246,15 @@ impl Transforms {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn calculate_drift_transforms_2d<P: AsRef<Path>>(file: P) -> Result<Vec<Transform<Ix2>>, Error> {
|
||||
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<P: AsRef<Path>>(file: P) -> 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()
|
||||
}
|
||||
|
||||
@@ -540,6 +552,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
t: usize,
|
||||
transform: &Transform<IxDyn>,
|
||||
order: usize,
|
||||
default_value: &DefaultValue,
|
||||
) -> Result<Arc<Frame>, Error> {
|
||||
match transform.ndim() {
|
||||
2 => {
|
||||
@@ -551,18 +564,22 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
tr.interpolate_par_order(
|
||||
&$array.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||
order,
|
||||
default_value,
|
||||
)?
|
||||
.mapv(|i| i > 0.5),
|
||||
)))
|
||||
};
|
||||
($array:expr ,$var:ident, f64) => {
|
||||
Ok(Arc::from(Frame::$var(
|
||||
tr.interpolate_par_order($array, order)?,
|
||||
)))
|
||||
Ok(Arc::from(Frame::$var(tr.interpolate_par_order(
|
||||
$array,
|
||||
order,
|
||||
default_value,
|
||||
)?)))
|
||||
};
|
||||
($array:expr ,$var:ident, $t:ty) => {
|
||||
Ok(Arc::from(Frame::$var(
|
||||
tr.interpolate_par_order($array, order)?.mapv(|i| i as $t),
|
||||
tr.interpolate_par_order($array, order, default_value)?
|
||||
.mapv(|i| i as $t),
|
||||
)))
|
||||
};
|
||||
}
|
||||
@@ -633,6 +650,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
let transformed = tr.interpolate_par_order(
|
||||
&stack.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||
order,
|
||||
default_value,
|
||||
)?;
|
||||
Ok(Arc::from(Frame::$var(
|
||||
transformed.slice(s![0, .., ..]).mapv(|i| i > 0.5),
|
||||
@@ -647,7 +665,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||
let transformed = tr.interpolate_par_order(&stack, order)?;
|
||||
let transformed =
|
||||
tr.interpolate_par_order(&stack, order, default_value)?;
|
||||
Ok(Arc::from(Frame::$var(
|
||||
transformed.slice(s![0, .., ..]).to_owned(),
|
||||
)))
|
||||
@@ -661,7 +680,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||
let transformed = tr.interpolate_par_order(&stack, order)?;
|
||||
let transformed =
|
||||
tr.interpolate_par_order(&stack, order, default_value)?;
|
||||
Ok(Arc::from(Frame::$var(
|
||||
transformed.slice(s![0, .., ..]).mapv(|i| i as $t),
|
||||
)))
|
||||
@@ -775,6 +795,7 @@ mod tests {
|
||||
]),
|
||||
);
|
||||
let view_t = view.with_transforms(Transforms {
|
||||
default_value: Default::default(),
|
||||
interpolation_order: None,
|
||||
channel,
|
||||
drift: Vec::new(),
|
||||
|
||||
+56
-19
@@ -5,14 +5,14 @@ use crate::metadata::Metadata;
|
||||
use crate::readers::{Dimensions, DynReader, Frame, Reader};
|
||||
use crate::stats::MinMax;
|
||||
#[cfg(feature = "transforms")]
|
||||
use crate::transforms::{TransformKind, Transforms};
|
||||
use crate::transforms::{DefaultValues, TransformKind, Transforms};
|
||||
#[cfg(feature = "transforms")]
|
||||
use image_registration::transform::Transform;
|
||||
use image_registration::{bspline::DefaultValue, 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};
|
||||
@@ -990,7 +990,13 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
) = (&kind, op_czt.first(), xy_dim == 2)
|
||||
{
|
||||
transform_done.insert((c, z, t));
|
||||
transform.map(|transform| (transform, order))
|
||||
transform.map(|transform| {
|
||||
(
|
||||
transform,
|
||||
order,
|
||||
Cow::Borrowed(&self.transforms.default_value),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1097,7 +1103,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
&slice_reader,
|
||||
&ax_out,
|
||||
defer_slice_xy,
|
||||
order,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1143,7 +1148,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
&slice_reader,
|
||||
&ax_out,
|
||||
defer_slice_xy,
|
||||
order,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1173,7 +1177,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
slice: &[Slice],
|
||||
ax: &HashMap<Axis, usize>,
|
||||
defer_slice_xy: bool,
|
||||
order: usize,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: Number,
|
||||
@@ -1188,7 +1191,6 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
};
|
||||
ax.len()
|
||||
];
|
||||
|
||||
let shape = array.shape();
|
||||
let slice_out = [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X]
|
||||
.into_iter()
|
||||
@@ -1200,9 +1202,17 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let transpose = ax[&Axis::X] < ax[&Axis::Y];
|
||||
for (ci, c) in slice_out[0].into_iter().zip(&slice[0]) {
|
||||
let default_value = if let Some(default_value) = self
|
||||
.transforms
|
||||
.default_value
|
||||
.get(self.channel_name(c as usize)?)
|
||||
{
|
||||
Cow::Borrowed(default_value)
|
||||
} else {
|
||||
Cow::Owned(DefaultValue::default())
|
||||
};
|
||||
let channel_name = self.channel_name(c as usize)?;
|
||||
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)? {
|
||||
@@ -1224,7 +1234,16 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
}
|
||||
let untransformed = array.slice(slice_transform.as_slice()).into_dyn();
|
||||
let transformed = transform
|
||||
.interpolate_par_order(untransformed, order)?
|
||||
.interpolate_par_order(
|
||||
untransformed,
|
||||
self.transforms
|
||||
.interpolation_order
|
||||
.unwrap_or(if T::ISFLOAT { 3 } else { 0 }),
|
||||
self.transforms
|
||||
.default_value
|
||||
.get(channel_name)
|
||||
.unwrap_or(&default_value),
|
||||
)?
|
||||
.mapv(|i| T::from_f64(i).unwrap());
|
||||
array
|
||||
.slice_mut(slice_transform.as_slice())
|
||||
@@ -1269,7 +1288,11 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
c: usize,
|
||||
z: usize,
|
||||
t: usize,
|
||||
#[cfg(feature = "transforms")] transform: Option<(Cow<'a, Transform<IxDyn>>, usize)>,
|
||||
#[cfg(feature = "transforms")] transform: Option<(
|
||||
Cow<'a, Transform<IxDyn>>,
|
||||
usize,
|
||||
Cow<'a, DefaultValues>,
|
||||
)>,
|
||||
) -> FrameKeyRef<'a> {
|
||||
FrameKeyRef {
|
||||
reader: ReaderKeyRef {
|
||||
@@ -1310,23 +1333,37 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
c: usize,
|
||||
z: usize,
|
||||
t: usize,
|
||||
#[cfg(feature = "transforms")] transform: Option<(Cow<Transform<IxDyn>>, usize)>,
|
||||
#[cfg(feature = "transforms")] transform: Option<(
|
||||
Cow<Transform<IxDyn>>,
|
||||
usize,
|
||||
Cow<DefaultValues>,
|
||||
)>,
|
||||
) -> Result<Arc<Frame>, Error> {
|
||||
let key = self.frame_key(
|
||||
c,
|
||||
z,
|
||||
t,
|
||||
#[cfg(feature = "transforms")]
|
||||
transform
|
||||
.as_ref()
|
||||
.map(|(transform, order)| (Cow::Borrowed(&**transform), *order)),
|
||||
transform.as_ref().map(|(transform, order, default_value)| {
|
||||
(
|
||||
Cow::Borrowed(&**transform),
|
||||
*order,
|
||||
Cow::Borrowed(&**default_value),
|
||||
)
|
||||
}),
|
||||
);
|
||||
if let Some(frame) = FrameCache::global().get(&key) {
|
||||
return Ok(frame);
|
||||
}
|
||||
#[cfg(feature = "transforms")]
|
||||
let frame = if let Some((transform, order)) = transform.as_ref() {
|
||||
self.get_transformed_frame(c, z, t, transform, *order)?
|
||||
let frame = if let Some((transform, order, default_value)) = transform.as_ref() {
|
||||
let channel_name = self.channel_name(c)?;
|
||||
let default_value = if let Some(default_value) = default_value.get(channel_name) {
|
||||
Cow::Borrowed(default_value)
|
||||
} else {
|
||||
Cow::Owned(DefaultValue::default())
|
||||
};
|
||||
self.get_transformed_frame(c, z, t, transform, *order, &default_value)?
|
||||
} else {
|
||||
Arc::new(self.reader.get_frame(c, z, t)?)
|
||||
};
|
||||
@@ -1864,7 +1901,7 @@ mod tests {
|
||||
fn frame_cache() -> Result<(), Error> {
|
||||
let cache = FrameCache::new(2);
|
||||
let key = |c: usize, z: usize, t: usize| FrameKey {
|
||||
reader_key: ReaderKey {
|
||||
reader: ReaderKey {
|
||||
name: "test".to_string(),
|
||||
path: PathBuf::from("test.tif"),
|
||||
series: 0,
|
||||
|
||||
Reference in new issue
Block a user