- open tiff seq folders with bioformats_java by finding the first tiff recursively
- make PyView serializable by skipping the ome field - fix python tests
This commit is contained in:
+1
-1
@@ -35,7 +35,7 @@ ndbioimage_generate_stub = "ndbioimage:ndbioimage_generate_stub"
|
|||||||
|
|
||||||
[tool.maturin]
|
[tool.maturin]
|
||||||
python-source = "py"
|
python-source = "py"
|
||||||
features = ["python", "bioformats_java", "tiffwrite", "czi"]
|
features = ["python", "bioformats_java", "tiffwrite"]
|
||||||
module-name = "ndbioimage.ndbioimage_rs"
|
module-name = "ndbioimage.ndbioimage_rs"
|
||||||
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
include = ["py/ndbioimage/jassets/j4rs*", "py/ndbioimage/deps/libj4rs*"]
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ impl From<crate::error::Error> for PyErr {
|
|||||||
struct PyView {
|
struct PyView {
|
||||||
view: View<IxDyn, DynReader>,
|
view: View<IxDyn, DynReader>,
|
||||||
dtype: PixelType,
|
dtype: PixelType,
|
||||||
|
#[serde(skip)]
|
||||||
ome: Ome,
|
ome: Ome,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
@@ -157,9 +158,12 @@ impl PyView {
|
|||||||
#[pyo3(signature = (path, dtype = None, axes = "cztyx", reader = None))]
|
#[pyo3(signature = (path, dtype = None, axes = "cztyx", reader = None))]
|
||||||
fn new<'py>(
|
fn new<'py>(
|
||||||
py: Python<'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>,
|
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<Bound<'py, PyAny>>,
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
axes: &str,
|
axes: &str,
|
||||||
reader: Option<&str>,
|
reader: Option<&str>,
|
||||||
@@ -167,7 +171,9 @@ impl PyView {
|
|||||||
if path.is_instance_of::<Self>() {
|
if path.is_instance_of::<Self>() {
|
||||||
Ok(path.cast_into::<Self>()?.extract::<Self>()?)
|
Ok(path.cast_into::<Self>()?.extract::<Self>()?)
|
||||||
} else if path.is_instance_of::<PyBytes>() {
|
} else if path.is_instance_of::<PyBytes>() {
|
||||||
Ok(from_bytes(&path.extract::<Vec<u8>>()?).map_err(Error::from)?)
|
let mut pyview: Self = from_bytes(&path.extract::<Vec<u8>>()?).map_err(Error::from)?;
|
||||||
|
pyview.ome = pyview.view.metadata()?;
|
||||||
|
Ok(pyview)
|
||||||
} else {
|
} else {
|
||||||
let builtins = PyModule::import(py, "builtins")?;
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
let path = PathBuf::from(
|
let path = PathBuf::from(
|
||||||
@@ -209,7 +215,9 @@ impl PyView {
|
|||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn get_positions<'py>(
|
fn get_positions<'py>(
|
||||||
py: Python,
|
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>,
|
path: Bound<'py, PyAny>,
|
||||||
) -> PyResult<HashSet<usize>> {
|
) -> PyResult<HashSet<usize>> {
|
||||||
Self::get_available_series(py, path, None)
|
Self::get_available_series(py, path, None)
|
||||||
@@ -337,7 +345,8 @@ impl PyView {
|
|||||||
fn as_type(
|
fn as_type(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'_>,
|
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>,
|
dtype: Bound<'_, PyAny>,
|
||||||
) -> PyResult<Self> {
|
) -> PyResult<Self> {
|
||||||
let np = PyModule::import(py, "numpy")?;
|
let np = PyModule::import(py, "numpy")?;
|
||||||
@@ -356,7 +365,8 @@ impl PyView {
|
|||||||
fn astype(
|
fn astype(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'_>,
|
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>,
|
dtype: Bound<'_, PyAny>,
|
||||||
) -> PyResult<Self> {
|
) -> PyResult<Self> {
|
||||||
self.as_type(py, dtype)
|
self.as_type(py, dtype)
|
||||||
@@ -510,7 +520,8 @@ impl PyView {
|
|||||||
fn __array__<'py>(
|
fn __array__<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
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<Bound<'py, PyAny>>,
|
dtype: Option<Bound<'py, PyAny>>,
|
||||||
copy: Option<bool>,
|
copy: Option<bool>,
|
||||||
) -> PyResult<Bound<'py, PyAny>> {
|
) -> PyResult<Bound<'py, PyAny>> {
|
||||||
@@ -1233,7 +1244,8 @@ impl PyView {
|
|||||||
#[pyo3(signature = (axes = None))]
|
#[pyo3(signature = (axes = None))]
|
||||||
fn transpose(
|
fn transpose(
|
||||||
&self,
|
&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<Vec<Bound<PyAny>>>,
|
axes: Option<Vec<Bound<PyAny>>>,
|
||||||
) -> PyResult<Self> {
|
) -> PyResult<Self> {
|
||||||
let view = if let Some(axes) = axes {
|
let view = if let Some(axes) = axes {
|
||||||
@@ -1316,7 +1328,8 @@ impl PyView {
|
|||||||
/// get the maximum overall or along a given axis
|
/// get the maximum overall or along a given axis
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[gen_stub(skip)]
|
#[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>(
|
fn max<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
@@ -1369,7 +1382,8 @@ impl PyView {
|
|||||||
/// get the minimum overall or along a given axis
|
/// get the minimum overall or along a given axis
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[gen_stub(skip)]
|
#[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>(
|
fn min<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
@@ -1420,7 +1434,8 @@ impl PyView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gen_stub(skip)]
|
#[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>(
|
fn mean<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
@@ -1459,7 +1474,8 @@ impl PyView {
|
|||||||
/// get the sum overall or along a given axis
|
/// get the sum overall or along a given axis
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[gen_stub(skip)]
|
#[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>(
|
fn sum<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
@@ -1656,23 +1672,13 @@ impl PyView {
|
|||||||
py_view.view.path().to_owned()
|
py_view.view.path().to_owned()
|
||||||
} else {
|
} else {
|
||||||
let builtins = PyModule::import(py, "builtins")?;
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
let mut path = PathBuf::from(
|
PathBuf::from(
|
||||||
builtins
|
builtins
|
||||||
.getattr("str")?
|
.getattr("str")?
|
||||||
.call1((path,))?
|
.call1((path,))?
|
||||||
.cast_into::<PyString>()?
|
.cast_into::<PyString>()?
|
||||||
.extract::<String>()?,
|
.extract::<String>()?,
|
||||||
);
|
)
|
||||||
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)?)
|
Ok(DynReader::get_available_series_select_reader(path, reader)?)
|
||||||
@@ -1693,23 +1699,13 @@ impl PyView {
|
|||||||
py_view.view.path().to_owned()
|
py_view.view.path().to_owned()
|
||||||
} else {
|
} else {
|
||||||
let builtins = PyModule::import(py, "builtins")?;
|
let builtins = PyModule::import(py, "builtins")?;
|
||||||
let mut path = PathBuf::from(
|
PathBuf::from(
|
||||||
builtins
|
builtins
|
||||||
.getattr("str")?
|
.getattr("str")?
|
||||||
.call1((path,))?
|
.call1((path,))?
|
||||||
.cast_into::<PyString>()?
|
.cast_into::<PyString>()?
|
||||||
.extract::<String>()?,
|
.extract::<String>()?,
|
||||||
);
|
)
|
||||||
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(
|
Ok(DynReader::get_available_positions_select_reader(
|
||||||
path, series, reader,
|
path, series, reader,
|
||||||
@@ -1758,7 +1754,8 @@ impl PyView {
|
|||||||
|
|
||||||
#[cfg(feature = "movie")]
|
#[cfg(feature = "movie")]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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(
|
fn save_as_movie(
|
||||||
&self,
|
&self,
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
@@ -1813,7 +1810,8 @@ submit! {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")]
|
#[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")]
|
||||||
#[pyfunction]
|
#[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(
|
fn batch_to_tiff(
|
||||||
py: Python,
|
py: Python,
|
||||||
files_in: Vec<PathBuf>,
|
files_in: Vec<PathBuf>,
|
||||||
@@ -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>(
|
fn __getitem__<'py>(
|
||||||
&self,
|
&self,
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
pub use crate::readers::{ArrayT, PixelType, Reader};
|
pub use crate::readers::{ArrayT, PixelType, Reader};
|
||||||
use crate::readers::{DynReader, Frame, Shape};
|
use crate::readers::{DynReader, Frame, Shape};
|
||||||
|
use itertools::Itertools;
|
||||||
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -461,6 +462,22 @@ impl Drop for BioFormatsJavaReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_tiff(path: PathBuf) -> Result<Option<PathBuf>, 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 {
|
impl Reader for BioFormatsJavaReader {
|
||||||
/// Create a new reader for the image file at a path, and open series #.
|
/// Create a new reader for the image file at a path, and open series #.
|
||||||
fn new<P>(path: P, series: usize, _position: usize) -> Result<Self, Error>
|
fn new<P>(path: P, series: usize, _position: usize) -> Result<Self, Error>
|
||||||
@@ -470,13 +487,10 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
DebugTools::set_root_level("ERROR")?;
|
DebugTools::set_root_level("ERROR")?;
|
||||||
let mut path = path.as_ref().to_path_buf();
|
let mut path = path.as_ref().to_path_buf();
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
for file in path.read_dir()?.flatten() {
|
let orig = path.clone();
|
||||||
let p = file.path();
|
path = find_tiff(path)?.ok_or_else(|| {
|
||||||
if file.path().is_file() && (p.extension() == Some("tif".as_ref())) {
|
Error::FileDoesNotExist(orig.join("**").join("*.tif").display().to_string())
|
||||||
path = p;
|
})?;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let mut new = BioFormatsJavaReader {
|
let mut new = BioFormatsJavaReader {
|
||||||
reader: ThreadLocal::default(),
|
reader: ThreadLocal::default(),
|
||||||
@@ -549,9 +563,16 @@ impl Reader for BioFormatsJavaReader {
|
|||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
DebugTools::set_root_level("ERROR")?;
|
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 {
|
let new = BioFormatsJavaReader {
|
||||||
reader: ThreadLocal::default(),
|
reader: ThreadLocal::default(),
|
||||||
path: path.as_ref().to_path_buf(),
|
path,
|
||||||
series: 0,
|
series: 0,
|
||||||
shape: Shape::default(),
|
shape: Shape::default(),
|
||||||
pixel_type: PixelType::I8,
|
pixel_type: PixelType::I8,
|
||||||
@@ -594,7 +615,8 @@ mod tests {
|
|||||||
metadata_e: "czi/YTL1849A131_2023_05_04__13_36_36.czi",
|
metadata_e: "czi/YTL1849A131_2023_05_04__13_36_36.czi",
|
||||||
metadata_f: "czi/EU_UV_t=1-01.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_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]
|
#[test]
|
||||||
|
|||||||
+1
-1
@@ -835,7 +835,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
|||||||
.collect();
|
.collect();
|
||||||
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
for (ax, op) in self.operations.iter().skip(op_xy.len() + op_czt.len()) {
|
||||||
if let Some(idx) = ax_out.remove(ax) {
|
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 {
|
if *i > idx {
|
||||||
*i -= 1;
|
*i -= 1;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -10,7 +10,9 @@ from ndbioimage import Imread
|
|||||||
"file",
|
"file",
|
||||||
[
|
[
|
||||||
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"
|
if not file.suffix == ".pzl"
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def array():
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def image(array):
|
def image(array):
|
||||||
with tempfile.TemporaryDirectory() as folder:
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
file = Path(folder) / "test.tif"
|
file = Path(folder) / "tiff" / "test.tif"
|
||||||
tiffwrite(file, array, "yxczt")
|
tiffwrite(file, array, "yxczt")
|
||||||
with Imread(file, axes="yxczt") as im:
|
with Imread(file, axes="yxczt") as im:
|
||||||
yield im
|
yield im
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def array():
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def image(array):
|
def image(array):
|
||||||
with tempfile.TemporaryDirectory() as folder:
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
file = Path(folder) / "test.tif"
|
file = Path(folder) / "tiff" / "test.tif"
|
||||||
tiffwrite(file, array, "yxczt")
|
tiffwrite(file, array, "yxczt")
|
||||||
with Imread(file, axes="yxczt") as im:
|
with Imread(file, axes="yxczt") as im:
|
||||||
yield im
|
yield im
|
||||||
|
|||||||
Reference in New Issue
Block a user