1251 lines
54 KiB
Rust
1251 lines
54 KiB
Rust
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<libczirw_sys::CziReader>,
|
|
block_map: HashMap<(usize, usize, usize), Vec<i32>>,
|
|
path: PathBuf,
|
|
series: usize,
|
|
position: usize,
|
|
shape: Shape,
|
|
pixel_type: PixelType,
|
|
}
|
|
|
|
impl From<CziReader> for DynReader {
|
|
fn from(value: CziReader) -> Self {
|
|
DynReader::Czi(value)
|
|
}
|
|
}
|
|
|
|
impl Hash for CziReader {
|
|
fn hash<H: Hasher>(&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<String, Error> {
|
|
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<HashMap<String, AttachmentData>, 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<Option<String>> for Version {
|
|
fn from(s: Option<String>) -> 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<P>(path: P, series: usize, position: usize) -> Result<Self, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
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<i32>> = 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<Ome, Error> {
|
|
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::<f32>().ok()),
|
|
p.attributes.get("Y").and_then(|y| y.parse::<f32>().ok()),
|
|
p.attributes.get("Z").and_then(|z| z.parse::<f32>().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::<f32>().ok())
|
|
.collect::<Vec<_>>();
|
|
(
|
|
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<f32> = None;
|
|
let mut pxsize_y: Option<f32> = None;
|
|
let mut pxsize_z: Option<f32> = 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::<IndexMap<_, _>>()
|
|
})
|
|
.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::<HashMap<_, _>>()
|
|
})
|
|
.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::<HashMap<_, _>>()
|
|
})
|
|
.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::<Color>().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::<Vec<_>>();
|
|
if !dt.is_empty() {
|
|
let mean = dt.iter().sum::<f64>() / dt.len() as f64;
|
|
let var =
|
|
dt.iter().map(|i| (i - mean).powi(2)).sum::<f64>() / 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::<Vec<_>>()
|
|
} 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::<f32>().ok())
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
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<Frame, Error> {
|
|
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<P>(path: P, series: usize) -> Result<HashSet<usize>, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
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<P>(path: P) -> Result<HashSet<usize>, Error>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
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<libczirw_sys::PixelType> for PixelType {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: libczirw_sys::PixelType) -> Result<Self, Self::Error> {
|
|
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<CziReader, Error> {
|
|
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",
|
|
}
|
|
}
|