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<Regex> = LazyLock::new(|| Regex::new(r"^([CZTSP])\D+(\d+)$").unwrap());
#[derive(Debug, Clone, Copy, Default)]
pub struct Dimensions {
pub c: Option<usize>,
pub z: Option<usize>,
pub t: Option<usize>,
pub s: Option<usize>,
pub p: Option<usize>,
}
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<P>(path: P) -> Result<(PathBuf, Self), Error>
where
P: AsRef<Path>,
{
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))
}
}
#[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,
}
}
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub enum ArrayT<D: Dimension> {
I8(Array<i8, D>),
U8(Array<u8, D>),
I16(Array<i16, D>),
U16(Array<u16, D>),
I32(Array<i32, D>),
U32(Array<u32, D>),
F32(Array<f32, D>),
F64(Array<f64, D>),
I64(Array<i64, D>),
U64(Array<u64, D>),
I128(Array<i128, D>),
U128(Array<u128, D>),
F128(Array<f64, D>),
}
pub type Frame = ArrayT<Ix2>;
pub trait Reader: Clone + Sized + Debug + Send + Hash + Into<DynReader> {
fn new<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
where
P: AsRef<Path>;
fn reader_name(&self) -> &'static str {
type_name::<Self>()
}
fn metadata(&self) -> Result<Ome, Error>;
fn view(&self) -> View<Ix5, Self> {
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],
)
}
#[allow(clippy::if_same_then_else)]
fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error>;
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<P>(path: P, series: usize) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>;
fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>;
}
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(Error::UnknownPixelType(value.to_string())),
}
}
}
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(Error::UnknownPixelType(s.to_string())),
}
}
}
macro_rules! impl_frame_cast {
($($t:tt: $s:ident $(,)?)*) => {
$(
impl<D: Dimension> From<Array<$t, D>> for ArrayT<D> {
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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().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::<T>().to_string()));
T::zero()
})
}),
};
match err {
Err(err) => Err(err),
Ok(()) => Ok(arr),
}
}};
}
impl<D, T> TryInto<Array<T, D>> for ArrayT<D>
where
D: Dimension,
T: FromPrimitive + Zero + 'static,
{
type Error = Error;
fn try_into(self) -> Result<Array<T, D>, Self::Error> {
try_into_array_body!(self, mapv_into_any)
}
}
impl<D, T> TryInto<Array<T, D>> for &ArrayT<D>
where
D: Dimension,
T: Clone + FromPrimitive + Zero + 'static,
{
type Error = Error;
fn try_into(self) -> Result<Array<T, D>, 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<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let mut errors = Vec::<String>::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<Ome, Error> {
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<Frame, Error> {
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<P>(path: P, series: usize) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>,
{
let mut errors = Vec::<String>::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<P>(path: P) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>,
{
let mut errors = Vec::<String>::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<P, R>(path: P, reader: R) -> Result<DynReader, Error>
where
P: AsRef<Path>,
R: AsRef<str>,
{
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<P, R>(
path: P,
series: usize,
reader: Option<R>,
) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>,
R: AsRef<str>,
{
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<P, R>(
path: P,
reader: Option<R>,
) -> Result<HashSet<usize>, Error>
where
P: AsRef<Path>,
R: AsRef<str>,
{
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)?
})
}
}