diff --git a/image-tiff b/image-tiff index 96d37bb..9b452e9 160000 --- a/image-tiff +++ b/image-tiff @@ -1 +1 @@ -Subproject commit 96d37bb087f7eb1d1a3c495b13ad889914061da3 +Subproject commit 9b452e93b708db6844a4402f4aac76311afbf44b diff --git a/ndbioimage/Cargo.toml b/ndbioimage/Cargo.toml index 2b25b62..0483a61 100644 --- a/ndbioimage/Cargo.toml +++ b/ndbioimage/Cargo.toml @@ -33,7 +33,7 @@ libczirw-sys = { version = "0.5", optional = true } ndarray = { version = "0.17", features = ["serde"] } num = "0.4" numpy = { version = "0.29", optional = true } -ome-metadata = "0.5" +ome-metadata = "0.6" ordered-float = { version = "5", optional = true } phf = { version = "0.14", features = ["macros"] } postcard = { version = "1", features = ["use-std"], optional = true } diff --git a/ndbioimage/src/readers/tiff.rs b/ndbioimage/src/readers/tiff.rs index 6101c6d..110610c 100644 --- a/ndbioimage/src/readers/tiff.rs +++ b/ndbioimage/src/readers/tiff.rs @@ -156,47 +156,61 @@ impl Reader for TiffReader { .unwrap_or(1) == 2; - let imagej_bytes = reader - .current_ifd() - .get_tag(Tag::Unknown(50839)) - .or_else(|_| reader.current_ifd().get_tag(Tag::ImageDescription))?; - let imagej_bytes = match imagej_bytes { - Value::Ascii(s) => s.into_bytes(), - other => other.into_u8_vec()?, - }; - let metadata_str = String::from_utf8_lossy(&imagej_bytes); - let metadata_map = parse_imagej_metadata(&metadata_str); + let image_description = reader.current_ifd().get_tag(Tag::ImageDescription); let p_ndim = if n_samples > 1 { 3u8 } else { 2u8 }; - let size_c = if p_ndim == 3 { - n_samples + let (size_c, size_t, size_z) = if let Ok(Value::Ascii(xml)) = image_description + && xml.starts_with(" s.into_bytes(), + other => other.into_u8_vec()?, + }; + let metadata_str = String::from_utf8_lossy(&imagej_bytes); + let metadata_map = parse_imagej_metadata(&metadata_str); + + let size_c = if p_ndim == 3 { + n_samples + } else { + metadata_map + .get("channels") + .and_then(|v| v.parse::().ok()) + .unwrap_or(1) + }; + let size_z = metadata_map + .get("slices") .and_then(|v| v.parse::().ok()) - .unwrap_or(1) - }; - let size_z = metadata_map - .get("slices") - .and_then(|v| v.parse::().ok()) - .unwrap_or(1); - let size_t = metadata_map - .get("frames") - .and_then(|v| v.parse::().ok()) - .unwrap_or(1); + .unwrap_or(1); + let size_t = metadata_map + .get("frames") + .and_then(|v| v.parse::().ok()) + .unwrap_or(1); - let mult = if p_ndim == 3 { n_samples } else { 1 }; - let expected = size_c * size_z * size_t / mult; + let mult = if p_ndim == 3 { n_samples } else { 1 }; + let expected = size_c * size_z * size_t / mult; - let mut n_ifds = 1; - while reader.more_images() { - reader.next_directory()?; - n_ifds += 1; - } + let mut n_ifds = 1; + while reader.more_images() { + reader.next_directory()?; + n_ifds += 1; + } - let size_z = if n_ifds != expected && n_ifds > 0 { - n_ifds / (size_c * size_t / mult).max(1) - } else { - size_z + let size_z = if n_ifds != expected && n_ifds > 0 { + n_ifds / (size_c * size_t / mult).max(1) + } else { + size_z + }; + (size_c, size_t, size_z) }; drop(reader); @@ -220,139 +234,146 @@ impl Reader for TiffReader { fn metadata(&self) -> Result { let mut reader = self.get_reader()?; - let imagej_bytes = reader - .current_ifd() - .get_tag(Tag::Unknown(50839)) - .or_else(|_| reader.current_ifd().get_tag(Tag::ImageDescription))?; - let imagej_bytes = match imagej_bytes { - Value::Ascii(s) => s.into_bytes(), - other => other.into_u8_vec()?, - }; - let metadata_str = String::from_utf8_lossy(&imagej_bytes); - let metadata_map = parse_imagej_metadata(&metadata_str); + let image_description = reader.current_ifd().get_tag(Tag::ImageDescription); + if let Ok(Value::Ascii(xml)) = image_description + && xml.starts_with(" s.into_bytes(), + other => other.into_u8_vec()?, + }; + let metadata_str = String::from_utf8_lossy(&imagej_bytes); + let metadata_map = parse_imagej_metadata(&metadata_str); - let interval_t = metadata_map - .get("interval") - .and_then(|v| v.parse::().ok()); - let ru = reader - .current_ifd() - .get_tag(Tag::ResolutionUnit) - .ok() - .and_then(|i| i.into_u16().ok()) - .unwrap_or(2); - let pixel_size = reader - .current_ifd() - .get_tag(Tag::XResolution) - .ok() - .and_then(|i| i.into_u64_vec().ok()) - .and_then(|i| { - i.first() - .and_then(|&n| i.get(1).map(|&d| (n as f64) / (d as f64))) - }) - .and_then(|i| { - if i <= 0.0 { - None - } else if ru == 3 { - Some(1e4 / i) - } else if ru == 2 { - Some(25400.0 / i) - } else { - Some(1.0 / i) - } - }); - let interval_z = metadata_map - .get("spacing") - .and_then(|v| v.parse::().ok()); + let interval_t = metadata_map + .get("interval") + .and_then(|v| v.parse::().ok()); + let ru = reader + .current_ifd() + .get_tag(Tag::ResolutionUnit) + .ok() + .and_then(|i| i.into_u16().ok()) + .unwrap_or(2); + let pixel_size = reader + .current_ifd() + .get_tag(Tag::XResolution) + .ok() + .and_then(|i| i.into_u64_vec().ok()) + .and_then(|i| { + i.first() + .and_then(|&n| i.get(1).map(|&d| (n as f64) / (d as f64))) + }) + .and_then(|i| { + if i <= 0.0 { + None + } else if ru == 3 { + Some(1e4 / i) + } else if ru == 2 { + Some(25400.0 / i) + } else { + Some(1.0 / i) + } + }); + let interval_z = metadata_map + .get("spacing") + .and_then(|v| v.parse::().ok()); - let ome_pixel_type = match self.pixel_type { - PixelType::I8 => ome::PixelType::Int8, - PixelType::U8 => ome::PixelType::Uint8, - PixelType::I16 => ome::PixelType::Int16, - PixelType::U16 => ome::PixelType::Uint16, - PixelType::I32 => ome::PixelType::Int32, - PixelType::U32 => ome::PixelType::Uint32, - PixelType::F32 => ome::PixelType::Float, - PixelType::F64 => ome::PixelType::Double, - _ => ome::PixelType::Uint16, - }; + let ome_pixel_type = match self.pixel_type { + PixelType::I8 => ome::PixelType::Int8, + PixelType::U8 => ome::PixelType::Uint8, + PixelType::I16 => ome::PixelType::Int16, + PixelType::U16 => ome::PixelType::Uint16, + PixelType::I32 => ome::PixelType::Int32, + PixelType::U32 => ome::PixelType::Uint32, + PixelType::F32 => ome::PixelType::Float, + PixelType::F64 => ome::PixelType::Double, + _ => ome::PixelType::Uint16, + }; - let mut 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: ome_pixel_type, - physical_size_x: pixel_size.map(|v| v as f32), - physical_size_x_unit: ome::UnitsLength::um, - physical_size_y: pixel_size.map(|v| v as f32), - physical_size_y_unit: ome::UnitsLength::um, - physical_size_z: interval_z.map(|v| v as f32), - physical_size_z_unit: ome::UnitsLength::um, - time_increment: interval_t.map(|v| v as f32), - time_increment_unit: ome::UnitsTime::s, - significant_bits: None, - interleaved: None, - big_endian: None, - channel: Vec::new(), - bin_data: Vec::new(), - tiff_data: Vec::new(), - metadata_only: None, - plane: Vec::new(), - }; + let mut 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: ome_pixel_type, + physical_size_x: pixel_size.map(|v| v as f32), + physical_size_x_unit: ome::UnitsLength::um, + physical_size_y: pixel_size.map(|v| v as f32), + physical_size_y_unit: ome::UnitsLength::um, + physical_size_z: interval_z.map(|v| v as f32), + physical_size_z_unit: ome::UnitsLength::um, + time_increment: interval_t.map(|v| v as f32), + time_increment_unit: ome::UnitsTime::s, + significant_bits: None, + interleaved: None, + big_endian: None, + channel: Vec::new(), + bin_data: Vec::new(), + tiff_data: Vec::new(), + metadata_only: None, + plane: Vec::new(), + }; - for c in 0..self.shape.c { - pixels.channel.push(ome::Channel { - id: format!("Channel:{}", c), - name: Some(format!("Channel {}", c)), - ..Default::default() - }); - } + for c in 0..self.shape.c { + pixels.channel.push(ome::Channel { + id: format!("Channel:{}", c), + name: Some(format!("Channel {}", c)), + ..Default::default() + }); + } - for c in 0..self.shape.c { - for z in 0..self.shape.z { - for t in 0..self.shape.t { - 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: interval_t.and_then(|v| { - if v > 0.0 { - Some((t as f64 * v) as f32) - } else { - None - } - }), - ..Default::default() - }); + for c in 0..self.shape.c { + for z in 0..self.shape.z { + for t in 0..self.shape.t { + 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: interval_t.and_then(|v| { + if v > 0.0 { + Some((t as f64 * v) as f32) + } else { + None + } + }), + ..Default::default() + }); + } } } - } - let mut ome = Ome::default(); - ome.image.push(ome::Image { - id: "Image:0".to_string(), - name: self - .path - .file_name() - .map(|n| n.to_string_lossy().to_string()), - pixels, - instrument_ref: None, - objective_settings: None, - acquisition_date: None, - description: None, - experimenter_ref: None, - experiment_ref: None, - experimenter_group_ref: None, - imaging_environment: None, - stage_label: None, - roi_ref: Vec::new(), - microbeam_manipulation_ref: Vec::new(), - annotation_ref: Vec::new(), - }); - Ok(ome) + let mut ome = Ome::default(); + ome.image.push(ome::Image { + id: "Image:0".to_string(), + name: self + .path + .file_name() + .map(|n| n.to_string_lossy().to_string()), + pixels, + instrument_ref: None, + objective_settings: None, + acquisition_date: None, + description: None, + experimenter_ref: None, + experiment_ref: None, + experimenter_group_ref: None, + imaging_environment: None, + stage_label: None, + roi_ref: Vec::new(), + microbeam_manipulation_ref: Vec::new(), + annotation_ref: Vec::new(), + }); + Ok(ome) + } } fn get_frame(&self, c: usize, z: usize, t: usize) -> Result { @@ -521,6 +542,8 @@ mod tests { metadata_e: "tiff/1xp53-01-AP1.tiff", metadata_f: "tiff/test.tif", metadata_g: "tiff/YTL1849A111_2023_05_04__14_46_19_cellnr_1_track.tif", - metadata_h: "tiff/Wapl-AID_C46_13_tagRFPt_MCP_well1_video1_1_max.tif" + metadata_h: "tiff/Wapl-AID_C46_13_tagRFPt_MCP_well1_video1_1_max.tif", + metadata_i: "tiff/R1_NRF1_Gtf3a500nM_REAL_20260731_25pct_500ms_001_MMStack_PosR1_NRF1_Gtf3a500nM_REAL_20260731_25pct_500ms_001.ome.tif" + metadata_j: "tiff/20260922_Pos0_max.tif" } }