From 0ce1187a4389849655d74bd807c3226f0c0bb67f Mon Sep 17 00:00:00 2001 From: "w.pomp" Date: Mon, 13 Jul 2026 13:41:02 +0200 Subject: [PATCH] - metadata --- src/metadata.rs | 222 ++++++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 102 deletions(-) diff --git a/src/metadata.rs b/src/metadata.rs index 969c126..52b8e5c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -8,9 +8,7 @@ use ome_metadata::ome::{ impl Metadata for Ome { fn get_instrument(&self) -> Option<&Instrument> { let instrument_id = self.get_image()?.instrument_ref.as_ref()?.id.clone(); - self.instrument - .iter() - .find(|i| i.id == instrument_id) + self.instrument.iter().find(|i| i.id == instrument_id) } fn get_image(&self) -> Option<&Image> { @@ -42,23 +40,11 @@ pub trait Metadata { .find(|o| o.id == objective_id) } - fn get_tube_lens(&self) -> Option { - Some(Objective { - manufacturer: None, - model: Some("Unknown".to_string()), - serial_number: None, - lot_number: None, - id: "TubeLens:1".to_string(), - correction: None, - immersion: None, - lens_na: None, - nominal_magnification: Some(1.0), - calibrated_magnification: None, - working_distance: None, - working_distance_unit: UnitsLength::um, - iris: None, - annotation_ref: vec![], - }) + fn get_tube_lens(&self) -> Option<&Objective> { + self.get_instrument()? + .objective + .iter() + .find(|o| o.id.starts_with("Objective:Tubelens")) } /// shape of the data along cztyx axes @@ -110,40 +96,46 @@ pub trait Metadata { /// distance between planes in z-stack in nm fn delta_z(&self) -> Result, Error> { - if let Some(pixels) = self.get_pixels() { - if let Some(z) = pixels.physical_size_z { - return Ok(Some( + Ok( + if let Some(pixels) = self.get_pixels() + && let Some(z) = pixels.physical_size_z + { + Some( pixels .physical_size_z_unit .convert(&UnitsLength::nm, z as f64)?, - )); - } - } - Ok(None) + ) + } else { + None + }, + ) } /// time interval in seconds for time-lapse images fn time_interval(&self) -> Result, Error> { - if let Some(pixels) = self.get_pixels() { - if let Some(t) = pixels.plane.iter().map(|p| p.the_t).max() { - if t > 0 { - let plane_a = pixels.plane - .iter() - .find(|p| (p.the_c == 0) && (p.the_z == 0) && (p.the_t == 0)); - let plane_b = pixels.plane - .iter() - .find(|p| (p.the_c == 0) && (p.the_z == 0) && (p.the_t == t)); - if let (Some(a), Some(b)) = (plane_a, plane_b) { - if let (Some(a_t), Some(b_t)) = (a.delta_t, b.delta_t) { - return Ok(Some( - (b.delta_t_unit.convert(&UnitsTime::s, b_t as f64)? - - a.delta_t_unit.convert(&UnitsTime::s, a_t as f64)?) - .abs() - / (t as f64), - )); - } - } - } + if let Some(pixels) = self.get_pixels() + && let Some(t) = pixels.plane.iter().filter_map(|p| p.the_t).max() + && (t > 0) + { + let plane_a = pixels.plane.iter().find(|p| { + (p.the_c.is_none() || (p.the_c == Some(0))) + && (p.the_z.is_none() || (p.the_z == Some(0))) + && (p.the_t == Some(0)) + }); + let plane_b = pixels.plane.iter().find(|p| { + (p.the_c.is_none() || (p.the_c == Some(0))) + && (p.the_z.is_none() || (p.the_z == Some(0))) + && (p.the_t == Some(t)) + }); + if let (Some(a), Some(b)) = (plane_a, plane_b) + && let (Some(a_t), Some(b_t)) = (a.delta_t, b.delta_t) + { + return Ok(Some( + (b.delta_t_unit.convert(&UnitsTime::s, b_t as f64)? + - a.delta_t_unit.convert(&UnitsTime::s, a_t as f64)?) + .abs() + / (t as f64), + )); } } Ok(None) @@ -152,17 +144,20 @@ pub trait Metadata { /// exposure time for channel, z=0 and t=0 fn exposure_time(&self, channel: usize) -> Result, Error> { let c = channel as i32; - if let Some(pixels) = self.get_pixels() { - if let Some(p) = pixels.plane - .iter() - .find(|p| (p.the_c == c) && (p.the_z == 0) && (p.the_t == 0)) + Ok( + if let Some(pixels) = self.get_pixels() + && let Some(p) = pixels.plane.iter().find(|p| { + (p.the_c == Some(c)) + && (p.the_z.is_none() || (p.the_z == Some(0))) + && (p.the_t.is_none() || (p.the_t == Some(0))) + }) + && let Some(t) = p.exposure_time { - if let Some(t) = p.exposure_time { - return Ok(Some(p.exposure_time_unit.convert(&UnitsTime::s, t as f64)?)); - } - } - } - Ok(None) + Some(p.exposure_time_unit.convert(&UnitsTime::s, t as f64)?) + } else { + None + }, + ) } fn binning(&self, channel: usize) -> Option { @@ -184,35 +179,36 @@ pub trait Metadata { } fn laser_wavelengths(&self, channel: usize) -> Result, Error> { - if let Some(pixels) = self.get_pixels() { - if let Some(channel) = pixels.channel.get(channel) { - if let Some(w) = channel.excitation_wavelength { - return Ok(Some( - channel - .excitation_wavelength_unit - .convert(&UnitsLength::nm, w as f64)?, - )); - } - } - } - Ok(None) + Ok( + if let Some(pixels) = self.get_pixels() + && let Some(channel) = pixels.channel.get(channel) + && let Some(w) = channel.excitation_wavelength + { + Some( + channel + .excitation_wavelength_unit + .convert(&UnitsLength::nm, w as f64)?, + ) + } else { + None + }, + ) } fn laser_powers(&self, channel: usize) -> Result, Error> { - if let Some(pixels) = self.get_pixels() { - if let Some(channel) = pixels.channel.get(channel) { - if let Some(ls) = &channel.light_source_settings { - if let Some(a) = ls.attenuation { - return if (0. ..=1.).contains(&a) { - Ok(Some(1f64 - (a as f64))) - } else { - Err(Error::InvalidAttenuation(a.to_string())) - }; - } - } + if let Some(pixels) = self.get_pixels() + && let Some(channel) = pixels.channel.get(channel) + && let Some(ls) = &channel.light_source_settings + && let Some(a) = ls.attenuation + { + if (0. ..=1.).contains(&a) { + Ok(Some(1f64 - (a as f64))) + } else { + Err(Error::InvalidAttenuation(a.to_string())) } + } else { + Ok(None) } - Ok(None) } fn objective_name(&self) -> Option { @@ -249,19 +245,28 @@ pub trait Metadata { } fn gain(&self, channel: usize) -> Option { - if let Some(pixels) = self.get_pixels() { - Some( - *pixels - .channel - .get(channel)? - .detector_settings - .as_ref()? - .gain - .as_ref()? as f64, - ) - } else { - None - } + self.get_pixels() + .and_then(|p| p.channel.get(channel)) + .and_then(|c| c.detector_settings.as_ref()) + .map(|ds| ds.id.as_str()) + .and_then(|detector_id| { + self.get_instrument() + .and_then(|i| i.detector.iter().find(|d| d.id == detector_id)) + .and_then(|d| d.amplification_gain) + .map(|g| g as f64) + }) + } + + fn is_zstack(&self) -> Result { + self.get_pixels() + .map(|p| Ok(p.size_z > 1)) + .unwrap_or_else(|| Err(Error::NoImageOrPixels)) + } + + fn is_time_lapse(&self) -> Result { + self.get_pixels() + .map(|p| Ok(p.size_t > 1)) + .unwrap_or_else(|| Err(Error::NoImageOrPixels)) } fn summary(&self) -> Result { @@ -274,10 +279,14 @@ pub trait Metadata { if let Ok(Some(pixel_size)) = self.pixel_size() { s.push_str(&format!("pixel size: {pixel_size:.2} nm\n")); } - if let Ok(Some(delta_z)) = self.delta_z() { + if let Ok(Some(delta_z)) = self.delta_z() + && self.is_zstack()? + { s.push_str(&format!("z-interval: {delta_z:.2} nm\n")) } - if let Ok(Some(time_interval)) = self.time_interval() { + if let Ok(Some(time_interval)) = self.time_interval() + && self.is_time_lapse()? + { s.push_str(&format!("time interval: {time_interval:.2} s\n")) } let exposure_time = (0..size_c) @@ -288,12 +297,15 @@ pub trait Metadata { .collect::>(); if !exposure_time.is_empty() { s.push_str(&format!( - "exposure time: {}\n", - exposure_time.into_iter().join(" | ") + "exposure time: {} s\n", + exposure_time + .iter() + .map(|e| format!("{:.2}", e)) + .join(" | ") )); } if let Some(magnification) = self.magnification() { - s.push_str(&format!("magnification: {magnification}x\n")) + s.push_str(&format!("magnification: {magnification:.1}x\n")) } if let Some(objective_name) = self.objective_name() { s.push_str(&format!("objective: {objective_name}\n")) @@ -320,7 +332,10 @@ pub trait Metadata { .flatten() .collect::>(); if !gain.is_empty() { - s.push_str(&format!("gain: {}\n", gain.into_iter().join(" "))); + s.push_str(&format!( + "gain: {}\n", + gain.into_iter().join(" | ") + )); } let laser_wavelengths = (0..size_c) .map(|c| self.laser_wavelengths(c)) @@ -342,8 +357,11 @@ pub trait Metadata { .collect::>(); if !laser_powers.is_empty() { s.push_str(&format!( - "laser powers: {}\n", - laser_powers.into_iter().join(" | ") + "laser powers: {} %\n", + laser_powers + .into_iter() + .map(|p| format!("{:.3}", 100.0 * p)) + .join(" | ") )); } let binning = (0..size_c)