ndbioimage 0.2.0

Read bio image formats using the bio-formats java package.
use crate::error::Error;
use crate::readers::ArrayT;
use crate::readers::{DynReader, Frame, PixelType, Reader, Shape};
use bioformats::{DimensionOrder, ImageReader};
use ndarray::Array2;
use ome_metadata::Ome;
use std::cell::{RefCell, RefMut};
use std::collections::HashSet;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use thread_local::ThreadLocal;

#[derive(serde::Serialize, serde::Deserialize)]
pub struct BioFormatsRustReader {
    #[serde(skip)]
    reader: ThreadLocal<RefCell<ImageReader>>,
    path: PathBuf,
    series: usize,
    shape: Shape,
    pixel_type: PixelType,
    little_endian: bool,
}

impl fmt::Debug for BioFormatsRustReader {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BioFormatsRustReader")
            .field("path", &self.path)
            .field("series", &self.series)
            .field("shape", &self.shape)
            .field("pixel_type", &self.pixel_type)
            .field("little_endian", &self.little_endian)
            .finish()
    }
}

impl From<BioFormatsRustReader> for DynReader {
    fn from(value: BioFormatsRustReader) -> Self {
        DynReader::BioFormatsRust(value)
    }
}

impl Hash for BioFormatsRustReader {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
        self.series.hash(state);
    }
}

impl PartialEq for BioFormatsRustReader {
    fn eq(&self, other: &Self) -> bool {
        self.path == other.path && self.series == other.series
    }
}

impl Eq for BioFormatsRustReader {}

impl Clone for BioFormatsRustReader {
    fn clone(&self) -> Self {
        BioFormatsRustReader {
            reader: ThreadLocal::default(),
            path: self.path.clone(),
            series: self.series,
            shape: self.shape.clone(),
            pixel_type: self.pixel_type,
            little_endian: self.little_endian,
        }
    }
}

impl Deref for BioFormatsRustReader {
    type Target = ThreadLocal<RefCell<ImageReader>>;

    fn deref(&self) -> &Self::Target {
        &self.reader
    }
}

fn map_pixel_type(bf: bioformats::PixelType) -> Result<PixelType, Error> {
    use bioformats::PixelType as Bf;
    Ok(match bf {
        Bf::Int8 => PixelType::I8,
        Bf::Uint8 => PixelType::U8,
        Bf::Int16 => PixelType::I16,
        Bf::Uint16 => PixelType::U16,
        Bf::Int32 => PixelType::I32,
        Bf::Uint32 => PixelType::U32,
        Bf::Float32 => PixelType::F32,
        Bf::Float64 => PixelType::F64,
        Bf::Bit => PixelType::U8,
    })
}

