- implement sliced views, including min, max, sum and mean operations

This commit is contained in:
Wim Pomp
2025-04-27 20:07:49 +02:00
parent 87e9715f97
commit 5195ccfcb5
15 changed files with 3566 additions and 1068 deletions

218
src/axes.rs Normal file
View File

@@ -0,0 +1,218 @@
use crate::stats::MinMax;
use anyhow::{anyhow, Error, Result};
use ndarray::{Array, Dimension, Ix2, SliceInfo, SliceInfoElem};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use std::hash::{Hash, Hasher};
use std::str::FromStr;
/// a trait to find axis indices from any object
pub trait Ax {
/// C: 0, Z: 1, T: 2, Y: 3, X: 4
fn n(&self) -> usize;
/// the indices of axes in self.axes, which always has all of CZTYX
fn pos(&self, axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize>;
/// the indices of axes in self.axes, which always has all of CZTYX, but skip axes with an operation
fn pos_op(&self, axes: &[Axis], slice: &[SliceInfoElem], op_axes: &[Axis]) -> Result<usize>;
}
/// Enum for CZTYX axes or a new axis
#[derive(Clone, Copy, Debug, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Axis {
C,
Z,
T,
Y,
X,
New,
}
impl Hash for Axis {
fn hash<H: Hasher>(&self, state: &mut H) {
(*self as usize).hash(state);
}
}
impl FromStr for Axis {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"C" => Ok(Axis::C),
"Z" => Ok(Axis::Z),
"T" => Ok(Axis::T),
"Y" => Ok(Axis::Y),
"X" => Ok(Axis::X),
"NEW" => Ok(Axis::New),
_ => Err(anyhow!("invalid axis: {}", s)),
}
}
}
impl Ax for Axis {
fn n(&self) -> usize {
*self as usize
}
fn pos(&self, axes: &[Axis], _slice: &[SliceInfoElem]) -> Result<usize> {
if let Some(pos) = axes.iter().position(|a| a == self) {
Ok(pos)
} else {
Err(Error::msg("Axis not found in axes"))
}
}
fn pos_op(&self, axes: &[Axis], _slice: &[SliceInfoElem], _op_axes: &[Axis]) -> Result<usize> {
self.pos(axes, _slice)
}
}
impl Ax for usize {
fn n(&self) -> usize {
*self
}
fn pos(&self, _axes: &[Axis], slice: &[SliceInfoElem]) -> Result<usize> {
let idx: Vec<_> = slice
.iter()
.enumerate()
.filter_map(|(i, s)| if s.is_index() { None } else { Some(i) })
.collect();
Ok(idx[*self])
}
fn pos_op(&self, axes: &[Axis], slice: &[SliceInfoElem], op_axes: &[Axis]) -> Result<usize> {
let idx: Vec<_> = axes
.iter()
.zip(slice.iter())
.enumerate()
.filter_map(|(i, (ax, s))| {
if s.is_index() | op_axes.contains(ax) {
None
} else {
Some(i)
}
})
.collect();
assert!(*self < idx.len(), "self: {}, idx: {:?}", self, idx);
Ok(idx[*self])
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) enum Operation {
Max,
Min,
Sum,
Mean,
}
impl Operation {
pub(crate) fn operate<T, D>(
&self,
array: Array<T, D>,
axis: usize,
) -> Result<<Array<T, D> as MinMax>::Output>
where
D: Dimension,
Array<T, D>: MinMax,
{
match self {
Operation::Max => array.max(axis),
Operation::Min => array.min(axis),
Operation::Sum => array.sum(axis),
Operation::Mean => array.mean(axis),
}
}
}
impl PartialEq for Axis {
fn eq(&self, other: &Self) -> bool {
(*self as u8) == (*other as u8)
}
}
pub(crate) fn slice_info<D: Dimension>(
info: &[SliceInfoElem],
) -> Result<SliceInfo<&[SliceInfoElem], Ix2, D>> {
Ok(info.try_into()?)
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "SliceInfoElem")]
pub(crate) enum SliceInfoElemDef {
Slice {
start: isize,
end: Option<isize>,
step: isize,
},
Index(isize),
NewAxis,
}
impl SerializeAs<SliceInfoElem> for SliceInfoElemDef {
fn serialize_as<S>(
source: &SliceInfoElem,
serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
SliceInfoElemDef::serialize(source, serializer)
}
}
impl<'de> DeserializeAs<'de, SliceInfoElem> for SliceInfoElemDef {
fn deserialize_as<D>(deserializer: D) -> std::result::Result<SliceInfoElem, D::Error>
where
D: Deserializer<'de>,
{
SliceInfoElemDef::deserialize(deserializer)
}
}
#[derive(Clone, Debug)]
pub(crate) struct Slice {
start: isize,
end: isize,
step: isize,
}
impl Slice {
pub(crate) fn new(start: isize, end: isize, step: isize) -> Self {
Self { start, end, step }
}
pub(crate) fn empty() -> Self {
Self {
start: 0,
end: 0,
step: 1,
}
}
}
impl Iterator for Slice {
type Item = isize;
fn next(&mut self) -> Option<Self::Item> {
if self.end - self.start >= self.step {
let r = self.start;
self.start += self.step;
Some(r)
} else {
None
}
}
}
impl IntoIterator for &Slice {
type Item = isize;
type IntoIter = Slice;
fn into_iter(self) -> Self::IntoIter {
self.clone()
}
}

View File

