- testing 2d transforms
This commit is contained in:
1 parent
4815690621
commit
453237cae9
20 files changed
+798
-304
No files matched your search
+2
-2
@@ -3,9 +3,9 @@ resolver = "3"
|
|||||||
members = ["ndbioimage", "image-tiff", "image-registration"]
|
members = ["ndbioimage", "image-tiff", "image-registration"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94.0"
|
rust-version = "1.94.1"
|
||||||
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Read bio image formats using the bio-formats java package."
|
description = "Read bio image formats using the bio-formats java package."
|
||||||
|
|||||||
+1
-1
Submodule image-registration updated: 7eb39a2fbf...5c580dc80f.
+1
-1
Submodule image-tiff updated: 9b452e93b7...a6e8ed772d.
@@ -37,7 +37,7 @@ ome-metadata = "0.6"
|
|||||||
ordered-float = { version = "5", optional = true }
|
ordered-float = { version = "5", optional = true }
|
||||||
phf = { version = "0.14", features = ["macros"] }
|
phf = { version = "0.14", features = ["macros"] }
|
||||||
postcard = { version = "1", features = ["use-std"], optional = true }
|
postcard = { version = "1", features = ["use-std"], optional = true }
|
||||||
pyo3 = { version = "0.29", features = ["abi3-py310", "anyhow", "generate-import-lib"], optional = true }
|
pyo3 = { version = "0.29", features = ["abi3-py310", "anyhow", "generate-import-lib", "multiple-pymethods"], optional = true }
|
||||||
pyo3-stub-gen = { version = "0.23", optional = true }
|
pyo3-stub-gen = { version = "0.23", optional = true }
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ndbioimage"
|
name = "ndbioimage"
|
||||||
version = "2027.0.5"
|
version = "2027.0.6"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
|
|||||||
@@ -198,9 +198,9 @@ impl<'de> DeserializeAs<'de, SliceInfoElem> for SliceInfoElemDef {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Slice {
|
pub(crate) struct Slice {
|
||||||
start: isize,
|
pub(crate) start: isize,
|
||||||
end: isize,
|
pub(crate) end: isize,
|
||||||
step: isize,
|
pub(crate) step: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slice {
|
impl Slice {
|
||||||
|
|||||||
+86
-12
@@ -1,14 +1,22 @@
|
|||||||
use crate::axes::{Axis, Operation};
|
use crate::axes::{Axis, Operation};
|
||||||
|
use crate::readers::Frame;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use image_registration::transform::Transform;
|
||||||
use indexmap::{Equivalent, IndexMap};
|
use indexmap::{Equivalent, IndexMap};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use ndarray::IxDyn;
|
||||||
use ndarray::{ArrayD, SliceInfoElem};
|
use ndarray::{ArrayD, SliceInfoElem};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::cmp::PartialEq;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
use crate::readers::Frame;
|
use crate::transforms::Transforms;
|
||||||
|
|
||||||
/// maximum number of frames held in the cache
|
/// maximum number of frames held in the cache
|
||||||
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
||||||
@@ -132,7 +140,15 @@ impl ReaderKeyRef<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type FrameKey = (ReaderKey, usize, usize, usize);
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub(crate) struct FrameKey {
|
||||||
|
pub(crate) reader_key: ReaderKey,
|
||||||
|
pub(crate) c: usize,
|
||||||
|
pub(crate) z: usize,
|
||||||
|
pub(crate) t: usize,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transform: Option<(Transform<IxDyn>, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -141,6 +157,8 @@ pub(crate) struct FrameKeyRef<'a> {
|
|||||||
pub(crate) c: usize,
|
pub(crate) c: usize,
|
||||||
pub(crate) z: usize,
|
pub(crate) z: usize,
|
||||||
pub(crate) t: usize,
|
pub(crate) t: usize,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transform: Option<(Cow<'a, Transform<IxDyn>>, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for FrameKeyRef<'_> {
|
impl Hash for FrameKeyRef<'_> {
|
||||||
@@ -149,19 +167,50 @@ impl Hash for FrameKeyRef<'_> {
|
|||||||
self.c.hash(state);
|
self.c.hash(state);
|
||||||
self.z.hash(state);
|
self.z.hash(state);
|
||||||
self.t.hash(state);
|
self.t.hash(state);
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
self.transform.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||||
let (rk, c, z, t) = key;
|
let FrameKey {
|
||||||
self.reader.equivalent(rk) && self.c == *c && self.z == *z && self.t == *t
|
reader_key,
|
||||||
|
c,
|
||||||
|
z,
|
||||||
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transform,
|
||||||
|
} = key;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(reader_key)
|
||||||
|
&& 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))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(reader_key) && self.c == *c && self.z == *z && self.t == *t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameKeyRef<'_> {
|
impl FrameKeyRef<'_> {
|
||||||
pub(crate) fn to_owned(&self) -> FrameKey {
|
pub(crate) fn to_owned(&self) -> FrameKey {
|
||||||
(self.reader.to_owned(), self.c, self.z, self.t)
|
FrameKey {
|
||||||
|
reader_key: self.reader.to_owned(),
|
||||||
|
c: self.c,
|
||||||
|
z: self.z,
|
||||||
|
t: self.t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transform: self
|
||||||
|
.transform
|
||||||
|
.as_ref()
|
||||||
|
.map(|(t, i)| (t.clone().into_owned(), *i)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +222,8 @@ pub(crate) struct ArrayKey {
|
|||||||
pub(crate) slice: Vec<SliceInfoElem>,
|
pub(crate) slice: Vec<SliceInfoElem>,
|
||||||
pub(crate) axes: Vec<Axis>,
|
pub(crate) axes: Vec<Axis>,
|
||||||
pub(crate) operations: Vec<(Axis, Operation)>,
|
pub(crate) operations: Vec<(Axis, Operation)>,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transforms: Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
||||||
@@ -183,6 +234,8 @@ pub(crate) struct ArrayKeyRef<'a> {
|
|||||||
pub(crate) slice: &'a [SliceInfoElem],
|
pub(crate) slice: &'a [SliceInfoElem],
|
||||||
pub(crate) axes: &'a [Axis],
|
pub(crate) axes: &'a [Axis],
|
||||||
pub(crate) operations: &'a IndexMap<Axis, Operation>,
|
pub(crate) operations: &'a IndexMap<Axis, Operation>,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
pub(crate) transforms: &'a Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for ArrayKeyRef<'_> {
|
impl Hash for ArrayKeyRef<'_> {
|
||||||
@@ -196,21 +249,40 @@ impl Hash for ArrayKeyRef<'_> {
|
|||||||
ax.hash(state);
|
ax.hash(state);
|
||||||
op.hash(state);
|
op.hash(state);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
self.transforms.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
||||||
fn equivalent(&self, key: &ArrayKey) -> bool {
|
fn equivalent(&self, key: &ArrayKey) -> bool {
|
||||||
self.reader.equivalent(&key.reader)
|
#[cfg(feature = "transforms")]
|
||||||
&& self.dtype == key.dtype
|
{
|
||||||
&& self.slice == key.slice
|
self.reader.equivalent(&key.reader)
|
||||||
&& self.axes == key.axes
|
&& self.dtype == key.dtype
|
||||||
&& self.operations.len() == key.operations.len()
|
&& self.slice == key.slice
|
||||||
&& self
|
&& self.axes == key.axes
|
||||||
|
&& self.operations.len() == key.operations.len()
|
||||||
|
&& self
|
||||||
.operations
|
.operations
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&key.operations)
|
.zip(&key.operations)
|
||||||
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||||
|
&& self.transforms == key.transforms
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
|
{
|
||||||
|
self.reader.equivalent(&key.reader)
|
||||||
|
&& self.dtype == key.dtype
|
||||||
|
&& self.slice == key.slice
|
||||||
|
&& self.axes == key.axes
|
||||||
|
&& self.operations.len() == key.operations.len()
|
||||||
|
&& self
|
||||||
|
.operations
|
||||||
|
.iter()
|
||||||
|
.zip(&key.operations)
|
||||||
|
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +298,8 @@ impl ArrayKeyRef<'_> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(ax, op)| (*ax, op.clone()))
|
.map(|(ax, op)| (*ax, op.clone()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transforms: self.transforms.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,6 +179,10 @@ pub enum Error {
|
|||||||
it cannot use. read the file before forking, or start the child with spawn/exec instead."
|
it cannot use. read the file before forking, or start the child with spawn/exec instead."
|
||||||
)]
|
)]
|
||||||
ForkedJvm(u32, u32),
|
ForkedJvm(u32, u32),
|
||||||
|
#[error("{0}D transformation is not implemented")]
|
||||||
|
TransformationNotImplemented(usize),
|
||||||
|
#[error("no channel {0} present")]
|
||||||
|
NoChannel(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||||
//! The ndbioimage crate exposes (bio) images as a struct that can be sliced like an ndarray Array
|
//! The ndbioimage crate exposes (bio) images as a struct that can be sliced like an ndarray Array
|
||||||
//! (Rust), but without loading the whole image into memory, reading from the file only when needed.
|
//! (Rust), but without loading the whole image into memory, reading from the file only when needed.
|
||||||
//! Some metadata is read
|
//! Some metadata is read
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
//! # fn main() -> Result<(), ndbioimage::error::Error> {
|
||||||
//! let path = "/path/to/file";
|
//! let path = "/path/to/file";
|
||||||
//! let reader = DynReader::new(&path, 0, 0)?;
|
//! let reader = DynReader::new(&path, 0, 0)?;
|
||||||
//! println!("shape: {}", reader.shape());
|
//! println!("shape: {}", reader.raw_shape());
|
||||||
//! let frame = reader.get_frame(0, 0, 0)?;
|
//! let frame = reader.get_frame(0, 0, 0)?;
|
||||||
//! if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
//! if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||||
//! println!("{:?}", arr);
|
//! println!("{:?}", arr);
|
||||||
@@ -70,7 +70,7 @@ pub mod readers;
|
|||||||
pub mod tiffwrite;
|
pub mod tiffwrite;
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
/// image registration and warping images using affine transforms
|
/// image registration and warping images using affine transforms
|
||||||
mod transforms;
|
pub mod transforms;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
/// main entry point for the application
|
/// main entry point for the application
|
||||||
@@ -338,7 +338,7 @@ mod tests {
|
|||||||
fn read_ser() -> Result<(), Error> {
|
fn read_ser() -> Result<(), Error> {
|
||||||
let file = "czi/Experiment-2029.czi";
|
let file = "czi/Experiment-2029.czi";
|
||||||
let reader = open(file)?;
|
let reader = open(file)?;
|
||||||
println!("shape: {}", reader.shape());
|
println!("shape: {}", reader.raw_shape());
|
||||||
let frame = reader.get_frame(0, 0, 0)?;
|
let frame = reader.get_frame(0, 0, 0)?;
|
||||||
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
if let Ok(arr) = <Frame as TryInto<Array2<i8>>>::try_into(frame) {
|
||||||
println!("{:?}", arr);
|
println!("{:?}", arr);
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ fn main() -> PyResult<()> {
|
|||||||
#[pymodule]
|
#[pymodule]
|
||||||
#[pyo3(name = "ndbioimage_rs")]
|
#[pyo3(name = "ndbioimage_rs")]
|
||||||
mod ndbioimage_rs {
|
mod ndbioimage_rs {
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[pymodule_export]
|
#[pymodule_export]
|
||||||
|
|||||||
+3
-139
@@ -3,8 +3,6 @@ use crate::error::Error;
|
|||||||
use crate::metadata::Metadata;
|
use crate::metadata::Metadata;
|
||||||
use crate::movie::MovieOptions;
|
use crate::movie::MovieOptions;
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
use crate::py::transforms::{PyTransform, PyTransforms};
|
|
||||||
use crate::readers::{DynReader, PixelType, Reader};
|
use crate::readers::{DynReader, PixelType, Reader};
|
||||||
use crate::view::{Item, View};
|
use crate::view::{Item, View};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -27,7 +25,7 @@ pub(crate) use pyo3::{
|
|||||||
use pyo3_stub_gen::derive::*;
|
use pyo3_stub_gen::derive::*;
|
||||||
use pyo3_stub_gen::inventory::submit;
|
use pyo3_stub_gen::inventory::submit;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// class to read image files, while taking good care of important metadata,
|
/// class to read image files, while taking good care of important metadata,
|
||||||
@@ -61,7 +59,7 @@ use std::path::PathBuf;
|
|||||||
#[pyclass(subclass, from_py_object, name = "Imread", module = "ndbioimage")]
|
#[pyclass(subclass, from_py_object, name = "Imread", module = "ndbioimage")]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct PyView {
|
pub(crate) struct PyView {
|
||||||
view: View<IxDyn, DynReader>,
|
pub(crate) view: View<IxDyn, DynReader>,
|
||||||
dtype: PixelType,
|
dtype: PixelType,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
ome: Ome,
|
ome: Ome,
|
||||||
@@ -296,141 +294,6 @@ impl PyView {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
|
||||||
/// backward compatibility
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
|
||||||
fn with_transform<'py>(
|
|
||||||
&self,
|
|
||||||
channels: bool,
|
|
||||||
drift: bool,
|
|
||||||
file: Option<Bound<'py, PyAny>>,
|
|
||||||
bead_files: Option<Bound<'py, PyAny>>,
|
|
||||||
main_channel: Option<usize>,
|
|
||||||
default_transform: Option<Vec<f64>>,
|
|
||||||
) -> PyResult<PyView> {
|
|
||||||
self.with_transforms(
|
|
||||||
channels,
|
|
||||||
drift,
|
|
||||||
file,
|
|
||||||
bead_files,
|
|
||||||
main_channel,
|
|
||||||
default_transform,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return a new view with transformations applied (channel alignment, drift correction)
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
#[expect(unused_variables)]
|
|
||||||
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
|
||||||
fn with_transforms<'py>(
|
|
||||||
&self,
|
|
||||||
channels: bool,
|
|
||||||
drift: bool,
|
|
||||||
file: Option<Bound<'py, PyAny>>,
|
|
||||||
bead_files: Option<Bound<'py, PyAny>>,
|
|
||||||
main_channel: Option<usize>,
|
|
||||||
default_transform: Option<Vec<f64>>,
|
|
||||||
) -> PyResult<PyView> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the transformation
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
#[getter]
|
|
||||||
fn get_transforms(&self) -> PyResult<PyTransforms> {
|
|
||||||
Ok(PyTransforms {
|
|
||||||
inner: self.view.get_transforms().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// set the transformation
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
#[setter]
|
|
||||||
fn set_transforms(&mut self, transform: PyTransforms) {
|
|
||||||
self.view.set_transforms(transform.inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
fn load_transforms_from_yaml(&mut self, path: PathBuf) -> PyResult<()> {
|
|
||||||
self.view.load_transforms_from_yaml(path.as_path())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
fn calculate_channel_transforms_2d(
|
|
||||||
&self,
|
|
||||||
py: Python,
|
|
||||||
main_channel: usize,
|
|
||||||
) -> PyResult<HashMap<String, PyTransform>> {
|
|
||||||
py.detach(|| {
|
|
||||||
Ok(self
|
|
||||||
.view
|
|
||||||
.calculate_channel_transforms_2d(main_channel)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(n, t)| {
|
|
||||||
(
|
|
||||||
n,
|
|
||||||
PyTransform {
|
|
||||||
inner: t.into_dyn(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
fn calculate_channel_transforms_3d(
|
|
||||||
&self,
|
|
||||||
py: Python,
|
|
||||||
main_channel: usize,
|
|
||||||
) -> PyResult<HashMap<String, PyTransform>> {
|
|
||||||
py.detach(|| {
|
|
||||||
Ok(self
|
|
||||||
.view
|
|
||||||
.calculate_channel_transforms_3d(main_channel)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(n, t)| {
|
|
||||||
(
|
|
||||||
n,
|
|
||||||
PyTransform {
|
|
||||||
inner: t.into_dyn(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
fn calculate_drift_transforms_2d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
|
||||||
py.detach(|| {
|
|
||||||
Ok(self
|
|
||||||
.view
|
|
||||||
.calculate_drift_transforms_2d()?
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| PyTransform {
|
|
||||||
inner: t.into_dyn(),
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
|
||||||
fn calculate_drift_transforms_3d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
|
||||||
py.detach(|| {
|
|
||||||
Ok(self
|
|
||||||
.view
|
|
||||||
.calculate_drift_transforms_3d()?
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| PyTransform {
|
|
||||||
inner: t.into_dyn(),
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gen_stub(override_return_type(type_repr="numpy.ndarray | int | float", imports=("numpy")))]
|
#[gen_stub(override_return_type(type_repr="numpy.ndarray | int | float", imports=("numpy")))]
|
||||||
fn squeeze<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
fn squeeze<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
let view = self.view.squeeze()?;
|
let view = self.view.squeeze()?;
|
||||||
@@ -1481,6 +1344,7 @@ impl PyView {
|
|||||||
))]
|
))]
|
||||||
axes: Option<Vec<Bound<PyAny>>>,
|
axes: Option<Vec<Bound<PyAny>>>,
|
||||||
) -> PyResult<Self> {
|
) -> PyResult<Self> {
|
||||||
|
// TODO: transpose("xyz"), transpose(*axes)
|
||||||
let view = if let Some(axes) = axes {
|
let view = if let Some(axes) = axes {
|
||||||
let ax = axes
|
let ax = axes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::py::imread::PyView;
|
||||||
use crate::transforms::Transforms;
|
use crate::transforms::Transforms;
|
||||||
use image_registration::metric::{SamplingArg, Sigma};
|
use image_registration::metric::{SamplingArg, Sigma};
|
||||||
use image_registration::register::{Optimizer, RegistrationStep};
|
use image_registration::register::{Optimizer, RegistrationStep};
|
||||||
@@ -709,6 +710,138 @@ pub(crate) struct PyTransforms {
|
|||||||
pub(crate) inner: Transforms,
|
pub(crate) inner: Transforms,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gen_stub_pymethods]
|
||||||
|
#[pymethods]
|
||||||
|
impl PyView {
|
||||||
|
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||||
|
/// backward compatibility
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||||
|
fn with_transform<'py>(
|
||||||
|
&self,
|
||||||
|
channels: bool,
|
||||||
|
drift: bool,
|
||||||
|
file: Option<Bound<'py, PyAny>>,
|
||||||
|
bead_files: Option<Bound<'py, PyAny>>,
|
||||||
|
main_channel: Option<usize>,
|
||||||
|
default_transform: Option<Vec<f64>>,
|
||||||
|
) -> PyResult<PyView> {
|
||||||
|
self.with_transforms(
|
||||||
|
channels,
|
||||||
|
drift,
|
||||||
|
file,
|
||||||
|
bead_files,
|
||||||
|
main_channel,
|
||||||
|
default_transform,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return a new view with transformations applied (channel alignment, drift correction)
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
#[expect(unused_variables)]
|
||||||
|
#[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None, main_channel = None, default_transform = None))]
|
||||||
|
fn with_transforms<'py>(
|
||||||
|
&self,
|
||||||
|
channels: bool,
|
||||||
|
drift: bool,
|
||||||
|
file: Option<Bound<'py, PyAny>>,
|
||||||
|
bead_files: Option<Bound<'py, PyAny>>,
|
||||||
|
main_channel: Option<usize>,
|
||||||
|
default_transform: Option<Vec<f64>>,
|
||||||
|
) -> PyResult<PyView> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the transformation
|
||||||
|
#[getter]
|
||||||
|
fn get_transforms(&self) -> PyResult<PyTransforms> {
|
||||||
|
Ok(PyTransforms {
|
||||||
|
inner: self.view.get_transforms().clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the transformation
|
||||||
|
#[setter]
|
||||||
|
fn set_transforms(&mut self, transform: PyTransforms) {
|
||||||
|
self.view.set_transforms(transform.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_transforms_from_yaml(&mut self, path: PathBuf) -> PyResult<()> {
|
||||||
|
self.view.load_transforms_from_yaml(path.as_path())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_channel_transforms_2d(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> PyResult<HashMap<String, PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_channel_transforms_2d(main_channel)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, t)| {
|
||||||
|
(
|
||||||
|
n,
|
||||||
|
PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_channel_transforms_3d(
|
||||||
|
&self,
|
||||||
|
py: Python,
|
||||||
|
main_channel: usize,
|
||||||
|
) -> PyResult<HashMap<String, PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_channel_transforms_3d(main_channel)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, t)| {
|
||||||
|
(
|
||||||
|
n,
|
||||||
|
PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_drift_transforms_2d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_drift_transforms_2d()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_drift_transforms_3d(&self, py: Python) -> PyResult<Vec<PyTransform>> {
|
||||||
|
py.detach(|| {
|
||||||
|
Ok(self
|
||||||
|
.view
|
||||||
|
.calculate_drift_transforms_3d()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| PyTransform {
|
||||||
|
inner: t.into_dyn(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gen_stub_pymethods]
|
#[gen_stub_pymethods]
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyTransforms {
|
impl PyTransforms {
|
||||||
|
|||||||
+14
-11
@@ -2,7 +2,7 @@ use crate::axes::{Axis, Shape};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
use ndarray::{Array, Dimension, Ix2, Ix5, s};
|
use ndarray::{Array, Dimension, Ix2, Ix5, s};
|
||||||
use num::{FromPrimitive, Zero};
|
use num::{FromPrimitive, One, Zero};
|
||||||
use ome_metadata::Ome;
|
use ome_metadata::Ome;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -148,6 +148,8 @@ impl PixelType {
|
|||||||
/// array data with a specific pixel type
|
/// array data with a specific pixel type
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ArrayT<D: Dimension> {
|
pub enum ArrayT<D: Dimension> {
|
||||||
|
/// boolean array
|
||||||
|
Bool(Array<bool, D>),
|
||||||
/// signed 8-bit integer array
|
/// signed 8-bit integer array
|
||||||
I8(Array<i8, D>),
|
I8(Array<i8, D>),
|
||||||
/// unsigned 8-bit integer array
|
/// unsigned 8-bit integer array
|
||||||
@@ -197,7 +199,7 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
|||||||
|
|
||||||
/// get a sliceable view on the image file
|
/// get a sliceable view on the image file
|
||||||
fn view(&self) -> View<Ix5, Self> {
|
fn view(&self) -> View<Ix5, Self> {
|
||||||
let shape = self.shape();
|
let shape = self.raw_shape();
|
||||||
let slice = s![0..shape.c, 0..shape.z, 0..shape.t, 0..shape.y, 0..shape.x,];
|
let slice = s![0..shape.c, 0..shape.z, 0..shape.t, 0..shape.y, 0..shape.x,];
|
||||||
View::new(
|
View::new(
|
||||||
self.clone(),
|
self.clone(),
|
||||||
@@ -216,7 +218,7 @@ pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
|
|||||||
/// the position index
|
/// the position index
|
||||||
fn position(&self) -> usize;
|
fn position(&self) -> usize;
|
||||||
/// the shape of the image data
|
/// the shape of the image data
|
||||||
fn shape(&self) -> &Shape;
|
fn raw_shape(&self) -> &Shape;
|
||||||
/// the pixel type of the image data
|
/// the pixel type of the image data
|
||||||
fn pixel_type(&self) -> &PixelType;
|
fn pixel_type(&self) -> &PixelType;
|
||||||
/// get all available positions for a given series
|
/// get all available positions for a given series
|
||||||
@@ -314,6 +316,7 @@ macro_rules! try_into_array_body {
|
|||||||
($self:expr, $map:ident) => {{
|
($self:expr, $map:ident) => {{
|
||||||
let mut err = Ok(());
|
let mut err = Ok(());
|
||||||
let arr = match $self {
|
let arr = match $self {
|
||||||
|
ArrayT::Bool(v) => v.$map(|x| if x { T::one() } else { T::zero() }),
|
||||||
ArrayT::I8(v) => v.$map(|x| {
|
ArrayT::I8(v) => v.$map(|x| {
|
||||||
T::from_i8(x).unwrap_or_else(|| {
|
T::from_i8(x).unwrap_or_else(|| {
|
||||||
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
err = Err(Error::Cast(x.to_string(), type_name::<T>().to_string()));
|
||||||
@@ -397,7 +400,7 @@ macro_rules! try_into_array_body {
|
|||||||
impl<D, T> TryInto<Array<T, D>> for ArrayT<D>
|
impl<D, T> TryInto<Array<T, D>> for ArrayT<D>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
T: FromPrimitive + Zero + 'static,
|
T: FromPrimitive + Zero + One + 'static,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -409,7 +412,7 @@ where
|
|||||||
impl<D, T> TryInto<Array<T, D>> for &ArrayT<D>
|
impl<D, T> TryInto<Array<T, D>> for &ArrayT<D>
|
||||||
where
|
where
|
||||||
D: Dimension,
|
D: Dimension,
|
||||||
T: Clone + FromPrimitive + Zero + 'static,
|
T: Clone + FromPrimitive + Zero + One + 'static,
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@@ -577,18 +580,18 @@ impl Reader for DynReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "tiff")]
|
#[cfg(feature = "tiff")]
|
||||||
DynReader::Tiff(r) => r.shape(),
|
DynReader::Tiff(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "tiffseq")]
|
#[cfg(feature = "tiffseq")]
|
||||||
DynReader::TiffSeq(r) => r.shape(),
|
DynReader::TiffSeq(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "czi")]
|
#[cfg(feature = "czi")]
|
||||||
DynReader::Czi(r) => r.shape(),
|
DynReader::Czi(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "bioformats_rust")]
|
#[cfg(feature = "bioformats_rust")]
|
||||||
DynReader::BioFormatsRust(r) => r.shape(),
|
DynReader::BioFormatsRust(r) => r.raw_shape(),
|
||||||
#[cfg(feature = "bioformats_java")]
|
#[cfg(feature = "bioformats_java")]
|
||||||
DynReader::BioFormatsJava(r) => r.shape(),
|
DynReader::BioFormatsJava(r) => r.raw_shape(),
|
||||||
#[expect(unreachable_patterns)]
|
#[expect(unreachable_patterns)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ static JVM_PID: AtomicU32 = AtomicU32::new(0);
|
|||||||
/// else -- verified by `JNI_GetCreatedJavaVMs`, which still reports 1 in the child, so a second
|
/// else -- verified by `JNI_GetCreatedJavaVMs`, which still reports 1 in the child, so a second
|
||||||
/// `JNI_CreateJavaVM` (which is what j4rs' `Jvm::attach_thread` calls) takes the already-exists
|
/// `JNI_CreateJavaVM` (which is what j4rs' `Jvm::attach_thread` calls) takes the already-exists
|
||||||
/// path. An `exec` replaces the address space, and that does reset the count to 0.
|
/// path. An `exec` replaces the address space, and that does reset the count to 0.
|
||||||
fn check_not_forked() -> Result<(), Error> {
|
pub fn check_not_forked() -> Result<(), Error> {
|
||||||
let built_in = JVM_PID.load(Ordering::Relaxed);
|
let built_in = JVM_PID.load(Ordering::Relaxed);
|
||||||
if built_in != 0 && built_in != std::process::id() {
|
if built_in != 0 && built_in != std::process::id() {
|
||||||
return Err(Error::ForkedJvm(built_in, std::process::id()));
|
return Err(Error::ForkedJvm(built_in, std::process::id()));
|
||||||
@@ -698,7 +698,7 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ impl Reader for BioFormatsRustReader {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1149,7 +1149,7 @@ impl Reader for CziReader {
|
|||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ impl Reader for TiffReader {
|
|||||||
fn position(&self) -> usize {
|
fn position(&self) -> usize {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
fn pixel_type(&self) -> &PixelType {
|
fn pixel_type(&self) -> &PixelType {
|
||||||
|
|||||||
@@ -572,7 +572,7 @@ impl Reader for TiffSeqReader {
|
|||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape(&self) -> &Shape {
|
fn raw_shape(&self) -> &Shape {
|
||||||
&self.shape
|
&self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
use crate::axes::Axis;
|
use crate::axes::Axis;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::readers::{DynReader, Reader};
|
use crate::readers::{DynReader, Frame, PixelType, Reader};
|
||||||
use crate::view::View;
|
use crate::view::View;
|
||||||
pub use image_registration::transform::Transform;
|
pub use image_registration::transform::Transform;
|
||||||
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, s};
|
use ndarray::{Array2, Dimension, Ix2, Ix3, IxDyn, array, s};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::Seek;
|
use std::io::Seek;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
struct TransformOld {
|
struct TransformOld {
|
||||||
@@ -54,6 +57,24 @@ pub struct Transforms {
|
|||||||
pub drift: Vec<Transform<IxDyn>>,
|
pub drift: Vec<Transform<IxDyn>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Transforms {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
let mut keys: Vec<_> = self.channel.keys().collect();
|
||||||
|
keys.sort();
|
||||||
|
for key in keys {
|
||||||
|
key.hash(state);
|
||||||
|
self.channel[key].hash(state);
|
||||||
|
}
|
||||||
|
self.drift.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<Transforms> for &Transforms {
|
||||||
|
fn eq(&self, other: &Transforms) -> bool {
|
||||||
|
**self == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for Transforms {
|
impl Serialize for Transforms {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@@ -89,7 +110,23 @@ impl Serialize for Transforms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum TransformKind {
|
||||||
|
None,
|
||||||
|
Channel,
|
||||||
|
Drift,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
impl Transforms {
|
impl Transforms {
|
||||||
|
pub fn kind(&self) -> TransformKind {
|
||||||
|
match (self.channel.is_empty(), self.drift.is_empty()) {
|
||||||
|
(true, true) => TransformKind::None,
|
||||||
|
(false, true) => TransformKind::Channel,
|
||||||
|
(true, false) => TransformKind::Drift,
|
||||||
|
(false, false) => TransformKind::Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||||
let mut file = std::fs::File::open(&path)?;
|
let mut file = std::fs::File::open(&path)?;
|
||||||
Ok(
|
Ok(
|
||||||
@@ -210,16 +247,32 @@ impl Transforms {
|
|||||||
}
|
}
|
||||||
Ok(average_transforms)
|
Ok(average_transforms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_transform(
|
||||||
|
&self,
|
||||||
|
channel: &str,
|
||||||
|
t: usize,
|
||||||
|
) -> Result<Option<Cow<'_, Transform<IxDyn>>>, Error> {
|
||||||
|
match (self.channel.get(channel), self.drift.get(t)) {
|
||||||
|
(Some(channel), Some(drift)) => match (channel.ndim(), drift.ndim()) {
|
||||||
|
(2, 2) | (3, 3) => Ok(Some(Cow::Owned(drift * channel))),
|
||||||
|
(2, 3) => Ok(Some(Cow::Owned(drift.add_dimension(0, 1) * channel))),
|
||||||
|
(3, 2) => Ok(Some(Cow::Owned(drift * channel.add_dimension(0, 1)))),
|
||||||
|
(2 | 3, n) | (n, _) => Err(Error::TransformationNotImplemented(n)),
|
||||||
|
},
|
||||||
|
(Some(channel), None) => Ok(Some(Cow::Borrowed(channel))),
|
||||||
|
(None, Some(drift)) => Ok(Some(Cow::Borrowed(drift))),
|
||||||
|
(None, None) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Dimension, R: Reader> View<D, R> {
|
impl<D: Dimension, R: Reader> View<D, R> {
|
||||||
#[cfg(feature = "transforms")]
|
pub fn get_transforms(&self) -> &Transforms {
|
||||||
pub(crate) fn get_transforms(&self) -> &Transforms {
|
|
||||||
&self.transforms
|
&self.transforms
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transforms")]
|
pub fn set_transforms(&mut self, transforms: Transforms) {
|
||||||
pub(crate) fn set_transforms(&mut self, transforms: Transforms) {
|
|
||||||
self.transforms = transforms;
|
self.transforms = transforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +371,181 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
pub fn calculate_drift_transforms_3d(&self) -> Result<Vec<Transform<Ix3>>, Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_transformed_frame(
|
||||||
|
&self,
|
||||||
|
c: usize,
|
||||||
|
z: usize,
|
||||||
|
t: usize,
|
||||||
|
transform: &Transform<IxDyn>,
|
||||||
|
order: usize,
|
||||||
|
) -> Result<Arc<Frame>, Error> {
|
||||||
|
match transform.ndim() {
|
||||||
|
2 => {
|
||||||
|
let tr = transform.clone().into_dimensionality::<Ix2>()?;
|
||||||
|
|
||||||
|
macro_rules! interpolate {
|
||||||
|
($array:expr ,$var:ident, bool) => {
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
tr.interpolate_par_order(
|
||||||
|
&$array.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||||
|
order,
|
||||||
|
)?
|
||||||
|
.mapv(|i| i > 0.5),
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
($array:expr ,$var:ident, f64) => {
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
tr.interpolate_par_order($array, order)?,
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
($array:expr ,$var:ident, $t:ty) => {
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
tr.interpolate_par_order($array, order)?.mapv(|i| i as $t),
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.get_cached_frame(c, z, t, None)?.as_ref() {
|
||||||
|
Frame::Bool(frame) => interpolate!(frame, Bool, bool),
|
||||||
|
Frame::I8(frame) => interpolate!(frame, I8, i8),
|
||||||
|
Frame::U8(frame) => interpolate!(frame, U8, u8),
|
||||||
|
Frame::I16(frame) => interpolate!(frame, I16, i16),
|
||||||
|
Frame::U16(frame) => interpolate!(frame, U16, u16),
|
||||||
|
Frame::I32(frame) => interpolate!(frame, I32, i32),
|
||||||
|
Frame::U32(frame) => interpolate!(frame, U32, u32),
|
||||||
|
Frame::U64(frame) => interpolate!(frame, U64, u64),
|
||||||
|
Frame::I64(frame) => interpolate!(frame, I64, i64),
|
||||||
|
Frame::I128(frame) => interpolate!(frame, I128, i128),
|
||||||
|
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 => {
|
||||||
|
let tr = transform.clone().into_dimensionality::<Ix3>()?;
|
||||||
|
let shape = self.shape();
|
||||||
|
let zf = z as f64;
|
||||||
|
let yf = shape.y as f64 - 1.0;
|
||||||
|
let xf = shape.x as f64 - 1.0;
|
||||||
|
let corners = array![[zf, 0.0, 0.0], [zf, 0.0, xf], [zf, yf, 0.0], [zf, yf, xf]];
|
||||||
|
let extent = tr.transform_points(&corners)?;
|
||||||
|
let z_extent = extent.slice(s![.., 2]);
|
||||||
|
let z_min = (z_extent
|
||||||
|
.iter()
|
||||||
|
.min_by(|a, b| a.total_cmp(b))
|
||||||
|
.unwrap()
|
||||||
|
.floor() as isize
|
||||||
|
- 2)
|
||||||
|
.max(0) as usize;
|
||||||
|
let z_max = (z_extent
|
||||||
|
.iter()
|
||||||
|
.max_by(|a, b| a.total_cmp(b))
|
||||||
|
.unwrap()
|
||||||
|
.ceil() as isize
|
||||||
|
+ 2)
|
||||||
|
.min(shape.z as isize - 1)
|
||||||
|
.max(0) as usize;
|
||||||
|
let frames = (z_min..=z_max)
|
||||||
|
.map(|zi| self.get_cached_frame(c, zi, t, None))
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
|
||||||
|
// TODO; check these translations
|
||||||
|
let u0 = tr.as_translation(&[(z_min as f64), 0.0, 0.0]);
|
||||||
|
let u1 = tr.as_translation(&[-(z_min as f64), 0.0, 0.0]);
|
||||||
|
let tr = u0 * tr * u1;
|
||||||
|
|
||||||
|
match frames.first().map(|f| f.as_ref()) {
|
||||||
|
Some(first) => {
|
||||||
|
macro_rules! interpolate {
|
||||||
|
($var:ident, bool) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed = tr.interpolate_par_order(
|
||||||
|
&stack.mapv(|i| if i { 1.0 } else { 0.0 }),
|
||||||
|
order,
|
||||||
|
)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).mapv(|i| i > 0.5),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
($var:ident, f64) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed = tr.interpolate_par_order(&stack, order)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).to_owned(),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
($var:ident, $t:ty) => {{
|
||||||
|
let views = frames
|
||||||
|
.iter()
|
||||||
|
.map(|f| match f.as_ref() {
|
||||||
|
Frame::$var(a) => Ok(a.view()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
|
let stack = ndarray::stack(ndarray::Axis(0), &views)?;
|
||||||
|
let transformed = tr.interpolate_par_order(&stack, order)?;
|
||||||
|
Ok(Arc::from(Frame::$var(
|
||||||
|
transformed.slice(s![0, .., ..]).mapv(|i| i as $t),
|
||||||
|
)))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
match first {
|
||||||
|
Frame::Bool(_) => interpolate!(Bool, bool),
|
||||||
|
Frame::I8(_) => interpolate!(I8, i8),
|
||||||
|
Frame::U8(_) => interpolate!(U8, u8),
|
||||||
|
Frame::I16(_) => interpolate!(I16, i16),
|
||||||
|
Frame::U16(_) => interpolate!(U16, u16),
|
||||||
|
Frame::I32(_) => interpolate!(I32, i32),
|
||||||
|
Frame::U32(_) => interpolate!(U32, u32),
|
||||||
|
Frame::F32(_) => interpolate!(F32, f32),
|
||||||
|
Frame::F64(_) => interpolate!(F64, f64),
|
||||||
|
Frame::I64(_) => interpolate!(I64, i64),
|
||||||
|
Frame::U64(_) => interpolate!(U64, u64),
|
||||||
|
Frame::I128(_) => interpolate!(I128, i128),
|
||||||
|
Frame::U128(_) => interpolate!(U128, u128),
|
||||||
|
Frame::F128(_) => interpolate!(F128, f64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let shape = self.shape();
|
||||||
|
Ok(Arc::new(match self.pixel_type() {
|
||||||
|
PixelType::Bool => Frame::Bool(Array2::default((shape.y, shape.x))),
|
||||||
|
PixelType::I8 => Frame::I8(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U8 => Frame::U8(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I16 => Frame::I16(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U16 => Frame::U16(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I32 => Frame::I32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::U32 => Frame::U32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::F32 => Frame::F32(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::F64 => Frame::F64(Array2::zeros((shape.y, shape.x))),
|
||||||
|
PixelType::I64 => Frame::I64(Array2::zeros((shape.y, shape.x))),
|
||||||
|
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))),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d => Err(Error::TransformationNotImplemented(d)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+304
-117
@@ -5,18 +5,23 @@ use crate::metadata::Metadata;
|
|||||||
use crate::readers::{Dimensions, DynReader, Frame, Reader};
|
use crate::readers::{Dimensions, DynReader, Frame, Reader};
|
||||||
use crate::stats::MinMax;
|
use crate::stats::MinMax;
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
use crate::transforms::Transforms;
|
use crate::transforms::{TransformKind, Transforms};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use image_registration::transform::Transform;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use itertools::{Itertools, iproduct};
|
use itertools::{Itertools, iproduct};
|
||||||
use ndarray::{
|
use ndarray::{
|
||||||
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
||||||
SliceArg, SliceInfoElem, s,
|
SliceArg, SliceInfoElem, s,
|
||||||
};
|
};
|
||||||
use num::traits::ToBytes;
|
use num::traits::{AsPrimitive, ToBytes};
|
||||||
use num::{Bounded, FromPrimitive, ToPrimitive, Zero};
|
use num::{Bounded, FromPrimitive, One, ToPrimitive, Zero};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use std::any::type_name;
|
use std::any::type_name;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@@ -57,27 +62,43 @@ pub trait Number:
|
|||||||
+ Sync
|
+ Sync
|
||||||
+ AddAssign
|
+ AddAssign
|
||||||
+ Bounded
|
+ Bounded
|
||||||
+ Clone
|
+ Copy
|
||||||
+ Div<Self, Output = Self>
|
+ Div<Self, Output = Self>
|
||||||
+ FromPrimitive
|
+ FromPrimitive
|
||||||
+ PartialOrd
|
+ PartialOrd
|
||||||
+ Zero
|
+ Zero
|
||||||
|
+ One
|
||||||
|
+ AsPrimitive<f64>
|
||||||
{
|
{
|
||||||
|
const ISFLOAT: bool;
|
||||||
}
|
}
|
||||||
impl<T> Number for T where
|
|
||||||
T: 'static
|
macro_rules! impl_int {
|
||||||
+ Send
|
($($t:tt),+ $(,)?) => {
|
||||||
+ Sync
|
$(
|
||||||
+ AddAssign
|
impl Number for $t {
|
||||||
+ Bounded
|
const ISFLOAT: bool = false;
|
||||||
+ Clone
|
}
|
||||||
+ Div<Self, Output = Self>
|
)*
|
||||||
+ FromPrimitive
|
};
|
||||||
+ PartialOrd
|
|
||||||
+ Zero
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_int!(
|
||||||
|
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
|
||||||
|
);
|
||||||
|
|
||||||
|
macro_rules! impl_float {
|
||||||
|
($($t:tt),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
impl Number for $t {
|
||||||
|
const ISFLOAT: bool = true;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_float!(f32, f64);
|
||||||
|
|
||||||
/// sliceable view on an image file
|
/// sliceable view on an image file
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -89,6 +110,8 @@ pub struct View<D: Dimension, R: Reader = DynReader> {
|
|||||||
/// always has all of cztyx with possibly some new axes added
|
/// always has all of cztyx with possibly some new axes added
|
||||||
axes: Vec<Axis>,
|
axes: Vec<Axis>,
|
||||||
operations: IndexMap<Axis, Operation>,
|
operations: IndexMap<Axis, Operation>,
|
||||||
|
#[serde(skip)]
|
||||||
|
channel_names: OnceCell<HashMap<usize, String>>,
|
||||||
dimensionality: PhantomData<D>,
|
dimensionality: PhantomData<D>,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
pub(crate) transforms: Transforms,
|
pub(crate) transforms: Transforms,
|
||||||
@@ -117,6 +140,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
slice,
|
slice,
|
||||||
axes,
|
axes,
|
||||||
operations: IndexMap::new(),
|
operations: IndexMap::new(),
|
||||||
|
channel_names: OnceCell::new(),
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms::default(),
|
transforms: Transforms::default(),
|
||||||
@@ -126,7 +150,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
pub(crate) fn new_with_axes(reader: R, axes: Vec<Axis>) -> Result<Self, Error> {
|
pub(crate) fn new_with_axes(reader: R, axes: Vec<Axis>) -> Result<Self, Error> {
|
||||||
let mut slice = Vec::new();
|
let mut slice = Vec::new();
|
||||||
let shape = reader.shape();
|
let shape = reader.raw_shape();
|
||||||
for axis in axes.iter() {
|
for axis in axes.iter() {
|
||||||
match axis {
|
match axis {
|
||||||
Axis::C => slice.push(SliceInfoElem::Slice {
|
Axis::C => slice.push(SliceInfoElem::Slice {
|
||||||
@@ -183,6 +207,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
axes,
|
axes,
|
||||||
operations: IndexMap::new(),
|
operations: IndexMap::new(),
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
|
channel_names: OnceCell::new(),
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms::default(),
|
transforms: Transforms::default(),
|
||||||
})
|
})
|
||||||
@@ -198,6 +223,26 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
self.reader.series()
|
self.reader.series()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the name of a particular channel
|
||||||
|
pub fn channel_name(&self, channel: usize) -> Result<&str, Error> {
|
||||||
|
if self.channel_names.get().is_none() {
|
||||||
|
let names = self.metadata()?.image[0]
|
||||||
|
.pixels
|
||||||
|
.channel
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| (i, c.name.clone().unwrap_or_else(|| format!("{}", i))))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let _ = self.channel_names.set(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.channel_names
|
||||||
|
.get()
|
||||||
|
.and_then(|names| names.get(&channel))
|
||||||
|
.map(String::as_str)
|
||||||
|
.ok_or(Error::NoChannel(channel))
|
||||||
|
}
|
||||||
|
|
||||||
/// set the maximum number of frames the process-wide cache may hold,
|
/// set the maximum number of frames the process-wide cache may hold,
|
||||||
/// evicting the least recently used frames
|
/// evicting the least recently used frames
|
||||||
pub fn with_cache_capacity(self, capacity: usize) -> Self {
|
pub fn with_cache_capacity(self, capacity: usize) -> Self {
|
||||||
@@ -234,6 +279,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
slice: self.slice,
|
slice: self.slice,
|
||||||
axes: self.axes,
|
axes: self.axes,
|
||||||
operations: self.operations,
|
operations: self.operations,
|
||||||
|
channel_names: self.channel_names,
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: Transforms::default(),
|
transforms: Transforms::default(),
|
||||||
@@ -249,6 +295,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
slice: self.slice,
|
slice: self.slice,
|
||||||
axes: self.axes,
|
axes: self.axes,
|
||||||
operations: self.operations,
|
operations: self.operations,
|
||||||
|
channel_names: self.channel_names,
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: self.transforms,
|
transforms: self.transforms,
|
||||||
@@ -262,6 +309,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
slice: self.slice,
|
slice: self.slice,
|
||||||
axes: self.axes,
|
axes: self.axes,
|
||||||
operations: self.operations,
|
operations: self.operations,
|
||||||
|
channel_names: self.channel_names,
|
||||||
dimensionality: PhantomData,
|
dimensionality: PhantomData,
|
||||||
#[cfg(feature = "transforms")]
|
#[cfg(feature = "transforms")]
|
||||||
transforms: self.transforms,
|
transforms: self.transforms,
|
||||||
@@ -671,7 +719,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
let slice: Vec<_> = index.iter().map(|s| SliceInfoElem::Index(*s)).collect();
|
let slice: Vec<_> = index.iter().map(|s| SliceInfoElem::Index(*s)).collect();
|
||||||
let view = self.clone().into_dyn().slice(slice.as_slice())?;
|
let view = self.clone().into_dyn().slice(slice.as_slice())?;
|
||||||
let arr = view.as_array()?;
|
let arr = view.as_array()?;
|
||||||
Ok(arr.first().unwrap().clone())
|
Ok(*arr.first().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// collect the view into an ndarray
|
/// collect the view into an ndarray
|
||||||
@@ -714,7 +762,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
IndexMap::new()
|
IndexMap::new()
|
||||||
};
|
};
|
||||||
let mut shape_out = Vec::new();
|
let mut shape_out = Vec::new();
|
||||||
let mut slice = Vec::new();
|
let mut slice_out = Vec::new();
|
||||||
let mut ax_out = Vec::new();
|
let mut ax_out = Vec::new();
|
||||||
for (s, a) in self.slice.iter().zip(&self.axes) {
|
for (s, a) in self.slice.iter().zip(&self.axes) {
|
||||||
match s {
|
match s {
|
||||||
@@ -722,7 +770,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
let end = end.expect("slice has no end");
|
let end = end.expect("slice has no end");
|
||||||
if !op_xy.contains_key(a) && !op_czt.contains_key(a) {
|
if !op_xy.contains_key(a) && !op_czt.contains_key(a) {
|
||||||
shape_out.push(((end - start).max(0) / step) as usize);
|
shape_out.push(((end - start).max(0) / step) as usize);
|
||||||
slice.push(SliceInfoElem::Slice {
|
slice_out.push(SliceInfoElem::Slice {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: None,
|
end: None,
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -733,59 +781,54 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
SliceInfoElem::Index(_) => {}
|
SliceInfoElem::Index(_) => {}
|
||||||
SliceInfoElem::NewAxis => {
|
SliceInfoElem::NewAxis => {
|
||||||
shape_out.push(1);
|
shape_out.push(1);
|
||||||
slice.push(SliceInfoElem::Index(0));
|
slice_out.push(SliceInfoElem::Index(0));
|
||||||
ax_out.push(*a);
|
ax_out.push(*a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut slice_reader = vec![Slice::empty(); 5];
|
let mut slice_reader = vec![Slice::empty(); 5];
|
||||||
let mut xy_dim = 0usize;
|
let mut xy_dim = 0usize;
|
||||||
let shape_reader = self.reader.shape();
|
let shape_reader = self.reader.raw_shape();
|
||||||
for (s, &axis) in self.slice.iter().zip(&self.axes) {
|
for (s, &axis) in self.slice.iter().zip(&self.axes) {
|
||||||
match axis {
|
match (axis, s) {
|
||||||
Axis::New => {}
|
(Axis::New, _) => {}
|
||||||
_ => match s {
|
(_, SliceInfoElem::Slice { start, end, step }) => {
|
||||||
SliceInfoElem::Slice { start, end, step } => {
|
if let Axis::X | Axis::Y = axis
|
||||||
if let Axis::X | Axis::Y = axis
|
&& !op_xy.contains_key(&axis)
|
||||||
&& !op_xy.contains_key(&axis)
|
{
|
||||||
{
|
xy_dim += 1;
|
||||||
xy_dim += 1;
|
|
||||||
}
|
|
||||||
slice_reader[axis as usize] = Slice::new(
|
|
||||||
idx_bnd(*start, shape_reader[axis] as isize)?,
|
|
||||||
slc_bnd(end.unwrap(), shape_reader[axis] as isize)?,
|
|
||||||
*step,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
SliceInfoElem::Index(j) => {
|
slice_reader[axis as usize] = Slice::new(
|
||||||
slice_reader[axis as usize] = Slice::new(
|
idx_bnd(*start, shape_reader[axis] as isize)?,
|
||||||
idx_bnd(*j, shape_reader[axis] as isize)?,
|
slc_bnd(end.unwrap(), shape_reader[axis] as isize)?,
|
||||||
slc_bnd(*j + 1, shape_reader[axis] as isize)?,
|
*step,
|
||||||
1,
|
);
|
||||||
);
|
}
|
||||||
}
|
(_, SliceInfoElem::Index(j)) => {
|
||||||
SliceInfoElem::NewAxis => panic!("axis cannot be a new axis"),
|
slice_reader[axis as usize] = Slice::new(
|
||||||
},
|
idx_bnd(*j, shape_reader[axis] as isize)?,
|
||||||
|
slc_bnd(*j + 1, shape_reader[axis] as isize)?,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(_, SliceInfoElem::NewAxis) => panic!("axis cannot be a new axis"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let xy = [
|
let xy = [
|
||||||
self.slice[Axis::Y.pos(&self.axes, &self.slice)?],
|
self.slice[Axis::Y.pos(&self.axes, &self.slice)?],
|
||||||
self.slice[Axis::X.pos(&self.axes, &self.slice)?],
|
self.slice[Axis::X.pos(&self.axes, &self.slice)?],
|
||||||
];
|
];
|
||||||
let mut array = if let Some((_, op)) = op_czt.first() {
|
let mut array = match op_czt.first() {
|
||||||
match op {
|
Some((_, Operation::Max)) => {
|
||||||
Operation::Max => {
|
ArrayD::<T>::from_elem(shape_out.into_dimension(), T::min_value())
|
||||||
ArrayD::<T>::from_elem(shape_out.into_dimension(), T::min_value())
|
|
||||||
}
|
|
||||||
Operation::Min => {
|
|
||||||
ArrayD::<T>::from_elem(shape_out.into_dimension(), T::max_value())
|
|
||||||
}
|
|
||||||
_ => ArrayD::<T>::zeros(shape_out.into_dimension()),
|
|
||||||
}
|
}
|
||||||
} else {
|
Some((_, Operation::Min)) => {
|
||||||
ArrayD::<T>::zeros(shape_out.into_dimension())
|
ArrayD::<T>::from_elem(shape_out.into_dimension(), T::max_value())
|
||||||
|
}
|
||||||
|
_ => ArrayD::<T>::zeros(shape_out.into_dimension()),
|
||||||
};
|
};
|
||||||
let shape = self.reader.shape();
|
|
||||||
|
let shape = self.reader.raw_shape();
|
||||||
let mut axes_out_idx = [None; 5];
|
let mut axes_out_idx = [None; 5];
|
||||||
for (i, ax) in ax_out.iter().enumerate() {
|
for (i, ax) in ax_out.iter().enumerate() {
|
||||||
if *ax < Axis::New {
|
if *ax < Axis::New {
|
||||||
@@ -793,22 +836,56 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let kind = self.transforms.kind();
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let order = if T::ISFLOAT { 3 } else { 0 };
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let mut transform_done = HashSet::new();
|
||||||
|
|
||||||
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2]) {
|
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2]) {
|
||||||
if let Some(i) = axes_out_idx[0] {
|
if let Some(i) = axes_out_idx[0] {
|
||||||
slice[i] = SliceInfoElem::Index(c)
|
slice_out[i] = SliceInfoElem::Index(c)
|
||||||
};
|
};
|
||||||
if let Some(i) = axes_out_idx[1] {
|
if let Some(i) = axes_out_idx[1] {
|
||||||
slice[i] = SliceInfoElem::Index(z)
|
slice_out[i] = SliceInfoElem::Index(z)
|
||||||
};
|
};
|
||||||
if let Some(i) = axes_out_idx[2] {
|
if let Some(i) = axes_out_idx[2] {
|
||||||
slice[i] = SliceInfoElem::Index(t)
|
slice_out[i] = SliceInfoElem::Index(t)
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let transform = self
|
||||||
|
.transforms
|
||||||
|
.get_transform(self.channel_name(c as usize)?, t as usize)?;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let transform_now = if let (TransformKind::Channel, Some((&Axis::C, _)), _)
|
||||||
|
| (TransformKind::Drift, Some((&Axis::T, _)), _)
|
||||||
|
| (TransformKind::Both, Some((&Axis::C | &Axis::T, _)), _)
|
||||||
|
| (
|
||||||
|
TransformKind::Channel | TransformKind::Drift | TransformKind::Both,
|
||||||
|
_,
|
||||||
|
false,
|
||||||
|
) = (&kind, op_czt.first(), xy_dim == 2)
|
||||||
|
{
|
||||||
|
transform_done.insert((c, z, t));
|
||||||
|
transform.map(|transform| (transform, order))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let frame = self.get_cached_frame(
|
||||||
|
(c % shape.c as isize) as usize,
|
||||||
|
(z % shape.z as isize) as usize,
|
||||||
|
(t % shape.t as isize) as usize,
|
||||||
|
transform_now,
|
||||||
|
)?;
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
let frame = self.get_cached_frame(
|
let frame = self.get_cached_frame(
|
||||||
(c % shape.c as isize) as usize,
|
(c % shape.c as isize) as usize,
|
||||||
(z % shape.z as isize) as usize,
|
(z % shape.z as isize) as usize,
|
||||||
(t % shape.t as isize) as usize,
|
(t % shape.t as isize) as usize,
|
||||||
)?;
|
)?;
|
||||||
|
// 0, 1 or 2d frame depending on x or y operations
|
||||||
let arr_frame: Array2<T> = frame.as_ref().try_into()?;
|
let arr_frame: Array2<T> = frame.as_ref().try_into()?;
|
||||||
let arr_frame = match xy_dim {
|
let arr_frame = match xy_dim {
|
||||||
0 => {
|
0 => {
|
||||||
@@ -855,35 +932,28 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
unreachable!("xy cannot be 3d or more");
|
unreachable!("xy cannot be 3d or more");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some((_, op)) = op_czt.first() {
|
// assign arr_frame to output array, apply first of c, z or t operation
|
||||||
match op {
|
match op_czt.first() {
|
||||||
Operation::Max => {
|
Some((_, Operation::Max)) => {
|
||||||
array
|
array
|
||||||
.slice_mut(slice.as_slice())
|
.slice_mut(slice_out.as_slice())
|
||||||
.zip_mut_with(&arr_frame, |x, y| {
|
.zip_mut_with(&arr_frame, |x, y| {
|
||||||
*x = if *x >= *y { x.clone() } else { y.clone() }
|
*x = if *x >= *y { *x } else { *y }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
Operation::Min => {
|
|
||||||
array
|
|
||||||
.slice_mut(slice.as_slice())
|
|
||||||
.zip_mut_with(&arr_frame, |x, y| {
|
|
||||||
*x = if *x < *y { x.clone() } else { y.clone() }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Operation::Sum => {
|
|
||||||
array
|
|
||||||
.slice_mut(slice.as_slice())
|
|
||||||
.zip_mut_with(&arr_frame, |x, y| *x += y.clone());
|
|
||||||
}
|
|
||||||
Operation::Mean => {
|
|
||||||
array
|
|
||||||
.slice_mut(slice.as_slice())
|
|
||||||
.zip_mut_with(&arr_frame, |x, y| *x += y.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
Some((_, Operation::Min)) => {
|
||||||
array.slice_mut(slice.as_slice()).assign(&arr_frame)
|
array
|
||||||
|
.slice_mut(slice_out.as_slice())
|
||||||
|
.zip_mut_with(&arr_frame, |x, y| {
|
||||||
|
*x = if *x < *y { *x } else { *y }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some((_, Operation::Mean | Operation::Sum)) => {
|
||||||
|
array
|
||||||
|
.slice_mut(slice_out.as_slice())
|
||||||
|
.zip_mut_with(&arr_frame, |x, y| *x += *y);
|
||||||
|
}
|
||||||
|
_ => array.slice_mut(slice_out.as_slice()).assign(&arr_frame),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut out = Some(array);
|
let mut out = Some(array);
|
||||||
@@ -894,23 +964,61 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
.collect();
|
.collect();
|
||||||
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
||||||
if let Some(idx) = ax_out.remove(ax) {
|
if let Some(idx) = ax_out.remove(ax) {
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let mut arr = out.take().unwrap();
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
|
let arr = out.take().unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
if let (Axis::C, TransformKind::Channel | TransformKind::Both)
|
||||||
|
| (Axis::T, TransformKind::Drift | TransformKind::Both) = (ax, &kind)
|
||||||
|
{
|
||||||
|
let mut slice_transform = vec![
|
||||||
|
SliceInfoElem::Slice {
|
||||||
|
start: 0,
|
||||||
|
end: None,
|
||||||
|
step: 1
|
||||||
|
};
|
||||||
|
ax_out.len()
|
||||||
|
];
|
||||||
|
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2])
|
||||||
|
{
|
||||||
|
if let Some(transform) = self
|
||||||
|
.transforms
|
||||||
|
.get_transform(self.channel_name(c as usize)?, t as usize)?
|
||||||
|
&& !transform_done.contains(&(c, z, t))
|
||||||
|
{
|
||||||
|
for (axis, i) in [&Axis::C, &Axis::Z, &Axis::T].iter().zip([c, z, t]) {
|
||||||
|
if let Some(axis_pos) = ax_out.get(axis) {
|
||||||
|
slice_transform[*axis_pos] = SliceInfoElem::Index(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let untransformed = arr.slice(slice_transform.as_slice());
|
||||||
|
let transformed = transform
|
||||||
|
.interpolate_par_order(untransformed, order)?
|
||||||
|
.mapv(|i| T::from_f64(i).unwrap());
|
||||||
|
arr.slice_mut(slice_transform.as_slice())
|
||||||
|
.assign(&transformed);
|
||||||
|
}
|
||||||
|
transform_done.insert((c, z, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = op.operate(arr, idx)?;
|
||||||
|
let _ = out.insert(a);
|
||||||
|
|
||||||
for i in ax_out.values_mut() {
|
for i in ax_out.values_mut() {
|
||||||
if *i > idx {
|
if *i > idx {
|
||||||
*i -= 1;
|
*i -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let arr = out.take().unwrap();
|
|
||||||
let a = op.operate(arr, idx)?;
|
|
||||||
let _ = out.insert(a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let n = if let Some((&ax, op)) = op_czt.first()
|
let n = if let Some((ax, Operation::Mean)) = op_czt.first() {
|
||||||
&& *op == Operation::Mean
|
|
||||||
{
|
|
||||||
self.axes
|
self.axes
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.slice.iter())
|
.zip(self.slice.iter())
|
||||||
.find(|(a, _)| **a == ax)
|
.find(|(a, _)| *a == ax)
|
||||||
.and_then(|(_, s)| match s {
|
.and_then(|(_, s)| match s {
|
||||||
SliceInfoElem::Slice { start, end, step } => {
|
SliceInfoElem::Slice { start, end, step } => {
|
||||||
end.map(|e| (((e - start).max(0) / step) as usize).max(1))
|
end.map(|e| (((e - start).max(0) / step) as usize).max(1))
|
||||||
@@ -921,12 +1029,29 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
let array = if n == 1 {
|
let array = if n == 1 {
|
||||||
out.take().unwrap()
|
out.take().unwrap()
|
||||||
} else {
|
} else {
|
||||||
let m = T::from_usize(n).unwrap_or_else(|| T::zero());
|
let m = T::from_usize(n).unwrap_or_else(|| T::zero());
|
||||||
out.take().unwrap().mapv(|x| x / m.clone())
|
out.take().unwrap().mapv(|x| x / m)
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
let mut array = if n == 1 {
|
||||||
|
out.take().unwrap()
|
||||||
|
} else {
|
||||||
|
let m = T::from_usize(n).unwrap_or_else(|| T::zero());
|
||||||
|
out.take().unwrap().mapv(|x| x / m)
|
||||||
|
};
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
if transform_done.is_empty() {
|
||||||
|
let c = slice_reader[0].start;
|
||||||
|
let t = slice_reader[2].start;
|
||||||
|
if let Some(transform) = self.transforms.get_transform(self.channel_name(c as usize)?, t as usize)? {
|
||||||
|
array = transform.interpolate_par_order(&array, order)?.mapv(|i| T::from_f64(i).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ArrayCache::global().insert(key.to_owned(), array.clone());
|
ArrayCache::global().insert(key.to_owned(), array.clone());
|
||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
@@ -957,7 +1082,13 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_key(&self, c: usize, z: usize, t: usize) -> FrameKeyRef<'_> {
|
fn frame_key<'a>(
|
||||||
|
&'a self,
|
||||||
|
c: usize,
|
||||||
|
z: usize,
|
||||||
|
t: usize,
|
||||||
|
#[cfg(feature = "transforms")] transform: Option<(Cow<'a, Transform<IxDyn>>, usize)>,
|
||||||
|
) -> FrameKeyRef<'a> {
|
||||||
FrameKeyRef {
|
FrameKeyRef {
|
||||||
reader: ReaderKeyRef {
|
reader: ReaderKeyRef {
|
||||||
name: self.reader.reader_name(),
|
name: self.reader.reader_name(),
|
||||||
@@ -969,6 +1100,8 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
c,
|
c,
|
||||||
z,
|
z,
|
||||||
t,
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,14 +1118,37 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
slice: &self.slice,
|
slice: &self.slice,
|
||||||
axes: &self.axes,
|
axes: &self.axes,
|
||||||
operations: &self.operations,
|
operations: &self.operations,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transforms: &self.transforms,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cached_frame(&self, c: usize, z: usize, t: usize) -> Result<Arc<Frame>, Error> {
|
pub(crate) fn get_cached_frame(
|
||||||
let key = self.frame_key(c, z, t);
|
&self,
|
||||||
|
c: usize,
|
||||||
|
z: usize,
|
||||||
|
t: usize,
|
||||||
|
#[cfg(feature = "transforms")] transform: Option<(Cow<Transform<IxDyn>>, usize)>,
|
||||||
|
) -> 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)),
|
||||||
|
);
|
||||||
if let Some(frame) = FrameCache::global().get(&key) {
|
if let Some(frame) = FrameCache::global().get(&key) {
|
||||||
return Ok(frame);
|
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)?
|
||||||
|
} else {
|
||||||
|
Arc::new(self.reader.get_frame(c, z, t)?)
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "transforms"))]
|
||||||
let frame = Arc::new(self.reader.get_frame(c, z, t)?);
|
let frame = Arc::new(self.reader.get_frame(c, z, t)?);
|
||||||
FrameCache::global().insert(key.to_owned(), frame.clone());
|
FrameCache::global().insert(key.to_owned(), frame.clone());
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
@@ -1187,7 +1343,7 @@ impl<R: Reader> Item for View<Ix0, R> {
|
|||||||
Array1<T>: MinMax<Output = Array0<T>>,
|
Array1<T>: MinMax<Output = Array0<T>>,
|
||||||
Array2<T>: MinMax<Output = Array1<T>>,
|
Array2<T>: MinMax<Output = Array1<T>>,
|
||||||
{
|
{
|
||||||
Ok(self.as_array()?.first().ok_or(Error::EmptyView)?.clone())
|
Ok(*self.as_array()?.first().ok_or(Error::EmptyView)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1229,7 +1385,7 @@ to_bytes_vec_impl!(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::axes::{Axis, Operation};
|
use crate::axes::{Axis, Operation};
|
||||||
use crate::cache::{
|
use crate::cache::{
|
||||||
ArrayCache, ArrayKey, ArrayKeyRef, FileStamp, FrameCache, ReaderKey, ReaderKeyRef,
|
ArrayCache, ArrayKey, ArrayKeyRef, FileStamp, FrameCache, FrameKey, ReaderKey, ReaderKeyRef,
|
||||||
};
|
};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::readers::{DynReader, Frame, Reader};
|
use crate::readers::{DynReader, Frame, Reader};
|
||||||
@@ -1241,6 +1397,8 @@ mod tests {
|
|||||||
use postcard::{from_bytes, to_stdvec};
|
use postcard::{from_bytes, to_stdvec};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
use crate::transforms::Transforms;
|
||||||
|
|
||||||
fn open(file: &str) -> Result<DynReader, Error> {
|
fn open(file: &str) -> Result<DynReader, Error> {
|
||||||
let path = std::env::current_dir()?
|
let path = std::env::current_dir()?
|
||||||
@@ -1512,19 +1670,19 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn frame_cache() -> Result<(), Error> {
|
fn frame_cache() -> Result<(), Error> {
|
||||||
let cache = FrameCache::new(2);
|
let cache = FrameCache::new(2);
|
||||||
let key = |c: usize, z: usize, t: usize| {
|
let key = |c: usize, z: usize, t: usize| FrameKey {
|
||||||
(
|
reader_key: ReaderKey {
|
||||||
ReaderKey {
|
name: "test".to_string(),
|
||||||
name: "test".to_string(),
|
path: PathBuf::from("test.tif"),
|
||||||
path: PathBuf::from("test.tif"),
|
series: 0,
|
||||||
series: 0,
|
position: 0,
|
||||||
position: 0,
|
stamp: FileStamp::default(),
|
||||||
stamp: FileStamp::default(),
|
},
|
||||||
},
|
c,
|
||||||
c,
|
z,
|
||||||
z,
|
t,
|
||||||
t,
|
#[cfg(feature = "transforms")]
|
||||||
)
|
transform: None,
|
||||||
};
|
};
|
||||||
cache.insert(
|
cache.insert(
|
||||||
key(0, 0, 0),
|
key(0, 0, 0),
|
||||||
@@ -1561,6 +1719,8 @@ mod tests {
|
|||||||
slice: vec![SliceInfoElem::Index(index)],
|
slice: vec![SliceInfoElem::Index(index)],
|
||||||
axes: axes.to_vec(),
|
axes: axes.to_vec(),
|
||||||
operations: vec![],
|
operations: vec![],
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transforms: Transforms::default(),
|
||||||
};
|
};
|
||||||
let k0 = key("u16", 0, &[Axis::T]);
|
let k0 = key("u16", 0, &[Axis::T]);
|
||||||
let k1 = key("u16", 1, &[Axis::T]);
|
let k1 = key("u16", 1, &[Axis::T]);
|
||||||
@@ -1587,6 +1747,8 @@ mod tests {
|
|||||||
slice: &[SliceInfoElem::Index(0)],
|
slice: &[SliceInfoElem::Index(0)],
|
||||||
axes: &[Axis::T],
|
axes: &[Axis::T],
|
||||||
operations: &ops,
|
operations: &ops,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
transforms: &Transforms::default(),
|
||||||
};
|
};
|
||||||
assert!(cache.get::<f64, _>(&borrowed).is_none());
|
assert!(cache.get::<f64, _>(&borrowed).is_none());
|
||||||
assert!(cache.get::<u16, _>(&borrowed).is_some());
|
assert!(cache.get::<u16, _>(&borrowed).is_some());
|
||||||
@@ -1602,12 +1764,37 @@ mod tests {
|
|||||||
let (c, z, t) = (shape[0] - 1, shape[1] - 1, shape[2] - 1);
|
let (c, z, t) = (shape[0] - 1, shape[1] - 1, shape[2] - 1);
|
||||||
let cache = FrameCache::global();
|
let cache = FrameCache::global();
|
||||||
let a: Array5<usize> = view.clone().try_into()?;
|
let a: Array5<usize> = view.clone().try_into()?;
|
||||||
assert!(cache.get(&view.frame_key(c, z, t)).is_some());
|
assert!(
|
||||||
|
cache
|
||||||
|
.get(&view.frame_key(
|
||||||
|
c,
|
||||||
|
z,
|
||||||
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
None
|
||||||
|
))
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
let b: Array5<usize> = view.clone().try_into()?;
|
let b: Array5<usize> = view.clone().try_into()?;
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
// a fresh reader on the same file shares the same cache key
|
// a fresh reader on the same file shares the same cache key
|
||||||
let view2 = open(file)?.view();
|
let view2 = open(file)?.view();
|
||||||
assert_eq!(view.frame_key(c, z, t), view2.frame_key(c, z, t));
|
assert_eq!(
|
||||||
|
view.frame_key(
|
||||||
|
c,
|
||||||
|
z,
|
||||||
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
None
|
||||||
|
),
|
||||||
|
view2.frame_key(
|
||||||
|
c,
|
||||||
|
z,
|
||||||
|
t,
|
||||||
|
#[cfg(feature = "transforms")]
|
||||||
|
None
|
||||||
|
)
|
||||||
|
);
|
||||||
let c2: Array5<usize> = view2.clone().try_into()?;
|
let c2: Array5<usize> = view2.clone().try_into()?;
|
||||||
assert_eq!(a, c2);
|
assert_eq!(a, c2);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in new issue
Block a user