use crate::axes::{Axis, Shape}; use crate::error::Error; use crate::view::View; use ndarray::{Array, Dimension, Ix2, Ix5, s}; use num::{FromPrimitive, Zero}; use ome_metadata::Ome; use regex::Regex; use serde::{Deserialize, Serialize}; use std::any::type_name; use std::collections::HashSet; use std::fmt::Debug; use std::hash::Hash; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::LazyLock; #[cfg(feature = "czi")] pub mod czi; #[cfg(feature = "bioformats_rust")] pub mod bioformats_rust; #[cfg(feature = "bioformats_java")] pub mod bioformats_java; #[cfg(feature = "tiffseq")] pub mod tiffseq; #[cfg(feature = "tiff")] pub mod tiff; static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([CZTSP])\D+(\d+)$").unwrap()); #[derive(Debug, Clone, Copy, Default)] pub struct Dimensions { pub c: Option, pub z: Option, pub t: Option, pub s: Option, pub p: Option, } impl Dimensions { pub fn new(series: usize, position: usize) -> Self { Self { c: None, z: None, t: None, s: Some(series), p: Some(position), } } pub fn parse_path

(path: P) -> Result<(PathBuf, Self), Error> where P: AsRef, { let mut path = path.as_ref(); let mut new = Self::default(); while !path.exists() { let last = path .file_name() .ok_or(Error::InvalidFileName)? .to_string_lossy() .to_string(); path = path.parent().ok_or(Error::NoParent)?; let last_upper = last.to_uppercase(); let caps = RE.captures(&last_upper).ok_or(Error::FileDoesNotExist( path.join(&last).display().to_string(), ))?; let p = caps[2].parse()?; match &caps[1] { "C" => new.c = Some(p), "Z" => new.z = Some(p), "T" => new.t = Some(p), "S" => new.s = Some(p), "P" => new.p = Some(p), _ => { return Err(Error::FileDoesNotExist( path.join(last).display().to_string(), )); } } } Ok((path.to_path_buf(), new)) } } /// 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, Copy, Debug, Serialize, Deserialize, PartialEq)] pub enum PixelType { I8, U8, I16, U16, I32, U32, F32, F64, I64, U64, I128, U128, F128, } impl PixelType { pub fn bytes_per_pixel(&self) -> usize { match self { PixelType::I8 | PixelType::U8 => 1, PixelType::I16 | PixelType::U16 => 2, PixelType::I32 | PixelType::U32 | PixelType::F32 => 4, PixelType::I64 | PixelType::U64 | PixelType::F64 => 8, PixelType::I128 | PixelType::U128 | PixelType::F128 => 16, } } } /// Struct containing frame data in one of eight pixel types. Cast to `Array2` using try_into. #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Debug)] pub enum ArrayT { I8(Array), U8(Array), I16(Array), U16(Array), I32(Array), U32(Array), F32(Array), F64(Array), I64(Array), U64(Array), I128(Array), U128(Array), F128(Array), // f128 is nightly } pub type Frame = ArrayT; pub trait Reader: Clone + Sized + Debug + Send + Hash + Into { fn new

(path: P, series: usize, position: usize) -> Result where P: AsRef; fn reader_name(&self) -> &'static str { type_name::() } // TODO: read from file if present fn metadata(&self) -> Result; /// get a sliceable view on the image file fn view(&self) -> View { let shape = self.shape(); let slice = s![0..shape.c, 0..shape.z, 0..shape.t, 0..shape.y, 0..shape.x,]; View::new( self.clone(), slice.as_ref().to_vec(), vec![Axis::C, Axis::Z, Axis::T, Axis::Y, Axis::X], ) } /// Retrieve fame at channel c, slize z and time t. #[allow(clippy::if_same_then_else)] fn get_frame(&self, c: usize, z: usize, t: usize) -> Result; fn path(&self) -> &Path; fn series(&self) -> usize; fn position(&self) -> usize; fn shape(&self) -> &Shape; fn pixel_type(&self) -> &PixelType; fn get_available_positions

