use crate::colors::Color; use crate::error::Error; use crate::readers::{ArrayT, DynReader, Frame, PixelType, Reader, Shape}; use indexmap::IndexMap; use itertools::Itertools; use libczirw_sys::{AttachmentData, Dimension, InputStream, ReaderOpenInfo}; use ndarray::{Array2, s}; use ome_metadata::{Ome, ome}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use thread_local::ThreadLocal; #[derive(Debug, Deserialize, Serialize)] pub struct CziReader { #[serde(skip)] reader: ThreadLocal, block_map: HashMap<(usize, usize, usize), Vec>, path: PathBuf, series: usize, position: usize, shape: Shape, pixel_type: PixelType, } impl From for DynReader { fn from(value: CziReader) -> Self { DynReader::Czi(value) } } impl Hash for CziReader { fn hash(&self, state: &mut H) { self.path.hash(state); self.series.hash(state); self.position.hash(state); } } impl PartialEq for CziReader { fn eq(&self, other: &Self) -> bool { self.path == other.path && self.series == other.series && self.position == other.position && self.shape == other.shape && self.pixel_type == other.pixel_type } } impl Eq for CziReader {} impl Clone for CziReader { fn clone(&self) -> Self { Self { reader: ThreadLocal::default(), block_map: self.block_map.clone(), path: self.path.clone(), series: self.series, position: self.position, shape: self.shape.clone(), pixel_type: self.pixel_type, } } } impl CziReader { fn get_reader(&self) -> Result<&libczirw_sys::CziReader, Error> { self.reader.get_or_try(|| { let reader = libczirw_sys::CziReader::create()?; let stream = InputStream::create_from_file_utf8(self.path.to_string_lossy().as_ref())?; let open_info = ReaderOpenInfo::new(&stream); reader.open(open_info)?; Ok(reader) }) } // pub fn set_reader(&self) -> Result<(), Error> { // self.get_reader().map(|_| ()) // } fn metadata_xml(&self) -> Result { let reader = self.get_reader()?; let metadata_segment = reader.get_metadata_segment()?; let xml = metadata_segment.get_metadata_as_xml()?; Ok(String::try_from(&xml)?) } fn attachments(&self) -> Result, Error> { let reader = self.get_reader()?; let n = reader.get_attachment_count()?; let mut attachments = HashMap::new(); for i in 0..n { let attachment = reader.read_attachment(i)?; let info = attachment.get_info()?; let name = info.get_name()?; let data = attachment.get_data()?; attachments.insert(name, data); } Ok(attachments) } } #[derive(Debug, Clone)] enum Version { /// unspecified None, /// 1.0 One, /// 1.1, 1.2, etc Other, } impl From> for Version { fn from(s: Option) -> Self { if let Some(v) = s { if v == "1.0" { Self::One } else { Self::Other } } else { Self::None } } } #[derive(Debug, thiserror::Error)] pub enum CziError { #[error("czi file has no valid blocks")] NoValidBlocks, } impl Reader for CziReader { fn new

