diff --git a/pyproject.toml b/pyproject.toml index 1739cc9..1a6bb11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ ndbioimage_generate_stub = "ndbioimage:ndbioimage_generate_stub" [tool.maturin] python-source = "py" -features = ["python", "bioformats_java", "tiffwrite", "czi"] +features = ["python", "bioformats_java", "tiffwrite"] module-name = "ndbioimage.ndbioimage_rs" include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"] strip = true diff --git a/src/py.rs b/src/py.rs index 4728bb6..11ecee5 100644 --- a/src/py.rs +++ b/src/py.rs @@ -70,6 +70,7 @@ impl From for PyErr { struct PyView { view: View, dtype: PixelType, + #[serde(skip)] ome: Ome, index: usize, } @@ -157,9 +158,12 @@ impl PyView { #[pyo3(signature = (path, dtype = None, axes = "cztyx", reader = None))] fn new<'py>( py: Python<'py>, - #[gen_stub(override_type(type_repr="str | pathlib.Path | View | bytes", imports=("pathlib")))] + #[gen_stub( + override_type(type_repr="str | pathlib.Path | View | bytes", imports=("pathlib")) + )] path: Bound<'py, PyAny>, - #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] + #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing") + ))] dtype: Option>, axes: &str, reader: Option<&str>, @@ -167,7 +171,9 @@ impl PyView { if path.is_instance_of::() { Ok(path.cast_into::()?.extract::()?) } else if path.is_instance_of::() { - Ok(from_bytes(&path.extract::>()?).map_err(Error::from)?) + let mut pyview: Self = from_bytes(&path.extract::>()?).map_err(Error::from)?; + pyview.ome = pyview.view.metadata()?; + Ok(pyview) } else { let builtins = PyModule::import(py, "builtins")?; let path = PathBuf::from( @@ -209,7 +215,9 @@ impl PyView { #[staticmethod] fn get_positions<'py>( py: Python, - #[gen_stub(override_type(type_repr="str | pathlib.Path | View | bytes", imports=("pathlib")))] + #[gen_stub( + override_type(type_repr="str | pathlib.Path | View | bytes", imports=("pathlib")) + )] path: Bound<'py, PyAny>, ) -> PyResult> { Self::get_available_series(py, path, None) @@ -337,7 +345,8 @@ impl PyView { fn as_type( &self, py: Python<'_>, - #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] + #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing") + ))] dtype: Bound<'_, PyAny>, ) -> PyResult { let np = PyModule::import(py, "numpy")?; @@ -356,7 +365,8 @@ impl PyView { fn astype( &self, py: Python<'_>, - #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] + #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing") + ))] dtype: Bound<'_, PyAny>, ) -> PyResult { self.as_type(py, dtype) @@ -510,7 +520,8 @@ impl PyView { fn __array__<'py>( &self, py: Python<'py>, - #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] + #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing") + ))] dtype: Option>, copy: Option, ) -> PyResult> { @@ -1233,7 +1244,8 @@ impl PyView { #[pyo3(signature = (axes = None))] fn transpose( &self, - #[gen_stub(override_type(type_repr="typing.Optional[typing.Sequence[int | str]]", imports=("typing")))] + #[gen_stub(override_type(type_repr="typing.Optional[typing.Sequence[int | str]]", imports=("typing") + ))] axes: Option>>, ) -> PyResult { let view = if let Some(axes) = axes { @@ -1316,7 +1328,8 @@ impl PyView { /// get the maximum overall or along a given axis #[allow(clippy::too_many_arguments)] #[gen_stub(skip)] - #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=None, r#where=true), text_signature = "axis: str | int")] + #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=None, r#where=true), text_signature = "axis: str | int" + )] fn max<'py>( &self, py: Python<'py>, @@ -1369,7 +1382,8 @@ impl PyView { /// get the minimum overall or along a given axis #[allow(clippy::too_many_arguments)] #[gen_stub(skip)] - #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=Some(0), r#where=true), text_signature = "axis: str | int")] + #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=Some(0), r#where=true), text_signature = "axis: str | int" + )] fn min<'py>( &self, py: Python<'py>, @@ -1420,7 +1434,8 @@ impl PyView { } #[gen_stub(skip)] - #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, *, r#where=true), text_signature = "axis: str | int")] + #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, *, r#where=true), text_signature = "axis: str | int" + )] fn mean<'py>( &self, py: Python<'py>, @@ -1459,7 +1474,8 @@ impl PyView { /// get the sum overall or along a given axis #[allow(clippy::too_many_arguments)] #[gen_stub(skip)] - #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=Some(0), r#where=true), text_signature = "axis: str | int")] + #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=Some(0), r#where=true), text_signature = "axis: str | int" + )] fn sum<'py>( &self, py: Python<'py>, @@ -1656,23 +1672,13 @@ impl PyView { py_view.view.path().to_owned() } else { let builtins = PyModule::import(py, "builtins")?; - let mut path = PathBuf::from( + PathBuf::from( builtins .getattr("str")? .call1((path,))? .cast_into::()? .extract::()?, - ); - if path.is_dir() { - for file in path.read_dir()?.flatten().sorted_by_key(|i| i.file_name()) { - let p = file.path(); - if file.path().is_file() && (p.extension() == Some("tif".as_ref())) { - path = p; - break; - } - } - } - path + ) }; Ok(DynReader::get_available_series_select_reader(path, reader)?) @@ -1693,23 +1699,13 @@ impl PyView { py_view.view.path().to_owned() } else { let builtins = PyModule::import(py, "builtins")?; - let mut path = PathBuf::from( + PathBuf::from( builtins .getattr("str")? .call1((path,))? .cast_into::()? .extract::()?, - ); - if path.is_dir() { - for file in path.read_dir()?.flatten().sorted_by_key(|i| i.file_name()) { - let p = file.path(); - if file.path().is_file() && (p.extension() == Some("tif".as_ref())) { - path = p; - break; - } - } - } - path + ) }; Ok(DynReader::get_available_positions_select_reader( path, series, reader, @@ -1758,7 +1754,8 @@ impl PyView { #[cfg(feature = "movie")] #[allow(clippy::too_many_arguments)] - #[pyo3(signature = (file, speed = 1.0, brightness = None, scale = 1.0, colors = None, overwrite = false, register = false, no_scaling = false))] + #[pyo3(signature = (file, speed = 1.0, brightness = None, scale = 1.0, colors = None, overwrite = false, register = false, no_scaling = false) + )] fn save_as_movie( &self, file: PathBuf, @@ -1813,7 +1810,8 @@ submit! { #[allow(clippy::too_many_arguments)] #[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")] #[pyfunction] -#[pyo3(signature = (files_in, files_out, operations = None, colors = None, overwrite = false, bar = true, message = None))] +#[pyo3(signature = (files_in, files_out, operations = None, colors = None, overwrite = false, bar = true, message = None) +)] fn batch_to_tiff( py: Python, files_in: Vec, @@ -1919,7 +1917,8 @@ impl PyShape { ) } - #[gen_stub(override_return_type(type_repr="typing.Optional[int | list[int]]", imports=("typing")))] + #[gen_stub(override_return_type(type_repr="typing.Optional[int | list[int]]", imports=("typing") + ))] fn __getitem__<'py>( &self, py: Python<'py>, diff --git a/src/readers/bioformats_java.rs b/src/readers/bioformats_java.rs index 1cab8cd..a44ba65 100644 --- a/src/readers/bioformats_java.rs +++ b/src/readers/bioformats_java.rs @@ -7,6 +7,7 @@ use std::path::{Path, PathBuf}; pub use crate::readers::{ArrayT, PixelType, Reader}; use crate::readers::{DynReader, Frame, Shape}; +use itertools::Itertools; use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; use std::cell::OnceCell; use std::collections::HashSet; @@ -461,6 +462,22 @@ impl Drop for BioFormatsJavaReader { } } +fn find_tiff(path: PathBuf) -> Result, Error> { + if let Some(ext) = path.extension() + && path.is_file() + && (["tif", "tiff"].contains(&ext.to_string_lossy().to_lowercase().as_str())) + { + return Ok(Some(path)); + } else if path.is_dir() { + for file in path.read_dir()?.flatten().sorted_by_key(|i| i.file_name()) { + if let Ok(Some(file)) = find_tiff(file.path()) { + return Ok(Some(file)); + } + } + } + Ok(None) +} + impl Reader for BioFormatsJavaReader { /// Create a new reader for the image file at a path, and open series #. fn new

(path: P, series: usize, _position: usize) -> Result @@ -470,13 +487,10 @@ impl Reader for BioFormatsJavaReader { DebugTools::set_root_level("ERROR")?; let mut path = path.as_ref().to_path_buf(); if path.is_dir() { - for file in path.read_dir()?.flatten() { - let p = file.path(); - if file.path().is_file() && (p.extension() == Some("tif".as_ref())) { - path = p; - break; - } - } + let orig = path.clone(); + path = find_tiff(path)?.ok_or_else(|| { + Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string()) + })?; } let mut new = BioFormatsJavaReader { reader: ThreadLocal::default(), @@ -549,9 +563,16 @@ impl Reader for BioFormatsJavaReader { P: AsRef, { DebugTools::set_root_level("ERROR")?; + let mut path = path.as_ref().to_path_buf(); + if path.is_dir() { + let orig = path.clone(); + path = find_tiff(path)?.ok_or_else(|| { + Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string()) + })?; + } let new = BioFormatsJavaReader { reader: ThreadLocal::default(), - path: path.as_ref().to_path_buf(), + path, series: 0, shape: Shape::default(), pixel_type: PixelType::I8, @@ -594,7 +615,8 @@ mod tests { metadata_e: "czi/YTL1849A131_2023_05_04__13_36_36.czi", metadata_f: "czi/EU_UV_t=1-01.czi", metadata_g: "tiffseq/4-Pos_001_002/img_000000000_Cy3-Cy3_filter_000.tif", - metadata_h: "tiffseq/20-Pos_005_005/img_000000000_Cy3-Cy3_filter_000.tif" + metadata_h: "tiffseq/20-Pos_005_005/img_000000000_Cy3-Cy3_filter_000.tif", + metadata_i: "tiffseq/YTL1841B2-2-1_1hr_DMSO_galinduction_1", } #[test] diff --git a/src/view.rs b/src/view.rs index a484705..5673550 100644 --- a/src/view.rs +++ b/src/view.rs @@ -835,7 +835,7 @@ impl View { .collect(); for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) { if let Some(idx) = ax_out.remove(ax) { - for (_, i) in ax_out.iter_mut() { + for i in ax_out.values_mut() { if *i > idx { *i -= 1; } diff --git a/tests/test_open.py b/tests/test_open.py index 3e01d8e..4d76d8e 100644 --- a/tests/test_open.py +++ b/tests/test_open.py @@ -10,7 +10,9 @@ from ndbioimage import Imread "file", [ file - for file in (Path(__file__).parent / "files").iterdir() + for path in (Path(__file__).parent / "files").iterdir() + if path.is_dir() and path.name != "czi_xml" + for file in path.iterdir() if not file.suffix == ".pzl" ], ) diff --git a/tests/test_slicing.py b/tests/test_slicing.py index 3fa9a57..dc77594 100644 --- a/tests/test_slicing.py +++ b/tests/test_slicing.py @@ -18,7 +18,7 @@ def array(): @pytest.fixture() def image(array): with tempfile.TemporaryDirectory() as folder: - file = Path(folder) / "test.tif" + file = Path(folder) / "tiff" / "test.tif" tiffwrite(file, array, "yxczt") with Imread(file, axes="yxczt") as im: yield im diff --git a/tests/test_ufuncs.py b/tests/test_ufuncs.py index a10e2ae..96bcfdc 100644 --- a/tests/test_ufuncs.py +++ b/tests/test_ufuncs.py @@ -17,7 +17,7 @@ def array(): @pytest.fixture() def image(array): with tempfile.TemporaryDirectory() as folder: - file = Path(folder) / "test.tif" + file = Path(folder) / "tiff" / "test.tif" tiffwrite(file, array, "yxczt") with Imread(file, axes="yxczt") as im: yield im