impl BioFormatsRustReader {
    fn get_reader(&self) -> Result<RefMut<'_, ImageReader>, Error> {
        self.reader
            .get_or_try(|| {
                let mut reader = ImageReader::open(&self.path)?;
                reader.set_series(self.series)?;
                Ok(RefCell::new(reader))
            })
            .map(|i| i.borrow_mut())
    }

    fn get_index(order: DimensionOrder, shape: &Shape, c: usize, z: usize, t: usize) -> u32 {
        let (s0, s1) = match order {
            DimensionOrder::XYZCT => (shape.z, shape.c),
            DimensionOrder::XYZTC => (shape.z, shape.t),
            DimensionOrder::XYCZT => (shape.c, shape.z),
            DimensionOrder::XYCTZ => (shape.c, shape.t),
            DimensionOrder::XYTZC => (shape.t, shape.z),
            DimensionOrder::XYTCZ => (shape.t, shape.c),
        };
        let (v0, v1, v2) = match order {
            DimensionOrder::XYZCT => (z, c, t),
            DimensionOrder::XYZTC => (z, t, c),
            DimensionOrder::XYCZT => (c, z, t),
            DimensionOrder::XYCTZ => (c, t, z),
            DimensionOrder::XYTZC => (t, z, c),
            DimensionOrder::XYTCZ => (t, c, z),
        };
        (v0 + v1 * s0 + v2 * s0 * s1) as u32
    }

    fn deinterleave(&self, bytes: Vec<u8>, channel: usize) -> Result<Vec<u8>, Error> {
        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.shape.c)
            .flat_map(|a| a.to_vec())
            .collect())
    }

    fn bytes_to_frame(&self, bytes: Vec<u8>) -> Result<Frame, Error> {
        macro_rules! get_frame {
            ($t:tt, <$n:expr) => {
                Ok(ArrayT::from(Array2::from_shape_vec(
                    (self.shape.y, self.shape.x),
                    bytes
                        .chunks($n)
                        .map(|x| $t::from_le_bytes(x.try_into().unwrap()))
                        .collect(),
                )?))
            };
            ($t:tt, >$n:expr) => {
                Ok(ArrayT::from(Array2::from_shape_vec(
                    (self.shape.y, self.shape.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),
        }
    }
}

impl Reader for BioFormatsRustReader {
    fn new<P>(path: P, series: usize, _position: usize) -> Result<Self, Error>
    where
        P: AsRef<Path>,
    {
        let mut new = Self {
            reader: ThreadLocal::default(),
            path: path.as_ref().to_path_buf(),
            series,
            shape: Shape::default(),
            pixel_type: PixelType::U16,
            little_endian: true,
        };
        let reader = new.get_reader()?;
        let metadata = reader.metadata().clone();
        drop(reader);
        new.shape.c = metadata.size_c as usize;
        new.shape.z = metadata.size_z as usize;
        new.shape.t = metadata.size_t as usize;
        new.shape.y = metadata.size_y as usize;
        new.shape.x = metadata.size_x as usize;
        new.little_endian = metadata.is_little_endian;
        new.pixel_type = map_pixel_type(metadata.pixel_type)?;
        Ok(new)
    }

    fn metadata(&self) -> Result<Ome, Error> {
        let reader = self.get_reader()?;
        let metadata = reader.metadata();
        let ome_metadata = reader
            .ome_metadata()
            .ok_or_else(|| Error::Parse("no metadata".into()))?;
        let xml = ome_metadata.to_ome_xml(metadata);
        Ok(Ome::from_xml(xml)?)
    }

    fn get_frame(&self, c: usize, z: usize, t: usize) -> Result<Frame, Error> {
        let mut reader = self.get_reader()?;
        let metadata = reader.metadata();
        let bytes = if metadata.is_rgb && metadata.is_interleaved {
            let index = Self::get_index(metadata.dimension_order, &self.shape, 0, z, t);
            self.deinterleave(reader.open_bytes(index)?, c)?
        } else {
            let index = Self::get_index(metadata.dimension_order, &self.shape, c, z, t);
            reader.open_bytes(index)?
        };
        self.bytes_to_frame(bytes)
    }

    fn path(&self) -> &Path {
        &self.path
    }

    fn series(&self) -> usize {
        self.series
    }

    fn position(&self) -> usize {
        0
    }

    fn shape(&self) -> &Shape {
        &self.shape
    }

    fn pixel_type(&self) -> &PixelType {
        &self.pixel_type
    }

    fn get_available_positions<P>(_path: P, _series: usize) -> Result<HashSet<usize>, Error>
    where
        P: AsRef<Path>,
    {
        Ok(HashSet::from([0]))
    }

    fn get_available_series<P>(path: P) -> Result<HashSet<usize>, Error>
    where
        P: AsRef<Path>,
    {
        let reader = ImageReader::open(path.as_ref())
            .map_err(|e| Error::Parse(format!("bioformats failed to open: {}", e)))?;
        let n = reader.series_count();
        Ok(HashSet::from_iter(0..n))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn open(file: &str) -> Result<BioFormatsRustReader, Error> {
        let path = std::env::current_dir()?
            .join("tests")
            .join("files")
            .join(file);
        BioFormatsRustReader::new(&path, 0, 0)
    }

    macro_rules! test_metadata {
        ($($name:ident: $file:expr $(,)?)*) => {
            $(
                #[test]
                fn $name() -> Result<(), Error> {
                    let bf = open($file)?;
                    println!("{}", bf.view().squeeze()?.summary()?);
                    Ok(())
                }
            )*
        };
    }

    test_metadata! {
        metadata_a: "czi/1xp53-01-AP1.czi",
        metadata_b: "czi/beads_2023_05_04__19_00_22.czi",
        metadata_c: "czi/Experiment-2029.czi",
        metadata_d: "czi/MK022_cE9_1-01-Airyscan Processing-01-Scene-2-P1.czi",
        metadata_e: "czi/YTL1849A131_2023_05_04__13_36_36.czi",
        metadata_f: "czi/EU_UV_t=1-01.czi",
        metadata_g: "tiffseq/4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif",
        metadata_h: "tiffseq/20-Pos_005_005/img_000000000_Cy3-Cy3_filter_000.tif"
    }
}