- default value argument for interpolation

This commit is contained in:
Wim Pomp committed 2026-10-25 10:23:57 +01:00
1 parent 22e8ab1dd2
commit 8a4c965477
9 files changed
+342 -94

No files matched your search

@@ -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
View File
@@ -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())),
}
}
}
+3
View File
@@ -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;
+4 -3
View File
@@ -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
View File
@@ -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,
+38 -17
View File
@@ -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
View File
@@ -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,