(path: P, series: usize) -> Result, Error> where P: AsRef; fn get_available_series

(path: P) -> Result, Error> where P: AsRef; } impl TryFrom for PixelType { type Error = Error; fn try_from(value: i32) -> Result { 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(Error::UnknownPixelType(value.to_string())), } } } impl FromStr for PixelType { type Err = Error; fn from_str(s: &str) -> Result { 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(Error::UnknownPixelType(s.to_string())), } } } macro_rules! impl_frame_cast { ($($t:tt: $s:ident $(,)?)*) => { $( impl From> for ArrayT { fn from(value: Array<$t, D>) -> Self { ArrayT::$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 } macro_rules! try_into_array_body { ($self:expr, $map:ident) => {{ let mut err = Ok(()); let arr = match $self { ArrayT::I8(v) => v.$map(|x| { T::from_i8(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::U8(v) => v.$map(|x| { T::from_u8(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::I16(v) => v.$map(|x| { T::from_i16(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::U16(v) => v.$map(|x| { T::from_u16(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::I32(v) => v.$map(|x| { T::from_i32(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::U32(v) => v.$map(|x| { T::from_u32(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::F32(v) => v.$map(|x| { T::from_f32(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::F64(v) | ArrayT::F128(v) => v.$map(|x| { T::from_f64(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::I64(v) => v.$map(|x| { T::from_i64(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::U64(v) => v.$map(|x| { T::from_u64(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::I128(v) => v.$map(|x| { T::from_i128(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), ArrayT::U128(v) => v.$map(|x| { T::from_u128(x).unwrap_or_else(|| { err = Err(Error::Cast(x.to_string(), type_name::().to_string())); T::zero() }) }), }; match err { Err(err) => Err(err), Ok(()) => Ok(arr), } }}; } impl TryInto> for ArrayT where D: Dimension, T: FromPrimitive + Zero + 'static, { type Error = Error; fn try_into(self) -> Result, Self::Error> { try_into_array_body!(self, mapv_into_any) } } impl TryInto> for &ArrayT where D: Dimension, T: Clone + FromPrimitive + Zero + 'static, { type Error = Error; fn try_into(self) -> Result, Self::Error> { try_into_array_body!(self, mapv) } } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)] pub enum DynReader { #[cfg(feature = "tiff")] Tiff(tiff::TiffReader), #[cfg(feature = "tiffseq")] TiffSeq(tiffseq::TiffSeqReader), #[cfg(feature = "czi")] Czi(czi::CziReader), #[cfg(feature = "bioformats_rust")] BioFormatsRust(bioformats_rust::BioFormatsRustReader), #[cfg(feature = "bioformats_java")] BioFormatsJava(bioformats_java::BioFormatsJavaReader), } impl Reader for DynReader { fn new

(path: P, series: usize, position: usize) -> Result where P: AsRef, { let mut errors = Vec::::new(); #[cfg(feature = "tiff")] match tiff::TiffReader::new(&path, series, position) { Ok(reader) => return Ok(DynReader::Tiff(reader)), Err(err) => errors.push(format!("TiffReader: {}", err)), } #[cfg(feature = "tiffseq")] match tiffseq::TiffSeqReader::new(&path, series, position) { Ok(reader) => return Ok(DynReader::TiffSeq(reader)), Err(err) => errors.push(format!("TiffseqReader: {}", err)), } #[cfg(feature = "czi")] match czi::CziReader::new(&path, series, position) { Ok(reader) => return Ok(DynReader::Czi(reader)), Err(err) => errors.push(format!("CziReader: {}", err)), } #[cfg(feature = "bioformats_rust")] match bioformats_rust::BioFormatsRustReader::new(&path, series, position) { Ok(reader) => return Ok(DynReader::BioFormatsRust(reader)), Err(err) => errors.push(format!("BioformatsRustReader: {}", err)), } #[cfg(feature = "bioformats_java")] match bioformats_java::BioFormatsJavaReader::new(&path, series, position) { Ok(reader) => return Ok(DynReader::BioFormatsJava(reader)), Err(err) => errors.push(format!("BioformatsReader: {}", err)), } Err(Error::NoReader( path.as_ref().display().to_string(), errors.join("\n"), )) } fn reader_name(&self) -> &'static str { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.reader_name(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.reader_name(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.reader_name(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.reader_name(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.reader_name(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn metadata(&self) -> Result { Ok(match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.metadata()?, #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.metadata()?, #[cfg(feature = "czi")] DynReader::Czi(r) => r.metadata()?, #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.metadata()?, #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.metadata()?, #[allow(unreachable_patterns)] _ => unreachable!(), }) } fn get_frame(&self, c: usize, z: usize, t: usize) -> Result { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.get_frame(c, z, t), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.get_frame(c, z, t), #[cfg(feature = "czi")] DynReader::Czi(r) => r.get_frame(c, z, t), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.get_frame(c, z, t), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.get_frame(c, z, t), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn path(&self) -> &Path { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.path(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.path(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.path(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.path(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.path(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn series(&self) -> usize { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.series(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.series(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.series(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.series(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.series(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn position(&self) -> usize { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.position(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.position(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.position(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.position(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.position(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn shape(&self) -> &Shape { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.shape(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.shape(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.shape(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.shape(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.shape(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn pixel_type(&self) -> &PixelType { match self { #[cfg(feature = "tiff")] DynReader::Tiff(r) => r.pixel_type(), #[cfg(feature = "tiffseq")] DynReader::TiffSeq(r) => r.pixel_type(), #[cfg(feature = "czi")] DynReader::Czi(r) => r.pixel_type(), #[cfg(feature = "bioformats_rust")] DynReader::BioFormatsRust(r) => r.pixel_type(), #[cfg(feature = "bioformats_java")] DynReader::BioFormatsJava(r) => r.pixel_type(), #[allow(unreachable_patterns)] _ => unreachable!(), } } fn get_available_positions

(path: P, series: usize) -> Result, Error> where P: AsRef, { let mut errors = Vec::::new(); #[cfg(feature = "tiff")] match tiff::TiffReader::get_available_positions(&path, series) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("TiffReader: {}", e)), } #[cfg(feature = "tiffseq")] match tiffseq::TiffSeqReader::get_available_positions(&path, series) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("TiffSeqReader: {}", e)), } #[cfg(feature = "czi")] match czi::CziReader::get_available_positions(&path, series) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("CziReader: {}", e)), } #[cfg(feature = "bioformats_rust")] match bioformats_rust::BioFormatsRustReader::get_available_positions(&path, series) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("BioFormatsRustReader: {}", e)), } #[cfg(feature = "bioformats_java")] match bioformats_java::BioFormatsJavaReader::get_available_positions(&path, series) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("BioFormatsReader: {}", e)), } Err(Error::NoReader( path.as_ref().display().to_string(), errors.join("\n"), )) } fn get_available_series

(path: P) -> Result, Error> where P: AsRef, { let mut errors = Vec::::new(); #[cfg(feature = "tiff")] match tiff::TiffReader::get_available_series(&path) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("TiffReader: {}", e)), } #[cfg(feature = "tiffseq")] match tiffseq::TiffSeqReader::get_available_series(&path) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("TiffSeqReader: {}", e)), } #[cfg(feature = "czi")] match czi::CziReader::get_available_series(&path) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("CziReader: {}", e)), } #[cfg(feature = "bioformats_rust")] match bioformats_rust::BioFormatsRustReader::get_available_series(&path) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("BioFormatsRustReader: {}", e)), } #[cfg(feature = "bioformats_java")] match bioformats_java::BioFormatsJavaReader::get_available_series(&path) { Ok(positions) => return Ok(positions), Err(e) => errors.push(format!("BioFormatsReader: {}", e)), } Err(Error::NoReader( path.as_ref().display().to_string(), errors.join("\n"), )) } } impl DynReader { pub fn from_path_select_reader(path: P, reader: R) -> Result where P: AsRef, R: AsRef, { let path = path.as_ref(); let (path, dimensions) = Dimensions::parse_path(path)?; let reader = reader.as_ref(); let reader = match reader.to_lowercase().as_str() { #[cfg(feature = "tiff")] "tif" => Ok(DynReader::Tiff(tiff::TiffReader::new( path, dimensions.s.unwrap_or(0), dimensions.p.unwrap_or(0), )?)), #[cfg(feature = "tiffseq")] "tiffseq" => Ok(DynReader::TiffSeq(tiffseq::TiffSeqReader::new( path, dimensions.s.unwrap_or(0), dimensions.p.unwrap_or(0), )?)), #[cfg(feature = "czi")] "czi" => Ok(DynReader::Czi(czi::CziReader::new( path, dimensions.s.unwrap_or(0), dimensions.p.unwrap_or(0), )?)), #[cfg(feature = "bioformats_rust")] "bioformats_rust" => Ok(DynReader::BioFormatsRust( bioformats_rust::BioFormatsRustReader::new( path, dimensions.s.unwrap_or(0), dimensions.p.unwrap_or(0), )?, )), #[cfg(feature = "bioformats_java")] "bioformats_java" => Ok(DynReader::BioFormatsJava( bioformats_java::BioFormatsJavaReader::new( path, dimensions.s.unwrap_or(0), dimensions.p.unwrap_or(0), )?, )), _ => Err(Error::Parse(reader.to_string())), }?; Ok(reader) } pub fn get_available_positions_select_reader( path: P, series: usize, reader: Option, ) -> Result, Error> where P: AsRef, R: AsRef, { Ok(if let Some(reader) = reader { let reader = reader.as_ref(); match reader.to_lowercase().as_str() { #[cfg(feature = "tiff")] "tif" => Ok(tiff::TiffReader::get_available_positions(path, series)?), #[cfg(feature = "tiffseq")] "tiffseq" => Ok(tiffseq::TiffSeqReader::get_available_positions( path, series, )?), #[cfg(feature = "czi")] "czi" => Ok(czi::CziReader::get_available_positions(path, series)?), #[cfg(feature = "bioformats_rust")] "bioformats_rust" => Ok( bioformats_rust::BioFormatsRustReader::get_available_positions(path, series)?, ), #[cfg(feature = "bioformats_java")] "bioformats_java" => Ok( bioformats_java::BioFormatsJavaReader::get_available_positions(path, series)?, ), _ => Err(Error::Parse(reader.to_string())), }? } else { DynReader::get_available_positions(&path, series)? }) } pub fn get_available_series_select_reader( path: P, reader: Option, ) -> Result, Error> where P: AsRef, R: AsRef, { Ok(if let Some(reader) = reader { let reader = reader.as_ref(); match reader.to_lowercase().as_str() { #[cfg(feature = "tiff")] "tif" => Ok(tiff::TiffReader::get_available_series(path)?), #[cfg(feature = "tiffseq")] "tiffseq" => Ok(tiffseq::TiffSeqReader::get_available_series(path)?), #[cfg(feature = "czi")] "czi" => Ok(czi::CziReader::get_available_series(path)?), #[cfg(feature = "bioformats_rust")] "bioformats_rust" => Ok( bioformats_rust::BioFormatsRustReader::get_available_series(path)?, ), #[cfg(feature = "bioformats_java")] "bioformats_java" => Ok( bioformats_java::BioFormatsJavaReader::get_available_series(path)?, ), _ => Err(Error::Parse(reader.to_string())), }? } else { DynReader::get_available_series(&path)? }) } }