From fefdd6448bf11c7703f3482a049d6317b05a23ff Mon Sep 17 00:00:00 2001 From: Wim Pomp Date: Sat, 8 Feb 2025 20:22:45 +0100 Subject: [PATCH] - added ome_xml method - some pyo3 methods --- Cargo.toml | 4 ++-- src/bioformats.rs | 32 ++++++++++++++++++++----------- src/lib.rs | 43 ++++++++++++++++++++++++++++++++++++++--- src/py.rs | 49 +++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db8f715..9d73c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ndbioimage" -version = "2025.1.2" +version = "2025.2.0" edition = "2021" authors = ["Wim Pomp "] license = "GPL-3.0-or-later" @@ -31,9 +31,9 @@ optional = true rayon = "1.10.0" [build-dependencies] +anyhow = { version = "1.0.95"} j4rs = { version = "0.22", features = [] } retry = { version = "2.0.0"} -anyhow = { version = "1.0.95"} [features] python = ["dep:pyo3", "dep:numpy"] diff --git a/src/bioformats.rs b/src/bioformats.rs index 43d81d5..a9e5c17 100644 --- a/src/bioformats.rs +++ b/src/bioformats.rs @@ -31,7 +31,7 @@ macro_rules! method_arg { macro_rules! method { ($name:ident, $method:expr $(,[$($n:tt: $t:ty$(|$p:tt)?),*])? $(=> $tt:ty$(|$c:tt)?)?) => { - pub fn $name(&self, $($($n: $t),*)?) -> method_return!($($tt)?) { + pub(crate) fn $name(&self, $($($n: $t),*)?) -> method_return!($($tt)?) { let args: Vec = vec![$($( method_arg!($n:$t$(|$p)?) ),*)?]; let _result = jvm().invoke(&self.0, $method, &args)?; @@ -55,10 +55,10 @@ macro_rules! method { }; } -pub struct DebugTools; +pub(crate) struct DebugTools; impl DebugTools { - pub fn set_root_level(level: &str) -> Result<()> { + pub(crate) fn set_root_level(level: &str) -> Result<()> { jvm().invoke_static( "loci.common.DebugTools", "setRootLevel", @@ -68,10 +68,10 @@ impl DebugTools { } } -pub struct ChannelSeparator(Instance); +pub(crate) struct ChannelSeparator(Instance); impl ChannelSeparator { - pub fn new(image_reader: &ImageReader) -> Result { + pub(crate) fn new(image_reader: &ImageReader) -> Result { let jvm = jvm(); let channel_separator = jvm.create_instance( "loci.formats.ChannelSeparator", @@ -80,7 +80,7 @@ impl ChannelSeparator { Ok(ChannelSeparator(channel_separator)) } - pub fn open_bytes(&self, index: i32) -> Result> { + pub(crate) fn open_bytes(&self, index: i32) -> Result> { let bi8 = self.open_bi8(index)?; Ok(unsafe { std::mem::transmute::, Vec>(bi8) }) } @@ -89,7 +89,7 @@ impl ChannelSeparator { method!(get_index, "getIndex", [z: i32|p, c: i32|p, t: i32|p] => i32|c); } -pub struct ImageReader(Instance); +pub(crate) struct ImageReader(Instance); impl Drop for ImageReader { fn drop(&mut self) { @@ -98,17 +98,27 @@ impl Drop for ImageReader { } impl ImageReader { - pub fn new() -> Result { + pub(crate) fn new() -> Result { let reader = jvm().create_instance("loci.formats.ImageReader", InvocationArg::empty())?; Ok(ImageReader(reader)) } - pub fn open_bytes(&self, index: i32) -> Result> { + pub(crate) fn open_bytes(&self, index: i32) -> Result> { let bi8 = self.open_bi8(index)?; Ok(unsafe { std::mem::transmute::, Vec>(bi8) }) } + pub(crate) fn ome_xml(&self) -> Result { + let mds = self.get_metadata_store()?; + Ok(jvm() + .chain(&mds)? + .cast("loci.formats.ome.OMEPyramidStore")? + .invoke("dumpXML", &[])? + .to_rust()?) + } + method!(set_metadata_store, "setMetadataStore", [ome_data: Instance]); + method!(get_metadata_store, "getMetadataStore" => Instance); method!(set_id, "setId", [id: &str]); method!(set_series, "setSeries", [series: i32|p]); method!(open_bi8, "openBytes", [index: i32|p] => Vec|c); @@ -129,10 +139,10 @@ impl ImageReader { method!(close, "close"); } -pub struct MetadataTools(Instance); +pub(crate) struct MetadataTools(Instance); impl MetadataTools { - pub fn new() -> Result { + pub(crate) fn new() -> Result { let meta_data_tools = jvm().create_instance("loci.formats.MetadataTools", InvocationArg::empty())?; Ok(MetadataTools(meta_data_tools)) diff --git a/src/lib.rs b/src/lib.rs index 24aef57..21464b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,7 +144,7 @@ pub struct Reader { image_reader: ImageReader, /// path to file pub path: PathBuf, - /// which (if more) than 1 of the series in the file to open + /// which (if more than 1) of the series in the file to open pub series: i32, /// size x (horizontal) pub size_x: usize, @@ -154,7 +154,7 @@ pub struct Reader { pub size_c: usize, /// size z (# slices) pub size_z: usize, - /// size t (time/frames) + /// size t (# time/frames) pub size_t: usize, /// pixel type ((u)int(8/16/32) or float(32/64)) pub pixel_type: PixelType, @@ -214,6 +214,11 @@ impl Reader { }) } + /// Get ome metadata as xml string + pub fn ome_xml(&self) -> Result { + self.image_reader.ome_xml() + } + fn deinterleave(&self, bytes: Vec, channel: usize) -> Result> { let chunk_size = match self.pixel_type { PixelType::INT8 => 1, @@ -311,7 +316,10 @@ mod tests { use rayon::prelude::*; fn open(file: &str) -> Result { - let path = std::env::current_dir()?.join("tests").join("files").join(file); + let path = std::env::current_dir()? + .join("tests") + .join("files") + .join(file); Reader::new(&path, 0) } @@ -363,4 +371,33 @@ mod tests { println!("{:?}", frames); Ok(()) } + + #[test] + fn read_sequence() -> Result<()> { + let file = "YTL1841B2-2-1_1hr_DMSO_galinduction_1/Pos0/img_000000000_mScarlet_GFP-mSc-filter_004.tif"; + let reader = open(file)?; + println!("reader: {:?}", reader); + let frame = reader.get_frame(0, 4, 0)?; + println!("frame: {:?}", frame); + let frame = reader.get_frame(0, 2, 0)?; + println!("frame: {:?}", frame); + Ok(()) + } + + #[test] + fn read_sequence1() -> Result<()> { + let file = "4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif"; + let reader = open(file)?; + println!("reader: {:?}", reader); + Ok(()) + } + + #[test] + fn ome_xml() -> Result<()> { + let file = "Experiment-2029.czi"; + let reader = open(file)?; + let xml = reader.ome_xml()?; + println!("{}", xml); + Ok(()) + } } diff --git a/src/py.rs b/src/py.rs index ca8ef08..abaca35 100644 --- a/src/py.rs +++ b/src/py.rs @@ -1,14 +1,51 @@ +use crate::{Frame, Reader}; +use numpy::{IntoPyArray, PyArrayMethods, ToPyArray}; use pyo3::prelude::*; +use pyo3::BoundObject; +use std::path::PathBuf; -/// Formats the sum of two numbers as string. -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - Ok((a + b).to_string()) +#[pyclass(subclass)] +#[pyo3(name = "Reader")] +#[derive(Debug)] +struct PyReader { + path: PathBuf, + series: i32, +} + +#[pymethods] +impl PyReader { + #[new] + fn new(path: &str, series: usize) -> PyResult { + Ok(PyReader { + path: PathBuf::from(path), + series: series as i32, + }) + } + + fn get_frame<'py>( + &self, + py: Python<'py>, + c: usize, + z: usize, + t: usize, + ) -> PyResult> { + let reader = Reader::new(&self.path, self.series)?; // TODO: prevent making a new Reader each time + Ok(match reader.get_frame(c, z, t)? { + Frame::INT8(arr) => arr.to_pyarray(py).into_any(), + Frame::UINT8(arr) => arr.to_pyarray(py).into_any(), + Frame::INT16(arr) => arr.to_pyarray(py).into_any(), + Frame::UINT16(arr) => arr.to_pyarray(py).into_any(), + Frame::INT32(arr) => arr.to_pyarray(py).into_any(), + Frame::UINT32(arr) => arr.to_pyarray(py).into_any(), + Frame::FLOAT(arr) => arr.to_pyarray(py).into_any(), + Frame::DOUBLE(arr) => arr.to_pyarray(py).into_any(), + }) + } } -/// A Python module implemented in Rust. #[pymodule] +#[pyo3(name = "ndbioimage_rs")] fn ndbioimage_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; Ok(()) }