@@ -96,6 +96,18 @@ macro_rules! method {
};
}
fn transmute_vec<T, U>(vec: Vec<T>) -> Vec<U> {
unsafe {
// Ensure the original vector is not dropped.
let mut v_clone = std::mem::ManuallyDrop::new(vec);
Vec::from_raw_parts(
v_clone.as_mut_ptr() as *mut U,
v_clone.len(),
v_clone.capacity(),
)
}
}
/// Wrapper around bioformats java class loci.common.DebugTools
pub struct DebugTools;
@@ -125,8 +137,7 @@ impl ChannelSeparator {
}
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>> {
let bi8 = self.open_bi8(index)?;
Ok(unsafe { std::mem::transmute::<Vec<i8>, Vec<u8>>(bi8) })
Ok(transmute_vec(self.open_bi8(index)?))
}
method!(open_bi8, "openBytes", [index: i32|p] => Vec<i8>|c);
@@ -149,8 +160,7 @@ impl ImageReader {
}
pub(crate) fn open_bytes(&self, index: i32) -> Result<Vec<u8>> {
let bi8 = self.open_bi8(index)?;
Ok(unsafe { std::mem::transmute::<Vec<i8>, Vec<u8>>(bi8) })
Ok(transmute_vec(self.open_bi8(index)?))
}
pub(crate) fn ome_xml(&self) -> Result<String> {

View File

@@ -1,333 +1,23 @@
mod bioformats;
mod axes;
#[cfg(feature = "python")]
mod py;
use anyhow::{anyhow, Result};
use bioformats::{DebugTools, ImageReader, MetadataTools};
use ndarray::Array2;
use num::{FromPrimitive, Zero};
use std::any::type_name;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use thread_local::ThreadLocal;
/// Pixel types (u)int(8/16/32) or float(32/64)
#[derive(Clone, Debug)]
pub enum PixelType {
INT8 = 0,
UINT8 = 1,
INT16 = 2,
UINT16 = 3,
INT32 = 4,
UINT32 = 5,
FLOAT = 6,
DOUBLE = 7,
}
impl TryFrom<i32> for PixelType {
type Error = anyhow::Error;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PixelType::INT8),
1 => Ok(PixelType::UINT8),
2 => Ok(PixelType::INT16),
3 => Ok(PixelType::UINT16),
4 => Ok(PixelType::INT32),
5 => Ok(PixelType::UINT32),
6 => Ok(PixelType::FLOAT),
7 => Ok(PixelType::DOUBLE),
_ => Err(anyhow::anyhow!("Unknown pixel type {}", value)),
}
}
}
/// Struct containing frame data in one of eight pixel types. Cast to `Array2<T>` using try_into.
#[derive(Clone, Debug)]
pub enum Frame {
INT8(Array2<i8>),
UINT8(Array2<u8>),
INT16(Array2<i16>),
UINT16(Array2<u16>),
INT32(Array2<i32>),
UINT32(Array2<u32>),
FLOAT(Array2<f32>),
DOUBLE(Array2<f64>),
}
macro_rules! impl_frame_cast {
($t:tt, $s:ident) => {
impl From<Array2<$t>> for Frame {
fn from(value: Array2<$t>) -> Self {
Frame::$s(value)
}
}
};
}
impl_frame_cast!(i8, INT8);
impl_frame_cast!(u8, UINT8);
impl_frame_cast!(i16, INT16);
impl_frame_cast!(u16, UINT16);
impl_frame_cast!(i32, INT32);
impl_frame_cast!(u32, UINT32);
impl_frame_cast!(f32, FLOAT);
impl_frame_cast!(f64, DOUBLE);
impl<T> TryInto<Array2<T>> for Frame
where
T: FromPrimitive + Zero + 'static,
{
type Error = anyhow::Error;
fn try_into(self) -> std::result::Result<Array2<T>, Self::Error> {
let mut err = Ok(());
let arr = match self {
Frame::INT8(v) => v.mapv_into_any(|x| {
T::from_i8(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::UINT8(v) => v.mapv_into_any(|x| {
T::from_u8(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::INT16(v) => v.mapv_into_any(|x| {
T::from_i16(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::UINT16(v) => v.mapv_into_any(|x| {
T::from_u16(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::INT32(v) => v.mapv_into_any(|x| {
T::from_i32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::UINT32(v) => v.mapv_into_any(|x| {
T::from_u32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::FLOAT(v) => v.mapv_into_any(|x| {
T::from_f32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::DOUBLE(v) => v.mapv_into_any(|x| {
T::from_f64(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
};
match err {
Err(err) => Err(err),
Ok(()) => Ok(arr),
}
}
}
/// Reader interface to file. Use get_frame to get data.
pub struct Reader {
image_reader: ThreadLocal<ImageReader>,
/// path to file
pub path: PathBuf,
/// which (if more than 1) of the series in the file to open
pub series: i32,
/// size x (horizontal)
pub size_x: usize,
/// size y (vertical)
pub size_y: usize,
/// size c (# channels)
pub size_c: usize,
/// size z (# slices)
pub size_z: usize,
/// size t (# time/frames)
pub size_t: usize,
/// pixel type ((u)int(8/16/32) or float(32/64))
pub pixel_type: PixelType,
little_endian: bool,
}
impl Deref for Reader {
type Target = ImageReader;
fn deref(&self) -> &Self::Target {
self.image_reader.get_or(|| {
let reader = ImageReader::new().unwrap();
let meta_data_tools = MetadataTools::new().unwrap();
let ome_meta = meta_data_tools.create_ome_xml_metadata().unwrap();
reader.set_metadata_store(ome_meta).unwrap();
reader.set_id(self.path.to_str().unwrap()).unwrap();
reader.set_series(self.series).unwrap();
reader
})
}
}
impl Clone for Reader {
fn clone(&self) -> Self {
Reader::new(&self.path, self.series).unwrap()
}
}
impl Debug for Reader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Reader")
.field("path", &self.path)
.field("series", &self.series)
.field("size_x", &self.size_x)
.field("size_y", &self.size_y)
.field("size_c", &self.size_c)
.field("size_z", &self.size_z)
.field("size_t", &self.size_t)
.field("pixel_type", &self.pixel_type)
.field("little_endian", &self.little_endian)
.finish()
}
}
impl Reader {
/// Create new reader for image file at path.
pub fn new(path: &Path, series: i32) -> Result<Self> {
DebugTools::set_root_level("ERROR")?;
let mut reader = Reader {
image_reader: ThreadLocal::new(),
path: PathBuf::from(path),
series,
size_x: 0,
size_y: 0,
size_c: 0,
size_z: 0,
size_t: 0,
pixel_type: PixelType::INT8,
little_endian: false,
};
reader.size_x = reader.get_size_x()? as usize;
reader.size_y = reader.get_size_y()? as usize;
reader.size_c = reader.get_size_c()? as usize;
reader.size_z = reader.get_size_z()? as usize;
reader.size_t = reader.get_size_t()? as usize;
reader.pixel_type = PixelType::try_from(reader.get_pixel_type()?)?;
reader.little_endian = reader.is_little_endian()?;
Ok(reader)
}
/// Get ome metadata as xml string
pub fn get_ome_xml(&self) -> Result<String> {
self.ome_xml()
}
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>> {
let chunk_size = match self.pixel_type {
PixelType::INT8 => 1,
PixelType::UINT8 => 1,
PixelType::INT16 => 2,
PixelType::UINT16 => 2,
PixelType::INT32 => 4,
PixelType::UINT32 => 4,
PixelType::FLOAT => 4,
PixelType::DOUBLE => 8,
};
Ok(bytes
.chunks(chunk_size)
.skip(channel)
.step_by(self.size_c)
.flat_map(|a| a.to_vec())
.collect())
}
/// Retrieve fame at channel c, slize z and time t.
pub fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame> {
let bytes = if self.is_rgb()? & self.is_interleaved()? {
let index = self.get_index(z as i32, 0, t as i32)?;
self.deinterleave(self.open_bytes(index)?, c)?
} else if self.get_rgb_channel_count()? > 1 {
let channel_separator = bioformats::ChannelSeparator::new(self)?;
let index = channel_separator.get_index(z as i32, c as i32, t as i32)?;
channel_separator.open_bytes(index)?
} else if self.is_indexed()? {
let index = self.get_index(z as i32, 0, t as i32)?;
self.open_bytes(index)?
// TODO: apply LUT
// let _bytes_lut = match self.pixel_type {
// PixelType::INT8 | PixelType::UINT8 => {
// let _lut = self.image_reader.get_8bit_lookup_table()?;
// }
// PixelType::INT16 | PixelType::UINT16 => {
// let _lut = self.image_reader.get_16bit_lookup_table()?;
// }
// _ => {}
// };
} else {
let index = self.get_index(z as i32, c as i32, t as i32)?;
self.open_bytes(index)?
};
self.bytes_to_frame(bytes)
}
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame> {
macro_rules! get_frame {
($t:tt, <$n:expr) => {
Ok(Frame::from(Array2::from_shape_vec(
(self.size_y, self.size_x),
bytes
.chunks($n)
.map(|x| $t::from_le_bytes(x.try_into().unwrap()))
.collect(),
)?))
};
($t:tt, >$n:expr) => {
Ok(Frame::from(Array2::from_shape_vec(
(self.size_y, self.size_x),
bytes
.chunks($n)
.map(|x| $t::from_be_bytes(x.try_into().unwrap()))
.collect(),
)?))
};
}
match (&self.pixel_type, self.little_endian) {
(PixelType::INT8, true) => get_frame!(i8, <1),
(PixelType::UINT8, true) => get_frame!(u8, <1),
(PixelType::INT16, true) => get_frame!(i16, <2),
(PixelType::UINT16, true) => get_frame!(u16, <2),
(PixelType::INT32, true) => get_frame!(i32, <4),
(PixelType::UINT32, true) => get_frame!(u32, <4),
(PixelType::FLOAT, true) => get_frame!(f32, <4),
(PixelType::DOUBLE, true) => get_frame!(f64, <8),
(PixelType::INT8, false) => get_frame!(i8, >1),
(PixelType::UINT8, false) => get_frame!(u8, >1),
(PixelType::INT16, false) => get_frame!(i16, >2),
(PixelType::UINT16, false) => get_frame!(u16, >2),
(PixelType::INT32, false) => get_frame!(i32, >4),
(PixelType::UINT32, false) => get_frame!(u32, >4),
(PixelType::FLOAT, false) => get_frame!(f32, >4),
(PixelType::DOUBLE, false) => get_frame!(f64, >8),
}
}
}
mod reader;
mod stats;
mod view;
#[cfg(test)]
mod tests {
use super::*;
use crate::stats::MinMax;
use ndarray::{Array, Array4, Array5, NewAxis};
use rayon::prelude::*;
use crate::axes::Axis;
use crate::reader::{Frame, Reader};
use anyhow::Result;
use ndarray::{s, Array2};
fn open(file: &str) -> Result<Reader> {
let path = std::env::current_dir()?
.join("tests")
@@ -413,4 +103,160 @@ mod tests {
println!("{}", xml);
Ok(())
}
#[test]
fn view() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let a = view.slice(s![0, 5, 0, .., ..])?;
let b = reader.get_frame(0, 5, 0)?;
let c: Array2<isize> = a.try_into()?;
let d: Array2<isize> = b.try_into()?;
assert_eq!(c, d);
Ok(())
}
#[test]
fn view_shape() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let a = view.slice(s![0, ..5, 0, .., 100..200])?;
let shape = a.shape();
assert_eq!(shape, vec![5, 1024, 100]);
Ok(())
}
#[test]
fn view_new_axis() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let a = Array5::<u8>::zeros((1, 9, 1, 1024, 1024));
let a = a.slice(s![0, ..5, 0, NewAxis, 100..200, ..]);
let v = view.slice(s![0, ..5, 0, NewAxis, 100..200, ..])?;
assert_eq!(v.shape(), a.shape());
println!("\nshape: {:?}", v.shape());
let a = a.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis]);
let v = v.slice(s![NewAxis, .., .., NewAxis, .., .., NewAxis])?;
assert_eq!(v.shape(), a.shape());
Ok(())
}
#[test]
fn view_permute_axes() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let s = view.shape();
let mut a = Array5::<u8>::zeros((s[0], s[1], s[2], s[3], s[4]));
assert_eq!(view.shape(), a.shape());
let b: Array5<usize> = view.clone().try_into()?;
assert_eq!(b.shape(), a.shape());
let view = view.swap_axes(Axis::C, Axis::Z)?;
a.swap_axes(0, 1);
assert_eq!(view.shape(), a.shape());
let b: Array5<usize> = view.clone().try_into()?;
assert_eq!(b.shape(), a.shape());
println!("{:?}", view.axes());
let view = view.permute_axes(&[Axis::X, Axis::Z, Axis::Y])?;
println!("{:?}", view.axes());
let a = a.permuted_axes([4, 1, 2, 0, 3]);
assert_eq!(view.shape(), a.shape());
let b: Array5<usize> = view.clone().try_into()?;
assert_eq!(b.shape(), a.shape());
Ok(())
}
macro_rules! test_max {
($($name:ident: $b:expr $(,)?)*) => {
$(
#[test]
fn $name() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let array: Array5<usize> = view.clone().try_into()?;
let view = view.max_proj($b)?;
let a: Array4<usize> = view.clone().try_into()?;
let b = array.max($b)?;
assert_eq!(a.shape(), b.shape());
assert_eq!(a, b);
Ok(())
}
)*
};
}
test_max! {
max_c: 0
max_z: 1
max_t: 2
max_y: 3
max_x: 4
}
macro_rules! test_index {
($($name:ident: $b:expr $(,)?)*) => {
$(
#[test]
fn $name() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let view = reader.view();
let v4: Array<usize, _> = view.slice($b)?.try_into()?;
let a5: Array5<usize> = reader.view().try_into()?;
let a4 = a5.slice($b).to_owned();
assert_eq!(a4, v4);
Ok(())
}
)*
};
}
test_index! {
index_0: s![.., .., .., .., ..]
index_1: s![0, .., .., .., ..]
index_2: s![.., 0, .., .., ..]
index_3: s![.., .., 0, .., ..]
index_4: s![.., .., .., 0, ..]
index_5: s![.., .., .., .., 0]
index_6: s![0, 0, .., .., ..]
index_7: s![0, .., 0, .., ..]
index_8: s![0, .., .., 0, ..]
index_9: s![0, .., .., .., 0]
index_a: s![.., 0, 0, .., ..]
index_b: s![.., 0, .., 0, ..]
index_c: s![.., 0, .., .., 0]
index_d: s![.., .., 0, 0, ..]
index_e: s![.., .., 0, .., 0]
index_f: s![.., .., .., 0, 0]
index_g: s![0, 0, 0, .., ..]
index_h: s![0, 0, .., 0, ..]
index_i: s![0, 0, .., .., 0]
index_j: s![0, .., 0, 0, ..]
index_k: s![0, .., 0, .., 0]
index_l: s![0, .., .., 0, 0]
index_m: s![0, 0, 0, 0, ..]
index_n: s![0, 0, 0, .., 0]
index_o: s![0, 0, .., 0, 0]
index_p: s![0, .., 0, 0, 0]
index_q: s![.., 0, 0, 0, 0]
index_r: s![0, 0, 0, 0, 0]
}
#[test]
fn dyn_view() -> Result<()> {
let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif";
let reader = open(file)?;
let a = reader.view().into_dyn();
let b = a.max_proj(1)?;
let c = b.slice(s![0, 0, .., ..])?;
let d = c.as_array::<usize>()?;
assert_eq!(d.shape(), [1024, 1024]);
Ok(())
}
}

605
src/py.rs
View File

@@ -1,64 +1,600 @@
use crate::axes::Axis;
use crate::bioformats::download_bioformats;
use crate::{Frame, Reader};
use numpy::ToPyArray;
use crate::reader::{PixelType, Reader};
use crate::view::{Item, View};
use anyhow::{anyhow, Result};
use ndarray::{Ix0, IxDyn, SliceInfoElem};
use numpy::IntoPyArray;
use pyo3::prelude::*;
use pyo3::types::{PyEllipsis, PyInt, PyList, PySlice, PySliceMethods, PyString, PyTuple};
use pyo3::IntoPyObjectExt;
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use std::path::PathBuf;
#[pyclass(subclass)]
#[pyo3(name = "Reader")]
#[derive(Debug)]
struct PyReader {
reader: Reader,
#[pyclass(module = "ndbioimage.ndbioimage_rs")]
struct ViewConstructor;
#[pymethods]
impl ViewConstructor {
#[new]
fn new() -> Self {
Self
}
fn __getstate__(&self) -> (u8,) {
(0,)
}
fn __setstate__(&self, _state: (u8,)) {}
#[staticmethod]
fn __call__(state: String) -> PyResult<PyView> {
if let Ok(new) = from_str(&state) {
Ok(new)
} else {
Err(anyhow!("cannot parse state").into())
}
}
}
#[pyclass(subclass, module = "ndbioimage.ndbioimage_rs")]
#[pyo3(name = "View")]
#[derive(Debug, Serialize, Deserialize)]
struct PyView {
view: View<IxDyn>,
dtype: PixelType,
}
#[pymethods]
impl PyReader {
impl PyView {
#[new]
fn new(path: &str, series: usize) -> PyResult<Self> {
#[pyo3(signature = (path, series = 0, dtype = "uint16"))]
/// new view on a file at path, open series #, open as dtype: (u)int(8/16/32) or float(32/64)
fn new(path: &str, series: usize, dtype: &str) -> PyResult<Self> {
let mut path = PathBuf::from(path);
if path.is_dir() {
for file in path.read_dir()? {
if let Ok(f) = file {
let p = f.path();
if f.path().is_file() & (p.extension() == Some("tif".as_ref())) {
path = p;
break;
}
for file in path.read_dir()?.flatten() {
let p = file.path();
if file.path().is_file() & (p.extension() == Some("tif".as_ref())) {
path = p;
break;
}
}
}
Ok(PyReader {
reader: Reader::new(&path, series as i32)?,
Ok(Self {
view: Reader::new(&path, series as i32)?.view().into_dyn(),
dtype: dtype.parse()?,
})
}
/// close the file: does nothing as this is handled automatically
fn close(&self) -> PyResult<()> {
Ok(())
}
fn copy(&self) -> PyView {
PyView {
view: self.view.clone(),
dtype: self.dtype.clone(),
}
}
/// slice the view and return a new view or a single number
fn __getitem__<'py>(
&self,
py: Python<'py>,
n: Bound<'py, PyAny>,
) -> PyResult<Bound<'py, PyAny>> {
let slice: Vec<_> = if n.is_instance_of::<PyTuple>() {
n.downcast_into::<PyTuple>()?.into_iter().collect()
} else if n.is_instance_of::<PyList>() {
n.downcast_into::<PyList>()?.into_iter().collect()
} else {
vec![n]
};
let mut new_slice = Vec::new();
let mut ellipsis = None;
let shape = self.view.shape();
for (i, (s, t)) in slice.iter().zip(shape.iter()).enumerate() {
if s.is_instance_of::<PyInt>() {
new_slice.push(SliceInfoElem::Index(
s.downcast::<PyInt>()?.extract::<isize>()?,
));
} else if s.is_instance_of::<PySlice>() {
let u = s.downcast::<PySlice>()?.indices(*t as isize)?;
new_slice.push(SliceInfoElem::Slice {
start: u.start,
end: Some(u.stop),
step: u.step,
});
} else if s.is_instance_of::<PyEllipsis>() {
if ellipsis.is_some() {
return Err(anyhow!("cannot have more than one ellipsis").into());
}
let _ = ellipsis.insert(i);
} else {
return Err(anyhow!("cannot convert {:?} to slice", s).into());
}
}
if new_slice.len() > shape.len() {
return Err(anyhow!(
"got more indices ({}) than dimensions ({})",
new_slice.len(),
shape.len()
)
.into());
}
while new_slice.len() < shape.len() {
if let Some(i) = ellipsis {
new_slice.insert(
i,
SliceInfoElem::Slice {
start: 0,
end: None,
step: 1,
},
)
} else {
new_slice.push(SliceInfoElem::Slice {
start: 0,
end: None,
step: 1,
})
}
}
let view = self.view.slice(new_slice.as_slice())?;
if view.ndim() == 0 {
Ok(match self.dtype {
PixelType::I8 => view
.into_dimensionality::<Ix0>()?
.item::<i8>()?
.into_pyobject(py)?
.into_any(),
PixelType::U8 => view
.into_dimensionality::<Ix0>()?
.item::<u8>()?
.into_pyobject(py)?
.into_any(),
PixelType::I16 => view
.into_dimensionality::<Ix0>()?
.item::<i16>()?
.into_pyobject(py)?
.into_any(),
PixelType::U16 => view
.into_dimensionality::<Ix0>()?
.item::<u16>()?
.into_pyobject(py)?
.into_any(),
PixelType::I32 => view
.into_dimensionality::<Ix0>()?
.item::<i32>()?
.into_pyobject(py)?
.into_any(),
PixelType::U32 => view
.into_dimensionality::<Ix0>()?
.item::<u32>()?
.into_pyobject(py)?
.into_any(),
PixelType::F32 => view
.into_dimensionality::<Ix0>()?
.item::<f32>()?
.into_pyobject(py)?
.into_any(),
PixelType::F64 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I64 => view
.into_dimensionality::<Ix0>()?
.item::<i64>()?
.into_pyobject(py)?
.into_any(),
PixelType::U64 => view
.into_dimensionality::<Ix0>()?
.item::<u64>()?
.into_pyobject(py)?
.into_any(),
PixelType::I128 => view
.into_dimensionality::<Ix0>()?
.item::<i128>()?
.into_pyobject(py)?
.into_any(),
PixelType::U128 => view
.into_dimensionality::<Ix0>()?
.item::<u128>()?
.into_pyobject(py)?
.into_any(),
PixelType::F128 => view
.into_dimensionality::<Ix0>()?
.item::<f64>()?
.into_pyobject(py)?
.into_any(),
})
} else {
PyView {
view,
dtype: self.dtype.clone(),
}
.into_bound_py_any(py)
}
}
fn __reduce__(&self) -> PyResult<(ViewConstructor, (String,))> {
if let Ok(s) = to_string(self) {
Ok((ViewConstructor, (s,)))
} else {
Err(anyhow!("cannot get state").into())
}
}
/// retrieve a single frame at czt, sliced accordingly
fn get_frame<'py>(
&self,
py: Python<'py>,
c: usize,
z: usize,
t: usize,
c: isize,
z: isize,
t: isize,
) -> PyResult<Bound<'py, PyAny>> {
Ok(match self.reader.get_frame(c, z, t)? {
Frame::INT8(arr) => arr.to_pyarray(py).into_any(),
Frame::UINT8(arr) => arr.to_pyarray(py).into_any(),
Frame::INT16(arr) => arr.to_pyarray(py).into_any(),
Frame::UINT16(arr) => arr.to_pyarray(py).into_any(),
Frame::INT32(arr) => arr.to_pyarray(py).into_any(),
Frame::UINT32(arr) => arr.to_pyarray(py).into_any(),
Frame::FLOAT(arr) => arr.to_pyarray(py).into_any(),
Frame::DOUBLE(arr) => arr.to_pyarray(py).into_any(),
Ok(match self.dtype {
PixelType::I8 => self
.view
.get_frame::<i8>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U8 => self
.view
.get_frame::<u8>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I16 => self
.view
.get_frame::<i16>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U16 => self
.view
.get_frame::<u16>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I32 => self
.view
.get_frame::<i32>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U32 => self
.view
.get_frame::<u32>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F32 => self
.view
.get_frame::<f32>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F64 => self
.view
.get_frame::<f64>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I64 => self
.view
.get_frame::<i64>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U64 => self
.view
.get_frame::<u64>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::I128 => self
.view
.get_frame::<i64>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::U128 => self
.view
.get_frame::<u64>(c, z, t)?
.into_pyarray(py)
.into_any(),
PixelType::F128 => self
.view
.get_frame::<f64>(c, z, t)?
.into_pyarray(py)
.into_any(),
})
}
/// retrieve the ome metadata as an xml string
fn get_ome_xml(&self) -> PyResult<String> {
Ok(self.reader.get_ome_xml()?)
Ok(self.view.get_ome_xml()?)
}
fn close(&mut self) -> PyResult<()> {
self.reader.close()?;
/// the file path
#[getter]
fn path(&self) -> PyResult<String> {
Ok(self.view.path.display().to_string())
}
/// the series in the file
#[getter]
fn series(&self) -> PyResult<i32> {
Ok(self.view.series)
}
/// the axes in the view
#[getter]
fn axes(&self) -> Vec<String> {
self.view
.axes()
.iter()
.map(|a| format!("{:?}", a))
.collect()
}
/// the shape of the view
#[getter]
fn shape(&self) -> Vec<usize> {
self.view.shape()
}
#[getter]
fn slice(&self) -> PyResult<Vec<String>> {
Ok(self
.view
.get_slice()
.iter()
.map(|s| format!("{:#?}", s))
.collect())
}
/// the number of pixels in the view
#[getter]
fn size(&self) -> usize {
self.view.size()
}
/// the number of dimensions in the view
#[getter]
fn ndim(&self) -> usize {
self.view.ndim()
}
/// find the position of an axis
#[pyo3(text_signature = "axis: str | int")]
fn get_ax(&self, axis: Bound<'_, PyAny>) -> PyResult<usize> {
if axis.is_instance_of::<PyString>() {
let axis = axis
.downcast_into::<PyString>()?
.extract::<String>()?
.parse::<Axis>()?;
Ok(self
.view
.axes()
.iter()
.position(|a| *a == axis)
.ok_or_else(|| anyhow!("cannot find axis {:?}", axis))?)
} else if axis.is_instance_of::<PyInt>() {
Ok(axis.downcast_into::<PyInt>()?.extract::<usize>()?)
} else {
Err(anyhow!("cannot convert to axis").into())
}
}
/// swap two axes
#[pyo3(text_signature = "ax0: str | int, ax1: str | int")]
fn swap_axes(&self, ax0: Bound<'_, PyAny>, ax1: Bound<'_, PyAny>) -> PyResult<Self> {
let ax0 = self.get_ax(ax0)?;
let ax1 = self.get_ax(ax1)?;
let view = self.view.swap_axes(ax0, ax1)?;
Ok(PyView {
view,
dtype: self.dtype.clone(),
})
}
/// permute the order of the axes
#[pyo3(signature = (axes = None), text_signature = "axes: list[str | int] = None")]
fn transpose(&self, axes: Option<Vec<Bound<'_, PyAny>>>) -> PyResult<Self> {
let view = if let Some(axes) = axes {
let ax = axes
.into_iter()
.map(|a| self.get_ax(a))
.collect::<Result<Vec<_>, _>>()?;
self.view.permute_axes(&ax)?
} else {
self.view.transpose()?
};
Ok(PyView {
view,
dtype: self.dtype.clone(),
})
}
/// collect data into a numpy array
fn as_array<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
Ok(match self.dtype {
PixelType::I8 => self.view.as_array_dyn::<i8>()?.into_pyarray(py).into_any(),
PixelType::U8 => self.view.as_array_dyn::<u8>()?.into_pyarray(py).into_any(),
PixelType::I16 => self.view.as_array_dyn::<i16>()?.into_pyarray(py).into_any(),
PixelType::U16 => self.view.as_array_dyn::<u16>()?.into_pyarray(py).into_any(),
PixelType::I32 => self.view.as_array_dyn::<i32>()?.into_pyarray(py).into_any(),
PixelType::U32 => self.view.as_array_dyn::<u32>()?.into_pyarray(py).into_any(),
PixelType::F32 => self.view.as_array_dyn::<f32>()?.into_pyarray(py).into_any(),
PixelType::F64 => self.view.as_array_dyn::<f64>()?.into_pyarray(py).into_any(),
PixelType::I64 => self.view.as_array_dyn::<i64>()?.into_pyarray(py).into_any(),
PixelType::U64 => self.view.as_array_dyn::<u64>()?.into_pyarray(py).into_any(),
PixelType::I128 => self.view.as_array_dyn::<i64>()?.into_pyarray(py).into_any(),
PixelType::U128 => self.view.as_array_dyn::<u64>()?.into_pyarray(py).into_any(),
PixelType::F128 => self.view.as_array_dyn::<f64>()?.into_pyarray(py).into_any(),
})
}
/// change the data type of the view: (u)int(8/16/32) or float(32/64)
fn as_type(&self, dtype: String) -> PyResult<Self> {
Ok(PyView {
view: self.view.clone(),
dtype: dtype.parse()?,
})
}
#[getter]
fn get_dtype(&self) -> PyResult<&str> {
Ok(match self.dtype {
PixelType::I8 => "int8",
PixelType::U8 => "uint8",
PixelType::I16 => "int16",
PixelType::U16 => "uint16",
PixelType::I32 => "int32",
PixelType::U32 => "uint32",
PixelType::F32 => "float32",
PixelType::F64 => "float64",
PixelType::I64 => "int64",
PixelType::U64 => "uint64",
PixelType::I128 => "int128",
PixelType::U128 => "uint128",
PixelType::F128 => "float128",
})
}
#[setter]
fn set_dtype(&mut self, dtype: String) -> PyResult<()> {
self.dtype = dtype.parse()?;
Ok(())
}
/// get the maximum overall or along a given axis
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
fn max<'py>(
&self,
py: Python<'py>,
axis: Option<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
if let Some(axis) = axis {
PyView {
dtype: self.dtype.clone(),
view: self.view.max_proj(self.get_ax(axis)?)?,
}
.into_bound_py_any(py)
} else {
Ok(match self.dtype {
PixelType::I8 => self.view.max::<i8>()?.into_pyobject(py)?.into_any(),
PixelType::U8 => self.view.max::<u8>()?.into_pyobject(py)?.into_any(),
PixelType::I16 => self.view.max::<i16>()?.into_pyobject(py)?.into_any(),
PixelType::U16 => self.view.max::<u16>()?.into_pyobject(py)?.into_any(),
PixelType::I32 => self.view.max::<i32>()?.into_pyobject(py)?.into_any(),
PixelType::U32 => self.view.max::<u32>()?.into_pyobject(py)?.into_any(),
PixelType::F32 => self.view.max::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.max::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.max::<f64>()?.into_pyobject(py)?.into_any(),
})
}
}
/// get the minimum overall or along a given axis
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
fn min<'py>(
&self,
py: Python<'py>,
axis: Option<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
if let Some(axis) = axis {
PyView {
dtype: self.dtype.clone(),
view: self.view.min_proj(self.get_ax(axis)?)?,
}
.into_bound_py_any(py)
} else {
Ok(match self.dtype {
PixelType::I8 => self.view.min::<i8>()?.into_pyobject(py)?.into_any(),
PixelType::U8 => self.view.min::<u8>()?.into_pyobject(py)?.into_any(),
PixelType::I16 => self.view.min::<i16>()?.into_pyobject(py)?.into_any(),
PixelType::U16 => self.view.min::<u16>()?.into_pyobject(py)?.into_any(),
PixelType::I32 => self.view.min::<i32>()?.into_pyobject(py)?.into_any(),
PixelType::U32 => self.view.min::<u32>()?.into_pyobject(py)?.into_any(),
PixelType::F32 => self.view.min::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.min::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.min::<f64>()?.into_pyobject(py)?.into_any(),
})
}
}
/// get the mean overall or along a given axis
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
fn mean<'py>(
&self,
py: Python<'py>,
axis: Option<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
if let Some(axis) = axis {
let dtype = if let PixelType::F32 = self.dtype {
PixelType::F32
} else {
PixelType::F64
};
PyView {
dtype,
view: self.view.mean_proj(self.get_ax(axis)?)?,
}
.into_bound_py_any(py)
} else {
Ok(match self.dtype {
PixelType::F32 => self.view.mean::<f32>()?.into_pyobject(py)?.into_any(),
_ => self.view.mean::<f64>()?.into_pyobject(py)?.into_any(),
})
}
}
/// get the sum overall or along a given axis
#[pyo3(signature = (axis = None), text_signature = "axis: str | int")]
fn sum<'py>(
&self,
py: Python<'py>,
axis: Option<Bound<'py, PyAny>>,
) -> PyResult<Bound<'py, PyAny>> {
let dtype = match self.dtype {
PixelType::I8 => PixelType::I16,
PixelType::U8 => PixelType::U16,
PixelType::I16 => PixelType::I32,
PixelType::U16 => PixelType::U32,
PixelType::I32 => PixelType::I64,
PixelType::U32 => PixelType::U64,
PixelType::F32 => PixelType::F32,
PixelType::F64 => PixelType::F64,
PixelType::I64 => PixelType::I128,
PixelType::U64 => PixelType::U128,
PixelType::I128 => PixelType::I128,
PixelType::U128 => PixelType::U128,
PixelType::F128 => PixelType::F128,
};
if let Some(axis) = axis {
PyView {
dtype,
view: self.view.sum_proj(self.get_ax(axis)?)?,
}
.into_bound_py_any(py)
} else {
Ok(match self.dtype {
PixelType::F32 => self.view.sum::<f32>()?.into_pyobject(py)?.into_any(),
PixelType::F64 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
PixelType::I64 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U64 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::I128 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
PixelType::U128 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
PixelType::F128 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
_ => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
})
}
}
}
pub(crate) fn ndbioimage_file() -> anyhow::Result<PathBuf> {
@@ -75,7 +611,8 @@ pub(crate) fn ndbioimage_file() -> anyhow::Result<PathBuf> {
#[pymodule]
#[pyo3(name = "ndbioimage_rs")]
fn ndbioimage_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyReader>()?;
m.add_class::<PyView>()?;
m.add_class::<ViewConstructor>()?;
#[pyfn(m)]
#[pyo3(name = "download_bioformats")]

442
src/reader.rs Normal file
View File

@@ -0,0 +1,442 @@
use crate::axes::Axis;
use crate::bioformats;
use crate::bioformats::{DebugTools, ImageReader, MetadataTools};
use crate::view::View;
use anyhow::{anyhow, Error, Result};
use ndarray::{s, Array2, Ix5};
use num::{FromPrimitive, Zero};
use serde::{Deserialize, Serialize};
use std::any::type_name;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use thread_local::ThreadLocal;
/// Pixel types (u)int(8/16/32) or float(32/64), (u/i)(64/128) are not included in bioformats
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PixelType {
I8,
U8,
I16,
U16,
I32,
U32,
F32,
F64,
I64,
U64,
I128,
U128,
F128,
}
impl TryFrom<i32> for PixelType {
type Error = Error;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PixelType::I8),
1 => Ok(PixelType::U8),
2 => Ok(PixelType::I16),
3 => Ok(PixelType::U16),
4 => Ok(PixelType::I32),
5 => Ok(PixelType::U32),
6 => Ok(PixelType::F32),
7 => Ok(PixelType::F64),
8 => Ok(PixelType::I64),
9 => Ok(PixelType::U64),
10 => Ok(PixelType::I128),
11 => Ok(PixelType::U128),
12 => Ok(PixelType::F128),
_ => Err(anyhow::anyhow!("Unknown pixel type {}", value)),
}
}
}
impl FromStr for PixelType {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"int8" | "i8" => Ok(PixelType::I8),
"uint8" | "u8" => Ok(PixelType::U8),
"int16" | "i16" => Ok(PixelType::I16),
"uint16" | "u16" => Ok(PixelType::U16),
"int32" | "i32" => Ok(PixelType::I32),
"uint32" | "u32" => Ok(PixelType::U32),
"float" | "f32" | "float32" => Ok(PixelType::F32),
"double" | "f64" | "float64" => Ok(PixelType::F64),
"int64" | "i64" => Ok(PixelType::I64),
"uint64" | "u64" => Ok(PixelType::U64),
"int128" | "i128" => Ok(PixelType::I128),
"uint128" | "u128" => Ok(PixelType::U128),
"extended" | "f128" => Ok(PixelType::F128),
_ => Err(anyhow::anyhow!("Unknown pixel type {}", s)),
}
}
}
/// Struct containing frame data in one of eight pixel types. Cast to `Array2<T>` using try_into.
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub enum Frame {
I8(Array2<i8>),
U8(Array2<u8>),
I16(Array2<i16>),
U16(Array2<u16>),
I32(Array2<i32>),
U32(Array2<u32>),
F32(Array2<f32>),
F64(Array2<f64>),
I64(Array2<i64>),
U64(Array2<u64>),
I128(Array2<i128>),
U128(Array2<u128>),
F128(Array2<f64>), // f128 is nightly
}
macro_rules! impl_frame_cast {
($($t:tt: $s:ident $(,)?)*) => {
$(
impl From<Array2<$t>> for Frame {
fn from(value: Array2<$t>) -> Self {
Frame::$s(value)
}
}
)*
};
}
impl_frame_cast! {
u8: U8
i8: I8
i16: I16
u16: U16
i32: I32
u32: U32
f32: F32
f64: F64
i64: I64
u64: U64
i128: I128
u128: U128
}
#[cfg(target_pointer_width = "32")]
impl_frame_cast! {
usize: UINT32
isize: INT32
}
impl<T> TryInto<Array2<T>> for Frame
where
T: FromPrimitive + Zero + 'static,
{
type Error = Error;
fn try_into(self) -> Result<Array2<T>, Self::Error> {
let mut err = Ok(());
let arr = match self {
Frame::I8(v) => v.mapv_into_any(|x| {
T::from_i8(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::U8(v) => v.mapv_into_any(|x| {
T::from_u8(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::I16(v) => v.mapv_into_any(|x| {
T::from_i16(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::U16(v) => v.mapv_into_any(|x| {
T::from_u16(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::I32(v) => v.mapv_into_any(|x| {
T::from_i32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::U32(v) => v.mapv_into_any(|x| {
T::from_u32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::F32(v) => v.mapv_into_any(|x| {
T::from_f32(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::F64(v) | Frame::F128(v) => v.mapv_into_any(|x| {
T::from_f64(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::I64(v) => v.mapv_into_any(|x| {
T::from_i64(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::U64(v) => v.mapv_into_any(|x| {
T::from_u64(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::I128(v) => v.mapv_into_any(|x| {
T::from_i128(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
Frame::U128(v) => v.mapv_into_any(|x| {
T::from_u128(x).unwrap_or_else(|| {
err = Err(anyhow!("cannot convert {} into {}", x, type_name::<T>()));
T::zero()
})
}),
};
match err {
Err(err) => Err(err),
Ok(()) => Ok(arr),
}
}
}
/// Reader interface to file. Use get_frame to get data.
#[derive(Serialize, Deserialize)]
pub struct Reader {
#[serde(skip)]
image_reader: ThreadLocal<ImageReader>,
/// path to file
pub path: PathBuf,
/// which (if more than 1) of the series in the file to open
pub series: i32,
/// size x (horizontal)
pub size_x: usize,
/// size y (vertical)
pub size_y: usize,
/// size c (# channels)
pub size_c: usize,
/// size z (# slices)
pub size_z: usize,
/// size t (# time/frames)
pub size_t: usize,
/// pixel type ((u)int(8/16/32) or float(32/64))
pub pixel_type: PixelType,
little_endian: bool,
}
impl Deref for Reader {
type Target = ImageReader;
fn deref(&self) -> &Self::Target {
self.image_reader.get_or(|| {
let reader = ImageReader::new().unwrap();
let meta_data_tools = MetadataTools::new().unwrap();
let ome_meta = meta_data_tools.create_ome_xml_metadata().unwrap();
reader.set_metadata_store(ome_meta).unwrap();
reader.set_id(self.path.to_str().unwrap()).unwrap();
reader.set_series(self.series).unwrap();
reader
})
}
}
impl Clone for Reader {
fn clone(&self) -> Self {
Reader::new(&self.path, self.series).unwrap()
}
}
impl Debug for Reader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Reader")
.field("path", &self.path)
.field("series", &self.series)
.field("size_x", &self.size_x)
.field("size_y", &self.size_y)
.field("size_c", &self.size_c)
.field("size_z", &self.size_z)
.field("size_t", &self.size_t)
.field("pixel_type", &self.pixel_type)
.field("little_endian", &self.little_endian)
.finish()
}
}
impl Reader {
/// Create a new reader for the image file at a path, and open series #.
pub fn new(path: &Path, series: i32) -> Result<Self> {
DebugTools::set_root_level("ERROR")?;
let mut reader = Reader {
image_reader: ThreadLocal::default(),
path: PathBuf::from(path),
series,
size_x: 0,
size_y: 0,
size_c: 0,
size_z: 0,
size_t: 0,
pixel_type: PixelType::I8,
little_endian: false,
};
reader.size_x = reader.get_size_x()? as usize;
reader.size_y = reader.get_size_y()? as usize;
reader.size_c = reader.get_size_c()? as usize;
reader.size_z = reader.get_size_z()? as usize;
reader.size_t = reader.get_size_t()? as usize;
reader.pixel_type = PixelType::try_from(reader.get_pixel_type()?)?;
reader.little_endian = reader.is_little_endian()?;
Ok(reader)
}
/// Get ome metadata as xml string
pub fn get_ome_xml(&self) -> Result<String> {
self.ome_xml()
}
fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>> {
let chunk_size = match self.pixel_type {
PixelType::I8 => 1,
PixelType::U8 => 1,
PixelType::I16 => 2,
PixelType::U16 => 2,
PixelType::I32 => 4,
PixelType::U32 => 4,
PixelType::F32 => 4,
PixelType::F64 => 8,
PixelType::I64 => 8,
PixelType::U64 => 8,
PixelType::I128 => 16,
PixelType::U128 => 16,
PixelType::F128 => 8,
};
Ok(bytes
.chunks(chunk_size)
.skip(channel)
.step_by(self.size_c)
.flat_map(|a| a.to_vec())
.collect())
}
/// Retrieve fame at channel c, slize z and time t.
pub fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame> {
let bytes = if self.is_rgb()? & self.is_interleaved()? {
let index = self.get_index(z as i32, 0, t as i32)?;
self.deinterleave(self.open_bytes(index)?, c)?
} else if self.get_rgb_channel_count()? > 1 {
let channel_separator = bioformats::ChannelSeparator::new(self)?;
let index = channel_separator.get_index(z as i32, c as i32, t as i32)?;
channel_separator.open_bytes(index)?
} else if self.is_indexed()? {
let index = self.get_index(z as i32, 0, t as i32)?;
self.open_bytes(index)?
// TODO: apply LUT
// let _bytes_lut = match self.pixel_type {
// PixelType::INT8 | PixelType::UINT8 => {
// let _lut = self.image_reader.get_8bit_lookup_table()?;
// }
// PixelType::INT16 | PixelType::UINT16 => {
// let _lut = self.image_reader.get_16bit_lookup_table()?;
// }
// _ => {}
// };
} else {
let index = self.get_index(z as i32, c as i32, t as i32)?;
self.open_bytes(index)?
};
self.bytes_to_frame(bytes)
}
fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame> {
macro_rules! get_frame {
($t:tt, <$n:expr) => {
Ok(Frame::from(Array2::from_shape_vec(
(self.size_y, self.size_x),
bytes
.chunks($n)
.map(|x| $t::from_le_bytes(x.try_into().unwrap()))
.collect(),
)?))
};
($t:tt, >$n:expr) => {
Ok(Frame::from(Array2::from_shape_vec(
(self.size_y, self.size_x),
bytes
.chunks($n)
.map(|x| $t::from_be_bytes(x.try_into().unwrap()))
.collect(),
)?))
};
}
match (&self.pixel_type, self.little_endian) {
(PixelType::I8, true) => get_frame!(i8, <1),
(PixelType::U8, true) => get_frame!(u8, <1),
(PixelType::I16, true) => get_frame!(i16, <2),
(PixelType::U16, true) => get_frame!(u16, <2),
(PixelType::I32, true) => get_frame!(i32, <4),
(PixelType::U32, true) => get_frame!(u32, <4),
(PixelType::F32, true) => get_frame!(f32, <4),
(PixelType::F64, true) => get_frame!(f64, <8),
(PixelType::I64, true) => get_frame!(i64, <8),
(PixelType::U64, true) => get_frame!(u64, <8),
(PixelType::I128, true) => get_frame!(i128, <16),
(PixelType::U128, true) => get_frame!(u128, <16),
(PixelType::F128, true) => get_frame!(f64, <8),
(PixelType::I8, false) => get_frame!(i8, >1),
(PixelType::U8, false) => get_frame!(u8, >1),
(PixelType::I16, false) => get_frame!(i16, >2),
(PixelType::U16, false) => get_frame!(u16, >2),
(PixelType::I32, false) => get_frame!(i32, >4),
(PixelType::U32, false) => get_frame!(u32, >4),
(PixelType::F32, false) => get_frame!(f32, >4),
(PixelType::F64, false) => get_frame!(f64, >8),
(PixelType::I64, false) => get_frame!(i64, >8),
(PixelType::U64, false) => get_frame!(u64, >8),
(PixelType::I128, false) => get_frame!(i128, >16),
(PixelType::U128, false) => get_frame!(u128, >16),
(PixelType::F128, false) => get_frame!(f64, >8),
}
}
/// get a slicable view on the image file
pub fn view(&self) -> View<Ix5> {
let slice = s![
0..self.size_c,
0..self.size_z,
0..self.size_t,
0..self.size_y,
0..self.size_x
];
View::new(
Arc::new(self.clone()),
slice.as_ref().to_vec(),
vec![Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X],
)
}
}
impl Drop for Reader {
fn drop(&mut self) {
let _ = self.close();
}
}

201
src/stats.rs Normal file
View File

@@ -0,0 +1,201 @@
use anyhow::{anyhow, Result};
use ndarray::{Array, ArrayD, ArrayView, Axis, Dimension, RemoveAxis};
/// a trait to define the min, max, sum and mean operations along an axis
pub trait MinMax {
type Output;
fn max(self, axis: usize) -> Result<Self::Output>;
fn min(self, axis: usize) -> Result<Self::Output>;
fn sum(self, axis: usize) -> Result<Self::Output>;
fn mean(self, axis: usize) -> Result<Self::Output>;
}
macro_rules! impl_frame_stats_float_view {
($($t:tt),+ $(,)?) => {
$(
impl<D> MinMax for ArrayView<'_, $t, D>
where
D: Dimension + RemoveAxis,
{
type Output = Array<$t, D::Smaller>;
fn max(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| {
x.iter()
.fold($t::NEG_INFINITY, |prev, curr| prev.max(*curr))
})
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn min(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| {
x.iter()
.fold($t::INFINITY, |prev, curr| prev.min(*curr))
})
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn sum(self, axis: usize) -> Result<Self::Output> {
Ok(self.sum_axis(Axis(axis)))
}
fn mean(self, axis: usize) -> Result<Self::Output> {
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
}
}
)*
};
}
macro_rules! impl_frame_stats_int_view {
($($t:tt),+ $(,)?) => {
$(
impl<D> MinMax for ArrayView<'_, $t, D>
where
D: Dimension + RemoveAxis,
{
type Output = Array<$t, D::Smaller>;
fn max(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| *x.iter().max().unwrap())
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn min(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| *x.iter().min().unwrap())
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn sum(self, axis: usize) -> Result<Self::Output> {
Ok(self.sum_axis(Axis(axis)))
}
fn mean(self, axis: usize) -> Result<Self::Output> {
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
}
}
)*
};
}
macro_rules! impl_frame_stats_float {
($($t:tt),+ $(,)?) => {
$(
impl<D> MinMax for Array<$t, D>
where
D: Dimension + RemoveAxis,
{
type Output = Array<$t, D::Smaller>;
fn max(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| {
x.iter()
.fold($t::NEG_INFINITY, |prev, curr| prev.max(*curr))
})
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn min(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| {
x.iter()
.fold($t::INFINITY, |prev, curr| prev.min(*curr))
})
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn sum(self, axis: usize) -> Result<Self::Output> {
Ok(self.sum_axis(Axis(axis)))
}
fn mean(self, axis: usize) -> Result<Self::Output> {
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
}
}
)*
};
}
macro_rules! impl_frame_stats_int {
($($t:tt),+ $(,)?) => {
$(
impl<D> MinMax for Array<$t, D>
where
D: Dimension + RemoveAxis,
{
type Output = Array<$t, D::Smaller>;
fn max(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| *x.iter().max().unwrap())
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn min(self, axis: usize) -> Result<Self::Output> {
let a: Vec<_> = self
.lanes(Axis(axis))
.into_iter()
.map(|x| *x.iter().min().unwrap())
.collect();
let mut shape = self.shape().to_vec();
shape.remove(axis);
Ok(ArrayD::from_shape_vec(shape, a)?.into_dimensionality()?)
}
fn sum(self, axis: usize) -> Result<Self::Output> {
Ok(self.sum_axis(Axis(axis)))
}
fn mean(self, axis: usize) -> Result<Self::Output> {
self.mean_axis(Axis(axis)).ok_or_else(|| anyhow!("no mean"))
}
}
)*
};
}
impl_frame_stats_float_view!(f32, f64);
impl_frame_stats_int_view!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);
impl_frame_stats_float!(f32, f64);
impl_frame_stats_int!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);

821
src/view.rs Normal file
View File

@@ -0,0 +1,821 @@
use crate::axes::{slice_info, Ax, Axis, Operation, Slice, SliceInfoElemDef};
use crate::reader::Reader;
use crate::stats::MinMax;
use anyhow::{anyhow, Error, Result};
use indexmap::IndexMap;
use itertools::iproduct;
use ndarray::{
s, Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
SliceArg, SliceInfoElem,
};
use num::{Bounded, FromPrimitive, Zero};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::any::type_name;
use std::collections::HashMap;
use std::iter::Sum;
use std::marker::PhantomData;
use std::mem::{transmute, transmute_copy};
use std::ops::{AddAssign, Deref, Div};
use std::path::PathBuf;
use std::sync::Arc;
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize> {
if idx < -bnd {
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
} else if idx < 0 {
Ok(bnd - idx)
} else if idx < bnd {
Ok(idx)
} else {
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
}
}
fn slc_bnd(idx: isize, bnd: isize) -> Result<isize> {
if idx < -bnd {
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
} else if idx < 0 {
Ok(bnd - idx)
} else if idx <= bnd {
Ok(idx)
} else {
Err(anyhow!("Index {} out of bounds {}", idx, bnd))
}
}
pub trait Number:
'static + AddAssign + Bounded + Clone + Div<Self, Output = Self> + FromPrimitive + PartialOrd + Zero
{
}
impl<T> Number for T where
T: 'static
+ AddAssign
+ Bounded
+ Clone
+ Div<Self, Output = Self>
+ FromPrimitive
+ PartialOrd
+ Zero
{
}
/// sliceable view on an image file
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct View<D: Dimension> {
reader: Arc<Reader>,
#[serde_as(as = "Vec<SliceInfoElemDef>")]
slice: Vec<SliceInfoElem>,
axes: Vec<Axis>,
operations: IndexMap<Axis, Operation>,
dimensionality: PhantomData<D>,
}
impl<D: Dimension> View<D> {
pub(crate) fn new(reader: Arc<Reader>, slice: Vec<SliceInfoElem>, axes: Vec<Axis>) -> Self {
Self {
reader,
slice,
axes,
operations: IndexMap::new(),
dimensionality: PhantomData,
}
}
/// the file path
pub fn path(&self) -> &PathBuf {
&self.reader.path
}
/// the series in the file
pub fn series(&self) -> i32 {
self.reader.series
}
fn with_operations(mut self, operations: IndexMap<Axis, Operation>) -> Self {
self.operations = operations;
self
}
/// change the dimension into a dynamic dimension
pub fn into_dyn(self) -> View<IxDyn> {
View {
reader: self.reader,
slice: self.slice,
axes: self.axes,
operations: self.operations,
dimensionality: PhantomData,
}
}
/// change the dimension into a concrete dimension
pub fn into_dimensionality<D2: Dimension>(self) -> Result<View<D2>> {
if let Some(d) = D2::NDIM {
if d == self.ndim() {
Ok(View {
reader: self.reader,
slice: self.slice,
axes: self.axes,
operations: self.operations,
dimensionality: PhantomData,
})
} else {
Err(anyhow!("Dimensionality mismatch: {} != {}", d, self.ndim()))
}
} else {
Ok(View {
reader: self.reader,
slice: self.slice,
axes: self.axes,
operations: self.operations,
dimensionality: PhantomData,
})
}
}
/// the order of the axes, including axes sliced out
pub fn get_axes(&self) -> &[Axis] {
&self.axes
}
/// the slice defining the view
pub fn get_slice(&self) -> &[SliceInfoElem] {
&self.slice
}
/// the axes in the view
pub fn axes(&self) -> Vec<Axis> {
self.axes
.iter()
.zip(self.slice.iter())
.filter_map(|(ax, s)| {
if s.is_index() || self.operations.contains_key(ax) {
None
} else {
Some(*ax)
}
})
.collect()
}
pub(crate) fn op_axes(&self) -> Vec<Axis> {
self.operations.keys().cloned().collect()
}
/// the number of dimensions in the view
pub fn ndim(&self) -> usize {
if let Some(d) = D::NDIM {
d
} else {
self.shape().len()
}
}
/// the number of pixels in the view
pub fn size(&self) -> usize {
self.shape().into_iter().product()
}
/// the shape of the view
pub fn shape(&self) -> Vec<usize> {
let mut shape = Vec::<usize>::new();
for (ax, s) in self.axes.iter().zip(self.slice.iter()) {
match s {
SliceInfoElem::Slice { start, end, step } => {
if !self.operations.contains_key(ax) {
if let Some(e) = end {
shape.push(((e - start).max(0) / step) as usize);
} else {
panic!("slice has no end")
}
}
}
SliceInfoElem::Index(_) => {}
SliceInfoElem::NewAxis => {
if !self.operations.contains_key(ax) {
shape.push(1);
}
}
}
}
shape
}
fn shape_all(&self) -> Vec<usize> {
let mut shape = Vec::<usize>::new();
for s in self.slice.iter() {
match s {
SliceInfoElem::Slice { start, end, step } => {
if let Some(e) = end {
shape.push(((e - start).max(0) / step) as usize);
} else {
panic!("slice has no end")
}
}
_ => shape.push(1),
}
}
shape
}
/// swap two axes
pub fn swap_axes<A: Ax>(&self, axis0: A, axis1: A) -> Result<Self> {
let idx0 = axis0.pos_op(&self.axes, &self.slice, &self.op_axes())?;
let idx1 = axis1.pos_op(&self.axes, &self.slice, &self.op_axes())?;
let mut slice = self.slice.to_vec();
slice.swap(idx0, idx1);
let mut axes = self.axes.clone();
axes.swap(idx0, idx1);
Ok(View::new(self.reader.clone(), slice, axes))
}
/// subset of gives axes will be reordered in given order
pub fn permute_axes<A: Ax>(&self, axes: &[A]) -> Result<Self> {
let idx: Vec<usize> = axes
.iter()
.map(|a| a.pos_op(&self.axes, &self.slice, &self.op_axes()).unwrap())
.collect();
let mut jdx = idx.clone();
jdx.sort();
let mut slice = self.slice.to_vec();
let mut axes = self.axes.clone();
for (&i, j) in idx.iter().zip(jdx) {
slice[j] = self.slice[i];
axes[j] = self.axes[i];
}
Ok(View::new(self.reader.clone(), slice, axes))
}
/// reverse the order of the axes
pub fn transpose(&self) -> Result<Self> {
Ok(View::new(
self.reader.clone(),
self.slice.iter().rev().cloned().collect(),
self.axes.iter().rev().cloned().collect(),
))
}
fn operate<A: Ax>(&self, axis: A, operation: Operation) -> Result<View<D::Smaller>> {
let pos = axis.pos_op(&self.axes, &self.slice, &self.op_axes())?;
let mut operations = self.operations.clone();
operations.insert(self.axes[pos], operation);
Ok(
View::new(self.reader.clone(), self.slice.clone(), self.axes.clone())
.with_operations(operations),
)
}
/// maximum along axis
pub fn max_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
self.operate(axis, Operation::Max)
}
/// minimum along axis
pub fn min_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
self.operate(axis, Operation::Min)
}
/// sum along axis
pub fn sum_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
self.operate(axis, Operation::Sum)
}
/// mean along axis
pub fn mean_proj<A: Ax>(&self, axis: A) -> Result<View<D::Smaller>> {
self.operate(axis, Operation::Mean)
}
/// created a new sliced view
pub fn slice<I>(&self, info: I) -> Result<View<I::OutDim>>
where
I: SliceArg<D>,
{
if self.slice.out_ndim() < info.in_ndim() {
return Err(Error::msg("not enough free dimensions"));
}
let info = info.as_ref();
let mut n_idx = 0;
let mut r_idx = 0;
let mut new_slice = Vec::new();
let mut new_axes = Vec::new();
let reader_slice = self.slice.as_slice();
while (r_idx < reader_slice.len()) & (n_idx < info.len()) {
let n = &info[n_idx];
let r = &reader_slice[r_idx];
let a = &self.axes[r_idx];
if self.operations.contains_key(a) {
new_slice.push(*r);
new_axes.push(*a);
r_idx += 1;
} else {
match (n, r) {
(
SliceInfoElem::Slice {
start: info_start,
end: info_end,
step: info_step,
},
SliceInfoElem::Slice { start, end, step },
) => {
let new_start = start + info_start;
let new_end = match (info_end, end) {
(Some(m), Some(n)) => *n.min(&(start + info_step * m)),
(None, Some(n)) => *n,
_ => panic!("slice has no end"),
};
let new_step = (step * info_step).abs();
new_slice.push(SliceInfoElem::Slice {
start: new_start,
end: Some(new_end),
step: new_step,
});
new_axes.push(*a);
n_idx += 1;
r_idx += 1;
}
(SliceInfoElem::Index(k), SliceInfoElem::Slice { start, end, step }) => {
if *k < 0 {
new_slice.push(SliceInfoElem::Index(end.unwrap_or(0) + step.abs() * k))
} else {
new_slice.push(SliceInfoElem::Index(start + step.abs() * k));
}
new_axes.push(*a);
n_idx += 1;
r_idx += 1;
}
(SliceInfoElem::NewAxis, SliceInfoElem::NewAxis) => {
new_slice.push(SliceInfoElem::NewAxis);
new_slice.push(SliceInfoElem::NewAxis);
new_axes.push(Axis::New);
new_axes.push(Axis::New);
n_idx += 1;
r_idx += 1;
}
(_, SliceInfoElem::NewAxis) => {
new_slice.push(SliceInfoElem::NewAxis);
new_axes.push(Axis::New);
n_idx += 1;
r_idx += 1;
}
(SliceInfoElem::NewAxis, _) => {
new_slice.push(SliceInfoElem::NewAxis);
new_axes.push(Axis::New);
n_idx += 1;
}
(_, SliceInfoElem::Index(k)) => {
new_slice.push(SliceInfoElem::Index(*k));
new_axes.push(*a);
r_idx += 1;
}
}
}
}
debug_assert_eq!(r_idx, reader_slice.len());
while n_idx < info.len() {
debug_assert!(info[n_idx].is_new_axis());
new_slice.push(SliceInfoElem::NewAxis);
new_axes.push(Axis::New);
n_idx += 1;
}
Ok(View::new(self.reader.clone(), new_slice, new_axes)
.with_operations(self.operations.clone()))
}
/// slice, but slice elements are in cztyx order, all cztyx must be given,
/// but axes not present in view will be ignored, view axes are reordered in cztyx order
pub fn slice_cztyx<I>(&self, info: I) -> Result<View<I::OutDim>>
where
I: SliceArg<Ix5>,
{
let axes = self.axes();
let slice: Vec<_> = info
.as_ref()
.iter()
.zip(self.axes.iter())
.filter_map(|(&s, ax)| if axes.contains(ax) { Some(s) } else { None })
.collect();
let new_axes: Vec<_> = [Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X]
.into_iter()
.filter(|ax| axes.contains(ax))
.collect();
self.clone()
.into_dyn()
.permute_axes(&new_axes)?
.slice(slice.as_slice())?
.into_dimensionality()
}
/// the pixel intensity at a given index
pub fn item_at<T>(&self, index: &[isize]) -> Result<T>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
let slice: Vec<_> = index.iter().map(|s| SliceInfoElem::Index(*s)).collect();
let view = self.clone().into_dyn().slice(slice.as_slice())?;
let arr = view.as_array()?;
Ok(arr.first().unwrap().clone())
}
/// collect the view into an ndarray
pub fn as_array<T>(&self) -> Result<Array<T, D>>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
Ok(self.as_array_dyn()?.into_dimensionality()?)
}
/// collect the view into a dynamic-dimension ndarray
pub fn as_array_dyn<T>(&self) -> Result<ArrayD<T>>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
let mut op_xy = IndexMap::new();
if let Some((&ax, op)) = self.operations.first() {
if (ax == Axis::X) || (ax == Axis::Y) {
op_xy.insert(ax, op.clone());
if let Some((&ax2, op2)) = self.operations.get_index(1) {
if (ax2 == Axis::X) || (ax2 == Axis::Y) {
op_xy.insert(ax2, op2.clone());
}
}
}
}
let op_czt = if let Some((&ax, op)) = self.operations.get_index(op_xy.len()) {
IndexMap::from([(ax, op.clone())])
} else {
IndexMap::new()
};
let mut shape_out = Vec::new();
let mut slice = Vec::new();
let mut ax_out = Vec::new();
for (s, a) in self.slice.iter().zip(&self.axes) {
match s {
SliceInfoElem::Slice { start, end, step } => {
if let Some(e) = end {
if !op_xy.contains_key(a) && !op_czt.contains_key(a) {
shape_out.push(((e - start).max(0) / step) as usize);
slice.push(SliceInfoElem::Slice {
start: 0,
end: None,
step: 1,
});
ax_out.push(*a);
}
} else {
panic!("slice has no end")
}
}
SliceInfoElem::Index(_) => {}
SliceInfoElem::NewAxis => {
shape_out.push(1);
slice.push(SliceInfoElem::Index(0));
ax_out.push(*a);
}
}
}
let mut slice_reader = vec![Slice::empty(); 5];
let mut xy_dim = 0usize;
let shape = [
self.size_c as isize,
self.size_z as isize,
self.size_t as isize,
self.size_y as isize,
self.size_x as isize,
];
for (s, &axis) in self.slice.iter().zip(&self.axes) {
match axis {
Axis::New => {}
_ => match s {
SliceInfoElem::Slice { start, end, step } => {
if let Axis::X | Axis::Y = axis {
if !op_xy.contains_key(&axis) {
xy_dim += 1;
}
}
slice_reader[axis as usize] = Slice::new(
idx_bnd(*start, shape[axis as usize])?,
slc_bnd(end.unwrap(), shape[axis as usize])?,
*step,
);
}
SliceInfoElem::Index(j) => {
slice_reader[axis as usize] = Slice::new(
idx_bnd(*j, shape[axis as usize])?,
slc_bnd(*j + 1, shape[axis as usize])?,
1,
);
}
SliceInfoElem::NewAxis => panic!("axis cannot be a new axis"),
},
}
}
let xy = [
self.slice[Axis::Y.pos(&self.axes, &self.slice)?],
self.slice[Axis::X.pos(&self.axes, &self.slice)?],
];
let mut array = if let Some((_, op)) = op_czt.first() {
match op {
Operation::Max => {
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 {
ArrayD::<T>::zeros(shape_out.into_dimension())
};
let size_c = self.reader.size_c as isize;
let size_z = self.reader.size_z as isize;
let size_t = self.reader.size_t as isize;
let mut axes_out_idx = [None; 5];
for (i, ax) in ax_out.iter().enumerate() {
axes_out_idx[*ax as usize] = Some(i);
}
for (c, z, t) in iproduct!(&slice_reader[0], &slice_reader[1], &slice_reader[2]) {
if let Some(i) = axes_out_idx[0] {
slice[i] = SliceInfoElem::Index(c)
};
if let Some(i) = axes_out_idx[1] {
slice[i] = SliceInfoElem::Index(z)
};
if let Some(i) = axes_out_idx[2] {
slice[i] = SliceInfoElem::Index(t)
};
let frame = self.reader.get_frame(
(c % size_c) as usize,
(z % size_z) as usize,
(t % size_t) as usize,
)?;
let arr_frame: Array2<T> = frame.try_into()?;
let arr_frame = match xy_dim {
0 => {
if op_xy.contains_key(&Axis::X) && op_xy.contains_key(&Axis::Y) {
let xys = slice_info::<Ix2>(&xy)?;
let (&ax0, op0) = op_xy.first().unwrap();
let (&ax1, op1) = op_xy.get_index(1).unwrap();
let a = arr_frame.slice(xys).to_owned();
let b = op0.operate(a, ax0 as usize - 3)?;
let c: &Array1<T> = unsafe { transmute(&b) };
let d = op1.operate(c.to_owned(), ax1 as usize - 3)?;
let e: &Array0<T> = unsafe { transmute(&d) };
e.to_owned().into_dyn()
} else if op_xy.contains_key(&Axis::X) || op_xy.contains_key(&Axis::Y) {
let xys = slice_info::<Ix1>(&xy)?;
let (&ax, op) = op_xy.first().unwrap();
let a = arr_frame.slice(xys).to_owned();
let b = op.operate(a, ax as usize - 3)?;
let c: &Array0<T> = unsafe { transmute(&b) };
c.to_owned().into_dyn()
} else {
let xys = slice_info::<Ix0>(&xy)?;
arr_frame.slice(xys).to_owned().into_dyn()
}
}
1 => {
if op_xy.contains_key(&Axis::X) || op_xy.contains_key(&Axis::Y) {
let xys = slice_info::<Ix2>(&xy)?;
let (&ax, op) = op_xy.first().unwrap();
let a = arr_frame.slice(xys).to_owned();
let b = op.operate(a, ax as usize - 3)?;
let c: &Array1<T> = unsafe { transmute(&b) };
c.to_owned().into_dyn()
} else {
let xys = slice_info::<Ix1>(&xy)?;
arr_frame.slice(xys).to_owned().into_dyn()
}
}
2 => {
let xys = slice_info::<Ix2>(&xy)?;
if axes_out_idx[4] < axes_out_idx[3] {
arr_frame.t().slice(xys).to_owned().into_dyn()
} else {
arr_frame.slice(xys).to_owned().into_dyn()
}
}
_ => {
panic!("xy cannot be 3d or more");
}
};
if let Some((_, op)) = op_czt.first() {
match op {
Operation::Max => {
array
.slice_mut(slice.as_slice())
.zip_mut_with(&arr_frame, |x, y| {
*x = if *x >= *y { x.clone() } else { y.clone() }
});
}
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 {
array.slice_mut(slice.as_slice()).assign(&arr_frame)
}
}
let mut out = Some(array);
let ax_out: HashMap<Axis, usize> = ax_out
.into_iter()
.enumerate()
.map(|(i, a)| (a, i))
.collect();
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
if let Some(&idx) = ax_out.get(ax) {
let arr = out.take().unwrap();
let _ = out.insert(unsafe { transmute_copy(&op.operate(arr, idx)?) });
}
}
let mut n = 1;
for (&ax, size) in self.axes.iter().zip(self.shape_all().iter()) {
if let Some(Operation::Mean) = self.operations.get(&ax) {
if (ax == Axis::C) || (ax == Axis::Z) || (ax == Axis::T) {
n *= size;
}
}
}
let array = if n == 1 {
out.take().unwrap()
} else {
let m = T::from_usize(n).unwrap_or_else(|| T::zero());
out.take().unwrap().mapv(|x| x / m.clone())
};
Ok(array)
}
/// retrieve a single frame at czt, sliced accordingly
pub fn get_frame<T>(&self, c: isize, z: isize, t: isize) -> Result<Array2<T>>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
self.slice_cztyx(s![c, z, t, .., ..])?.as_array()
}
fn get_stat<T>(&self, operation: Operation) -> Result<T>
where
T: Number + Sum,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
let arr: ArrayD<T> = self.as_array_dyn()?;
Ok(match operation {
Operation::Max => arr
.flatten()
.into_iter()
.reduce(|a, b| if a > b { a } else { b })
.unwrap_or_else(|| T::min_value()),
Operation::Min => arr
.flatten()
.into_iter()
.reduce(|a, b| if a < b { a } else { b })
.unwrap_or_else(|| T::max_value()),
Operation::Sum => arr.flatten().into_iter().sum(),
Operation::Mean => {
arr.flatten().into_iter().sum::<T>()
/ T::from_usize(arr.len()).ok_or_else(|| {
anyhow!("cannot convert {} into {}", arr.len(), type_name::<T>())
})?
}
})
}
/// maximum intensity
pub fn max<T>(&self) -> Result<T>
where
T: Number + Sum,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
self.get_stat(Operation::Max)
}
/// minimum intensity
pub fn min<T>(&self) -> Result<T>
where
T: Number + Sum,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
self.get_stat(Operation::Min)
}
/// sum intensity
pub fn sum<T>(&self) -> Result<T>
where
T: Number + Sum,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
self.get_stat(Operation::Sum)
}
/// mean intensity
pub fn mean<T>(&self) -> Result<T>
where
T: Number + Sum,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
self.get_stat(Operation::Mean)
}
}
impl<D: Dimension> Deref for View<D> {
type Target = Reader;
fn deref(&self) -> &Self::Target {
self.reader.as_ref()
}
}
impl<T, D> TryFrom<View<D>> for Array<T, D>
where
T: Number,
D: Dimension,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
type Error = Error;
fn try_from(view: View<D>) -> Result<Self, Self::Error> {
view.as_array()
}
}
impl<T, D> TryFrom<&View<D>> for Array<T, D>
where
T: Number,
D: Dimension,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
type Error = Error;
fn try_from(view: &View<D>) -> Result<Self, Self::Error> {
view.as_array()
}
}
/// trait to define a function to retrieve the only item in a 0d array
pub trait Item {
fn item<T>(&self) -> Result<T>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax;
}
impl Item for View<Ix0> {
fn item<T>(&self) -> Result<T>
where
T: Number,
ArrayD<T>: MinMax,
Array1<T>: MinMax,
Array2<T>: MinMax,
{
Ok(self
.as_array()?
.first()
.ok_or_else(|| anyhow!("Empty view"))?
.clone())
}
}