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"
}
}