(path: P, series: usize, position: usize) -> Result where P: AsRef, { let mut new = Self { reader: ThreadLocal::default(), block_map: HashMap::new(), path: path.as_ref().to_path_buf(), series, position, shape: Shape::default(), pixel_type: PixelType::U8, }; let reader = new.get_reader()?; let statistics_simple = reader.get_statistics_simple()?; let c = statistics_simple.get_sub_block_count(); let mut block_map: HashMap<(usize, usize, usize), Vec> = HashMap::new(); let mut pixel_type = None; let m = statistics_simple.get_max_m_index() != i32::MIN; for i in 0..c { if let Ok(info) = reader.try_get_sub_block_info_for_index(i) { if m && info.get_m_index() != position as i32 { continue; } let coordinate = info.get_coordinate(); let d = Dimension::vec_from_bitflags(coordinate.get_dimensions_valid()); let v = coordinate.get_value(); if d.contains(&Dimension::S) && v[Dimension::S as usize - 1] as usize != series { continue; } let c = if d.contains(&Dimension::C) { v[Dimension::C as usize - 1] as usize } else { 0 }; let z = if d.contains(&Dimension::Z) { v[Dimension::Z as usize - 1] as usize } else { 0 }; let t = if d.contains(&Dimension::T) { v[Dimension::T as usize - 1] as usize } else { 0 }; if let Some(blocks) = block_map.get_mut(&(c, z, t)) { blocks.push(i); } else { block_map.insert((c, z, t), vec![i]); } if pixel_type.is_none() { pixel_type = Some(info.get_pixel_type()?.try_into()?) } } } let mut min_x = i32::MAX; let mut max_x = i32::MIN; let mut min_y = i32::MAX; let mut max_y = i32::MIN; for &i in Ok(block_map.get(&(0, 0, 0))) .transpose() .unwrap_or_else(|| block_map.values().next().ok_or(CziError::NoValidBlocks))? { if let Ok(info) = reader.try_get_sub_block_info_for_index(i) { let rect = info.get_logical_rect(); min_x = min_x.min(rect.get_x()); max_x = max_x.max(rect.get_x() + rect.get_w()); min_y = min_y.min(rect.get_y()); max_y = max_y.max(rect.get_y() + rect.get_h()); } } let dim_bounds = statistics_simple.get_dim_bounds(); let dimensions = Dimension::vec_from_bitflags(dim_bounds.get_dimensions_valid()); for (i, d) in dim_bounds.get_size().into_iter().zip(dimensions) { match d { Dimension::C => new.shape.c = i as usize, Dimension::Z => new.shape.z = i as usize, Dimension::T => new.shape.t = i as usize, _ => {} } } new.shape.x = (max_y - min_y) as usize; new.shape.y = (max_x - min_x) as usize; new.block_map = block_map; new.pixel_type = pixel_type.ok_or(CziError::NoValidBlocks)?; Ok(new) } fn metadata(&self) -> Result { let mut ome = Ome::default(); let xml = xmltree::Element::parse(self.metadata_xml()?.as_bytes())?; if let Some(metadata) = xml.get_child("Metadata") { let experiment = metadata.get_child("Experiment"); let version: Version = if let Some(version) = metadata.get_child("Version") { version.get_text().map(|s| s.to_string()) } else if let Some(experiment) = experiment && let Some(version) = experiment.attributes.get("Version") { Some(version.to_string()) } else { None } .into(); let information = metadata.get_child("Information"); let display_setting = metadata.get_child("DisplaySetting"); let acquisition_block = experiment .and_then(|e| e.get_child("ExperimentBlocks")) .and_then(|e| e.get_child("AcquisitionBlock")); let instrument = information.and_then(|i| i.get_child("Instrument")); let image = information.and_then(|i| i.get_child("Image")); let multi_track_setup = acquisition_block.and_then(|e| e.get_child("MultiTrackSetup")); // set experimenters if let Version::One = version { ome.experimenter.push(ome::Experimenter { id: "Experimenter:0".to_string(), user_name: information .and_then(|i| i.get_child("User")) .and_then(|u| u.get_child("DisplayName")) .and_then(|d| d.get_text()) .map(|t| t.to_string()), ..Default::default() }); } else if let Version::Other = version { ome.experimenter.push(ome::Experimenter { id: "Experimenter:0".to_string(), user_name: information .and_then(|i| i.get_child("Document")) .and_then(|d| d.get_child("UserName")) .and_then(|u| u.get_text()) .map(|t| t.to_string()), ..Default::default() }); } // set instruments if let Version::One = version { ome.instrument.push(ome::Instrument { id: instrument .and_then(|i| i.attributes.get("Id")) .map(|i| i.to_string()) .unwrap_or("Instrument:0".to_string()), ..Default::default() }) } else if let Version::Other = version && let Some(microscopes) = instrument.and_then(|i| i.get_child("Microscopes")) { for i in 0..microscopes.children.len() { ome.instrument.push(ome::Instrument { id: format!("Instrument:{}", i), ..Default::default() }) } } // set detectors if let Some(detectors) = instrument.and_then(|i| i.get_child("Detectors")) { if let Version::One = version { for detector in &detectors.children { if let Some(detector) = detector.as_element() { let n = ome.instrument[0].detector.len(); ome.instrument[0].detector.push(ome::Detector { id: detector .attributes .get("Id") .map(|i| i.to_string()) .unwrap_or(format!("Detector:{}", n)), model: detector .get_child("Manufacturer") .and_then(|m| m.get_child("Model")) .and_then(|m| m.get_text()) .map(|t| t.to_string()), amplification_gain: detector .get_child("AmplificationGain") .and_then(|a| a.get_text()) .and_then(|t| t.parse().ok()), gain: detector .get_child("Gain") .and_then(|g| g.get_text()) .and_then(|t| t.parse().ok()), zoom: detector .get_child("Zoom") .and_then(|z| z.get_text()) .and_then(|t| t.parse().ok()), r#type: Some( detector .get_child("Type") .and_then(|t| t.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(ome::DetectorType::Other), ), ..Default::default() }) } } } else if let Version::Other = version { for detector in &detectors.children { if let Some(detector) = detector.as_element() { let n = ome.instrument[0].detector.len(); ome.instrument[0].detector.push(ome::Detector { id: detector .attributes .get("Id") .map(|i| i.replace(" ", "")) .unwrap_or(format!("Detector:{}", n)), model: detector .get_child("Manufacturer") .and_then(|m| m.get_child("Model")) .and_then(|m| m.get_text()) .map(|t| t.to_string()), r#type: Some( detector .get_child("Type") .and_then(|t| t.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(ome::DetectorType::Other), ), ..Default::default() }) } } } } // set objectives if let Some(objectives) = instrument.and_then(|i| i.get_child("Objectives")) { for objective in &objectives.children { if let Some(objective) = objective.as_element() { let n = ome.instrument[0].objective.len(); ome.instrument[0].objective.push(ome::Objective { id: objective .attributes .get("Id") .map(|t| t.to_string()) .unwrap_or(format!("Objective:{}", n)), model: objective .get_child("Manufacturer") .and_then(|m| m.get_child("Model")) .and_then(|m| m.get_text()) .map(|t| t.to_string()), lens_na: objective .get_child("LensNA") .and_then(|l| l.get_text()) .and_then(|l| l.parse().ok()), nominal_magnification: objective .get_child("NominalMagnification") .and_then(|n| n.get_text()) .and_then(|l| l.parse().ok()), ..Default::default() }) } } } // set tube lenses let pat = regex::Regex::new(r"\d+[,.]\d*")?; if let Version::One = version { if let Some(multi_track_setup) = multi_track_setup { for (idx, track_setup) in multi_track_setup.children.iter().enumerate() { if let Some(tube_lens) = track_setup .as_element() .and_then(|i| i.get_child("TubeLens")) { let text = tube_lens.get_text().map(|t| t.to_string()); ome.instrument[0].objective.push(ome::Objective { id: format!("Objective:Tubelens:{idx}"), nominal_magnification: Some( text.as_ref() .map(|t| t.replace(",", ".")) .and_then(|t| { pat.captures(t.as_str()) .and_then(|c| c.get(0)) .and_then(|c| c.as_str().parse().ok()) }) .unwrap_or(1.0), ), model: text, ..Default::default() }); } } } } else if let Version::Other = version && let Some(tube_lenses) = instrument.and_then(|i| i.get_child("TubeLenses")) { for (idx, tube_lens) in tube_lenses.children.iter().enumerate() { if let Some(tube_lens) = tube_lens.as_element() { let text = tube_lens.attributes.get("Name").map(|t| t.to_string()); ome.instrument[0].objective.push(ome::Objective { id: format!( "Objective:{}", tube_lenses .attributes .get("Id") .unwrap_or(&format!("Tubelens:{}", idx)) ), nominal_magnification: Some( text.as_ref() .map(|t| t.replace(",", ".")) .and_then(|t| { pat.captures(t.as_str()) .and_then(|c| c.get(0)) .and_then(|c| c.as_str().parse().ok()) }) .unwrap_or(1.0), ), model: text, ..Default::default() }) } } } // set light sources if let Some(light_sources) = instrument.and_then(|i| i.get_child("LightSources")) { if let Version::One = version { for (idx, light_source) in light_sources.children.iter().enumerate() { if let Some(light_source) = light_source.as_element() && let Some(laser) = light_source .get_child("LightSourceType") .and_then(|l| l.get_child("Laser")) { ome.instrument[0] .light_source_group .push(ome::LightSourceGroup::Laser(ome::Laser { id: light_source .attributes .get("Id") .map(|i| i.to_string()) .unwrap_or(format!("Laser:{}", idx)), model: light_source .get_child("Manufacturer") .and_then(|m| m.get_child("Model")) .and_then(|m| m.get_text()) .map(|t| t.to_string()), power: light_source .get_child("Power") .and_then(|p| p.get_text()) .and_then(|t| t.parse().ok()), wavelength: laser .get_child("Wavelength") .and_then(|w| w.get_text()) .and_then(|t| t.parse().ok()), ..Default::default() })) } } } else if let Version::Other = version { let pat = regex::Regex::new(r"^.*?(\d*)$")?; for (idx, light_source) in light_sources.children.iter().enumerate() { if let Some(light_source) = light_source.as_element() && light_source .get_child("LightSourceType") .and_then(|l| l.get_child("Laser")) .is_some() { let id = light_source.attributes.get("Id"); ome.instrument[0] .light_source_group .push(ome::LightSourceGroup::Laser(ome::Laser { id: format!( "LightSource:{}", id.unwrap_or(&format!("{}", idx)) ), power: light_source .get_child("Power") .and_then(|p| p.get_text()) .and_then(|t| t.parse().ok()), wavelength: id.and_then(|i| { pat.captures(i) .and_then(|c| c.get(1)) .and_then(|c| c.as_str().parse().ok()) }), ..Default::default() })) } } } } // set filters if let Version::One = version && let Some(multi_track_setup) = multi_track_setup { for (idx, track_setup) in multi_track_setup.children.iter().enumerate() { if let Some(track_setup) = track_setup.as_element() && let Some(beam_splitters) = track_setup.get_child("BeamSplitters") { for beam_splitter in &beam_splitters.children { if let Some(beam_splitter) = beam_splitter.as_element() { ome.instrument[0].filter_set.push(ome::FilterSet { id: format!("FilterSet:{}", idx), model: beam_splitter .get_child("Filter") .and_then(|f| f.get_text()) .map(|t| t.to_string()), ..Default::default() }) } } } } } // get positions let (pos_x, pos_y, pos_z) = if let Version::One = version { image .and_then(|i| i.get_child("S")) .and_then(|s| s.get_child("Scenes")) .and_then(|s| s.children.first()) .and_then(|c| c.as_element()) .and_then(|e| e.get_child("Positions")) .and_then(|p| p.children.first()) .and_then(|p| p.as_element()) .map(|p| { ( p.attributes.get("X").and_then(|x| x.parse::().ok()), p.attributes.get("Y").and_then(|y| y.parse::().ok()), p.attributes.get("Z").and_then(|z| z.parse::().ok()), ) }) .unwrap_or((None, None, None)) } else if let Version::Other = version { image .and_then(|i| i.get_child("Dimensions")) .and_then(|d| d.get_child("S")) .and_then(|s| s.get_child("Scenes")) .and_then(|s| s.get_child("CenterPosition")) .and_then(|c| c.get_text()) .map(|s| { let c = s .split(',') .take(2) .map(|i| i.parse::().ok()) .collect::>(); ( c.first().cloned().flatten(), c.get(1).cloned().flatten(), None, ) }) .unwrap_or((None, None, None)) } else { (None, None, None) }; // set pixels if let Some(image) = information.and_then(|i| i.get_child("Image")) { let mut pxsize_x: Option = None; let mut pxsize_y: Option = None; let mut pxsize_z: Option = None; if let Some(items) = metadata .get_child("Scaling") .and_then(|m| m.get_child("Items")) { for distance in &items.children { if let Some(distance) = distance.as_element() { match distance.attributes.get("Id").map(|i| i.as_str()) { Some("X") => { pxsize_x = distance .get_child("Value") .and_then(|v| v.get_text()) .and_then(|v| v.parse().ok()) } Some("Y") => { pxsize_y = distance .get_child("Value") .and_then(|v| v.get_text()) .and_then(|v| v.parse().ok()) } Some("Z") => { pxsize_z = distance .get_child("Value") .and_then(|v| v.get_text()) .and_then(|v| v.parse().ok()) } _ => {} } } } } let objective_settings = image.get_child("ObjectiveSettings"); ome.image.push(ome::Image { id: "Image:0".to_string(), name: information .and_then(|i| i.get_child("Document")) .and_then(|d| d.get_child("Name")) .and_then(|n| n.get_text()) .map(|t| format!("{} #1", t)), pixels: ome::Pixels { id: "Pixels:0".to_string(), size_x: self.shape.x as i32, size_y: self.shape.y as i32, size_z: self.shape.z as i32, size_c: self.shape.c as i32, size_t: self.shape.t as i32, dimension_order: ome::PixelsDimensionOrderType::Xyczt, r#type: image .get_child("PixelType") .and_then(|p| p.get_text()) .and_then(|t| t.to_lowercase().replace("gray", "uint").parse().ok()) .unwrap_or(ome::PixelType::Uint16), significant_bits: image .get_child("ComponentBitCount") .and_then(|c| c.get_text()) .and_then(|t| t.parse().ok()), big_endian: None, interleaved: None, metadata_only: None, physical_size_x: pxsize_x.map(|p| p * 1e9), physical_size_x_unit: ome::UnitsLength::nm, physical_size_y: pxsize_y.map(|p| p * 1e9), physical_size_y_unit: ome::UnitsLength::nm, physical_size_z: pxsize_z.map(|p| p * 1e9), physical_size_z_unit: ome::UnitsLength::nm, time_increment: None, time_increment_unit: ome::UnitsTime::s, channel: Vec::new(), bin_data: Vec::new(), tiff_data: Vec::new(), plane: Vec::new(), }, experimenter_ref: Some(ome::AnnotationRef { id: "Experimenter:0".to_string(), }), instrument_ref: Some(ome::AnnotationRef { id: "Instrument:0".to_string(), }), objective_settings: objective_settings.map(|o| ome::ObjectiveSettings { id: o .get_child("ObjectiveRef") .and_then(|o| o.attributes.get("Id")) .map(|t| t.to_string()) .unwrap_or("Objective:0".to_string()), medium: o .get_child("Medium") .and_then(|m| m.get_text()) .and_then(|t| t.parse().ok()), refractive_index: o .get_child("RefractiveIndex") .and_then(|r| r.get_text()) .and_then(|t| t.parse().ok()), ..Default::default() }), stage_label: Some(ome::StageLabel { name: "Scene position #0".to_string(), x: pos_x, x_unit: ome::UnitsLength::um, y: pos_y, y_unit: ome::UnitsLength::um, z: pos_z, z_unit: ome::UnitsLength::um, }), acquisition_date: None, description: None, experiment_ref: None, experimenter_group_ref: None, imaging_environment: None, roi_ref: Vec::new(), microbeam_manipulation_ref: Vec::new(), annotation_ref: Vec::new(), }); } // channels let channels_im = image .and_then(|i| i.get_child("Dimensions")) .and_then(|d| d.get_child("Channels")) .map(|c| { c.children .iter() .filter_map(|c| { c.as_element() .and_then(|e| e.attributes.get("Id").map(|id| (id, e))) }) .collect::>() }) .unwrap_or_else(IndexMap::new); let channels_ds = display_setting .and_then(|d| d.get_child("Channels")) .map(|c| { c.children .iter() .filter_map(|c| { c.as_element() .and_then(|e| e.attributes.get("Id").map(|id| (id, e))) }) .collect::>() }) .unwrap_or_else(HashMap::new); let channels_ts = experiment .and_then(|e| e.get_child("ExperimentBlocks")) .and_then(|e| e.get_child("AcquisitionBlock")) .and_then(|a| a.get_child("MultiTrackSetup")) .map(|m| { m.children .iter() .filter_map(|t| { t.as_element().and_then(|ts| { ts.get_child("Detectors").map(move |d| { d.children.iter().filter_map(move |d| { d.as_element() .and_then(|d| d.attributes.get("Id").map(|id| (id, ts))) }) }) }) }) .flatten() .collect::>() }) .unwrap_or_else(HashMap::new); // set channels for (idx, (&key, &channel)) in channels_im.iter().enumerate() { let detector_settings = channel.get_child("DetectorSettings"); let laser_scan_info = channel.get_child("LaserScanInfo"); let detector = detector_settings.and_then(|ds| ds.get_child("Detector")); let filter_set = channels_ts .get(key) .and_then(|c| c.get_child("BeamSplitters")) .and_then(|b| b.children.first()) .and_then(|b| b.as_element()) .and_then(|b| b.get_child("Filter")) .and_then(|f| f.get_text()) .map(|t| t.to_string()) .and_then(|fs| { ome.instrument[0] .filter_set .iter() .find(|&f| Some(&fs) == f.model.as_ref()) }); let light_source_settings = if let Version::One = version { // no space in ome for multiple lightsources simultaneously channel .get_child("LightSourcesSettings") .and_then(|ls| { if ls.children.is_empty() { None } else if ls.children.len() > idx { Some(&ls.children[idx]) } else { Some(&ls.children[0]) } }) .and_then(|lss| lss.as_element()) .and_then(|lss| lss.get_child("LightSource").map(|ls| (lss, ls))) .and_then(|(lss, ls)| ls.attributes.get("Id").map(|id| (lss, id))) .map(|(lss, id)| ome::LightSourceSettings { id: id.to_string(), attenuation: lss .get_child("Attenuation") .and_then(|a| a.get_text()) .and_then(|t| t.parse().ok()), wavelength: lss .get_child("Wavelength") .and_then(|w| w.get_text()) .and_then(|t| t.parse().ok()) .filter(|&w| w > 0.0), wavelength_unit: ome::UnitsLength::nm, }) } else if let Version::Other = version { channel.get_child("LightSourcesSettings").and_then(|ls| { ls.children.first().and_then(|l| l.as_element()).map(|f| { ome::LightSourceSettings { id: format!( "LightSource:{}", ls.children .iter() .filter_map(|l| l .as_element() .and_then(|i| i.attributes.get("Id"))) .join("_") ), attenuation: f .get_child("Attenuation") .and_then(|a| a.get_text()) .and_then(|t| t.parse().ok()), wavelength: f .get_child("Wavelength") .and_then(|w| w.get_text()) .and_then(|t| t.parse().ok()), wavelength_unit: ome::UnitsLength::nm, } }) }) } else { None }; ome.image[0].pixels.channel.push(ome::Channel { id: format!("Channel:{}", idx), name: channel.attributes.get("Name").map(|n| n.to_string()), acquisition_mode: channel .get_child("AcquisitionMode") .and_then(|a| a.get_text()) .and_then(|t| { t.replace("SingleMoleculeLocalisation", "SingleMoleculeImaging") .parse() .ok() }), color: channel .attributes .get("Id") .and_then(|id| channels_ds.get(id)) .and_then(|c| c.get_child("Color")) .and_then(|c| c.get_text()) .and_then(|c| c.parse::().ok()) .map(|i| { let rgb = i.to_rgb(); 65536 * (rgb[0] as i32) + 256 * (rgb[1] as i32) + (rgb[2] as i32) }) .unwrap_or(0), detector_settings: detector.and_then(|d| d.attributes.get("Id")).map(|id| { ome::DetectorSettings { id: id.to_string().replace(" ", ""), binning: Some( detector_settings .and_then(|d| d.get_child("Binning")) .and_then(|b| { if b.children.is_empty() { None } else if b.children.len() < self.shape.c { b.children.first() } else { b.children.get(idx) } }) .and_then(|b| b.as_text()) .and_then(|t| t.parse().ok()) .unwrap_or(ome::BinningType::Other), ), ..Default::default() } }), emission_wavelength: channel .get_child("EmissionWaveLength") .and_then(|e| e.get_text()) .and_then(|t| t.parse().ok()) .filter(|&w| w > 0.0), emission_wavelength_unit: ome::UnitsLength::nm, excitation_wavelength: light_source_settings .as_ref() .and_then(|ls| ls.wavelength) .or_else(|| { channel .get_child("ExcitationWavelength") .and_then(|e| e.get_text()) .and_then(|t| t.parse().ok()) }), excitation_wavelength_unit: light_source_settings .as_ref() .map(|ls| ls.wavelength_unit) .unwrap_or(ome::UnitsLength::nm), filter_set_ref: filter_set.map(|fs| ome::AnnotationRef { id: fs.id.clone() }), illumination_type: channel .get_child("IlluminationType") .and_then(|i| i.get_text()) .and_then(|t| t.parse().ok()), light_source_settings, samples_per_pixel: laser_scan_info .and_then(|ls| ls.get_child("Averaging")) .and_then(|a| a.get_text()) .and_then(|t| t.parse().ok()), contrast_method: channel .get_child("ContrastMethod") .and_then(|cm| cm.get_text()) .and_then(|t| t.parse().ok()), ..Default::default() }); } // set planes let time_stamps = self .attachments()? .get("TimeStamps") .and_then(|a| a.try_into_float().ok()) .map(|mut time_stamps| { time_stamps.sort_by(|a, b| a.partial_cmp(b).unwrap()); let mut dt = time_stamps .array_windows() .filter_map(|[a, b]| if b > a { Some(b - a) } else { None }) .collect::>(); if !dt.is_empty() { let mean = dt.iter().sum::() / dt.len() as f64; let var = dt.iter().map(|i| (i - mean).powi(2)).sum::() / dt.len() as f64; if dt.len() > 2 && var.sqrt() / mean > 0.02 { dt.sort_by(|a, b| a.partial_cmp(b).unwrap()); let n = dt.len(); let median = if n.is_multiple_of(2) { (dt[n / 2 - 1] + dt[n / 2]) / 2.0 } else { dt[n / 2] }; println!( "warning: delta_t is inconsistent, using median value {}", median ); (0..time_stamps.len()) .map(|i| (i as f64) * median) .collect::>() } else { time_stamps } } else { time_stamps } }); let exposure_times = channels_im .iter() .filter_map(|(_, &c)| { c.get_child("LaserScanInfo") .and_then(|ls| ls.get_child("FrameTime")) .and_then(|ft| ft.get_text()) .and_then(|t| t.parse::().ok()) }) .collect::>(); for c in 0..self.shape.c { let exposure_time = if exposure_times.is_empty() { None } else if c < exposure_times.len() { Some(exposure_times[0]) } else { Some(exposure_times[c]) }; for z in 0..self.shape.z { for t in 0..self.shape.t { ome.image[0].pixels.plane.push(ome::Plane { the_c: Some(c as i32), the_z: Some(z as i32), the_t: Some(t as i32), delta_t: time_stamps .as_ref() .and_then(|ts| ts.get(t).map(|&t| t as f32)), position_x: pos_x, position_x_unit: ome::UnitsLength::nm, position_y: pos_y, position_y_unit: ome::UnitsLength::nm, position_z: pos_z, position_z_unit: ome::UnitsLength::nm, exposure_time, exposure_time_unit: ome::UnitsTime::s, ..Default::default() }) } } } // annotations if let Some(layers) = metadata.get_child("Layers") { for layer in &layers.children { if let Some(geometry) = layer .as_element() .and_then(|layer| layer.get_child("Elements")) .and_then(|elements| elements.get_child("Rectangle")) .and_then(|rectangle| rectangle.get_child("Geometry")) { ome.roi.push(ome::Roi { id: format!("ROI:{}", ome.roi.len()), union: Some(ome::RoiUnion { shape_group: vec![ome::ShapeGroup::Rectangle(ome::Rectangle { id: "Shape:0:0".to_string(), height: geometry .get_child("Height") .and_then(|height| height.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(f32::NAN), width: geometry .get_child("Width") .and_then(|width| width.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(f32::NAN), x: geometry .get_child("Left") .and_then(|left| left.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(f32::NAN), y: geometry .get_child("Right") .and_then(|right| right.get_text()) .and_then(|t| t.parse().ok()) .unwrap_or(f32::NAN), ..Default::default() })], }), ..Default::default() }) } } } } Ok(ome) } fn get_frame(&self, c: usize, z: usize, t: usize) -> Result { let reader = self.get_reader()?; let mut min_x = i32::MAX; let mut min_y = i32::MAX; if let Some(indices) = self.block_map.get(&(c, z, t)) { for &i in indices { if let Ok(info) = reader.try_get_sub_block_info_for_index(i) { let rect = info.get_logical_rect(); min_x = min_x.min(rect.get_x()); min_y = min_y.min(rect.get_y()); } } } macro_rules! get_frame { ($t:tt, $n:expr) => {{ let mut array = Array2::zeros((self.shape.y, self.shape.x)); if let Some(indices) = self.block_map.get(&(c, z, t)) { for &i in indices { let sub_block = reader.read_sub_block(i)?; let bitmap = sub_block.create_bitmap()?.lock()?; let bytes = bitmap.lock_info.get_data_roi(); let info = sub_block.get_info()?; let rect = info.get_logical_rect(); let x = (rect.get_x() - min_x) as usize; let y = (rect.get_y() - min_y) as usize; let w = rect.get_w() as usize; let h = rect.get_h() as usize; array .slice_mut(s![x..x + w, y..y + h]) .assign(&Array2::from_shape_vec( (w, h), bytes .chunks($n) .map(|x| $t::from_le_bytes(x.try_into().unwrap())) .collect(), )?); } } Ok(ArrayT::from(array)) }}; } match self.pixel_type { PixelType::I8 => get_frame!(i8, 1), PixelType::U8 => get_frame!(u8, 1), PixelType::I16 => get_frame!(i16, 2), PixelType::U16 => get_frame!(u16, 2), PixelType::I32 => get_frame!(i32, 4), PixelType::U32 => get_frame!(u32, 4), PixelType::F32 => get_frame!(f32, 4), PixelType::F64 => get_frame!(f64, 8), PixelType::I64 => get_frame!(i64, 8), PixelType::U64 => get_frame!(u64, 8), PixelType::I128 => get_frame!(i128, 16), PixelType::U128 => get_frame!(u128, 16), PixelType::F128 => get_frame!(f64, 8), } } fn path(&self) -> &Path { &self.path } fn series(&self) -> usize { self.series } fn position(&self) -> usize { self.position } fn shape(&self) -> &Shape { &self.shape } fn pixel_type(&self) -> &PixelType { &self.pixel_type } fn get_available_positions

(path: P, series: usize) -> Result, Error> where P: AsRef, { let new = Self { reader: ThreadLocal::default(), block_map: HashMap::new(), path: path.as_ref().to_path_buf(), series, position: 0, shape: Shape::default(), pixel_type: PixelType::U8, }; let reader = new.get_reader()?; let statistics_simple = reader.get_statistics_simple()?; let c = statistics_simple.get_sub_block_count(); let mut positions = HashSet::new(); for i in 0..c { if let Ok(info) = reader.try_get_sub_block_info_for_index(i) { let coordinate = info.get_coordinate(); let d = Dimension::vec_from_bitflags(coordinate.get_dimensions_valid()); let v = coordinate.get_value(); if d.contains(&Dimension::S) && v[Dimension::S as usize - 1] as usize != series { continue; } positions.insert(info.get_m_index() as usize); } } if positions.is_empty() { positions.insert(0); } Ok(positions) } fn get_available_series

(path: P) -> Result, Error> where P: AsRef, { let new = Self { reader: ThreadLocal::default(), block_map: HashMap::new(), path: path.as_ref().to_path_buf(), series: 0, position: 0, shape: Shape::default(), pixel_type: PixelType::U8, }; let reader = new.get_reader()?; let statistics_simple = reader.get_statistics_simple()?; let c = statistics_simple.get_sub_block_count(); let mut series = HashSet::new(); for i in 0..c { if let Ok(info) = reader.try_get_sub_block_info_for_index(i) { let coordinate = info.get_coordinate(); let d = Dimension::vec_from_bitflags(coordinate.get_dimensions_valid()); let v = coordinate.get_value(); if d.contains(&Dimension::S) { series.insert(v[Dimension::S as usize - 1] as usize); } } } if series.is_empty() { series.insert(0); } Ok(series) } } impl TryFrom for PixelType { type Error = Error; fn try_from(value: libczirw_sys::PixelType) -> Result { match value { libczirw_sys::PixelType::Gray8 => Ok(PixelType::U8), libczirw_sys::PixelType::Gray16 => Ok(PixelType::U16), libczirw_sys::PixelType::Gray32 => Ok(PixelType::U32), libczirw_sys::PixelType::Gray64Float => Ok(PixelType::F64), _ => Err(Error::Conversion(format!("{:?}", value))), } } } #[cfg(test)] mod tests { use super::*; fn open(file: &str) -> Result { let path = std::env::current_dir()? .join("tests") .join("files") .join(file); CziReader::new(&path, 0, 0) } // fn write_xml(file: &str, xml: &str) -> Result<(), Error> { // let path = std::env::current_dir()? // .join("tests") // .join("files") // .join("czi_xml") // .join(file) // .with_extension("xml"); // std::fs::write(path, xml)?; // Ok(()) // } macro_rules! test_metadata { ($($name:ident: $file:expr $(,)?)*) => { $( #[test] fn $name() -> Result<(), Error> { let czi = open($file)?; // let ome = czi.metadata()?; // write_xml($file, &czi.metadata_xml()?)?; // let ome = czi.metadata()?; // println!("{:?}", ome); println!("{}", czi.view().squeeze()?.summary()?); // println!("block map: {:#?}", czi.block_map); 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", } }