use crate::axes::{Axis, Shape}; use crate::error::Error; use crate::metadata::Metadata; #[cfg(feature = "movie")] use crate::movie::MovieOptions; use crate::readers::{DynReader, PixelType, Reader}; use crate::view::{Item, View}; use itertools::Itertools; use ndarray::{Ix0, Ix1, IxDyn, SliceInfoElem}; use numpy::{ AllowTypeChange, IntoPyArray, PyArray, PyArrayDescr, PyArrayLike0, PyArrayLike1, PyArrayMethods, dtype, }; use ome_metadata::Ome; use postcard::{from_bytes, to_stdvec}; use pyo3::IntoPyObjectExt; use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{ PyBytes, PyEllipsis, PyInt, PyList, PyNone, PySlice, PySliceMethods, PyString, PyTuple, }; use pyo3_stub_gen::derive::*; use pyo3_stub_gen::inventory::submit; use pyo3_stub_gen::{StubGenConfig, StubInfo}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::path::PathBuf; impl From for PyErr { fn from(err: crate::error::Error) -> PyErr { color_eyre::eyre::Report::from(err).into() } } /// class to read image files, while taking good care of important metadata, /// currently optimized for .czi files, but can open anything that bioformats can handle /// path: path to the image file /// optional: /// axes: order of axes, default: cztyx, but omitting any axes with lenght 1 /// dtype: datatype to be used when returning frames /// /// Examples: /// >> im = Imread('/path/to/file.image', axes='czt) /// >> im /// << shows summary /// >> im.shape /// << (15, 26, 1000, 1000) /// >> im.axes /// << 'ztyx' /// >> plt.imshow(im[1, 0]) /// << plots frame at position z=1, t=0 (python type indexing) /// >> plt.imshow(im[:, 0].max('z')) /// << plots max-z projection at t=0 /// >> im.pxsize /// << 0.09708737864077668 image-plane pixel size in um /// >> im.laserwavelengths /// << [642, 488] /// >> im.laserpowers /// << [0.02, 0.0005] in % /// /// TODO: argmax, argmin, nanmax, nanmin, nanmean, nansum, nanstd, nanvar, std, var #[gen_stub_pyclass] #[pyclass( subclass, from_py_object, name = "View", module = "ndbioimage.ndbioimage_rs" )] #[derive(Clone, Debug, Serialize, Deserialize)] struct PyView { view: View, dtype: PixelType, ome: Ome, index: usize, } unsafe impl Send for PyView {} unsafe impl Sync for PyView {} impl PyView { fn item(py: Python, view: View, dtype: PixelType) -> PyResult> { Ok(match dtype { PixelType::I8 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U8 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I16 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U16 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), }) } } #[gen_stub_pymethods] #[pymethods] impl PyView { /// new view on a file at path, open series #, open as dtype: (u)int(8/16/32) or float(32/64) #[new] #[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")))] path: Bound<'py, PyAny>, #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] dtype: Option>, axes: &str, reader: Option<&str>, ) -> PyResult { 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)?) } else { let builtins = PyModule::import(py, "builtins")?; let path = PathBuf::from( builtins .getattr("str")? .call1((path,))? .cast_into::()? .extract::()?, ); let axes = axes .chars() .map(|a| a.to_string().parse().map_err(Error::from)) .collect::, Error>>()?; let view = if let Some(reader) = reader { DynReader::from_path_select_reader(&path, reader)?.view() } else { View::<_, DynReader>::from_path(&path)? } .permute_axes_dyn(&axes)?; let dtype = if let Some(dtype) = dtype { let np = PyModule::import(py, "numpy")?; let dt = np.getattr("dtype")?.call1((&dtype,))?; let name = dt.getattr("name")?; let dtype_str = name.extract::()?; dtype_str.parse()? } else { PixelType::U16 }; let ome = view.metadata()?; Ok(Self { view, dtype, ome, index: 0, }) } } #[staticmethod] fn get_positions<'py>( py: Python, #[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) } /// only remains for backwards compatibility #[staticmethod] fn kill_vm() {} #[getter] fn reader_name(&self) -> String { self.view.reader_name() } #[allow(unused_variables)] fn reshape<'py>(&self, order: &str, copy: bool) -> PyResult> { todo!() } #[allow(unused_variables)] #[pyo3(signature = (channels = true, drift = false, file = None, bead_files = None))] fn with_transform<'py>( &self, channels: bool, drift: bool, file: Option>, bead_files: Option>, ) -> PyResult { todo!() } #[getter] fn get_transform(&self) -> PyResult<()> { todo!() } #[gen_stub(override_return_type(type_repr="numpy.ndarray | int | float", imports=("numpy")))] fn squeeze<'py>(&self, py: Python<'py>) -> PyResult> { let view = self.view.squeeze()?; if view.ndim() == 0 { Ok(match self.dtype { PixelType::I8 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U8 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I16 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U16 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::I128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::U128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F32 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F64 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), PixelType::F128 => view .into_dimensionality::()? .item::()? .into_pyobject(py)? .into_any(), }) } else { PyView { view, dtype: self.dtype, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } } /// close the file: does nothing as this is handled automatically fn close(&self) -> PyResult<()> { Ok(()) } /// change the data type of the view: (u)int(8/16/32) or float(32/64) fn as_type( &self, py: Python<'_>, #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] dtype: Bound<'_, PyAny>, ) -> PyResult { let np = PyModule::import(py, "numpy")?; let dt = np.getattr("dtype")?.call1((&dtype,))?; let name = dt.getattr("name")?; let dtype_str = name.extract::()?; Ok(PyView { view: self.view.clone(), dtype: dtype_str.parse()?, ome: self.ome.clone(), index: 0, }) } /// change the data type of the view: (u)int(8/16/32) or float(32/64) fn astype( &self, py: Python<'_>, #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] dtype: Bound<'_, PyAny>, ) -> PyResult { self.as_type(py, dtype) } /// slice the view and return a new view or a single number fn __getitem__<'py>( &self, py: Python<'py>, n: Bound<'py, PyAny>, ) -> PyResult> { let slice: Vec<_> = if n.is_instance_of::() { n.cast_into::()?.into_iter().collect() } else if n.is_instance_of::() { n.cast_into::()?.into_iter().collect() } else { vec![n] }; let mut new_slice = Vec::new(); let mut ellipsis = None; let shape = self.view.shape(); for (i, (s, t)) in slice.iter().zip(shape.iter()).enumerate() { if s.is_none() { new_slice.push(SliceInfoElem::Slice { start: 0, end: None, step: 1, }); } else if s.is_instance_of::() { new_slice.push(SliceInfoElem::Index(s.cast::()?.extract::()?)); } else if s.is_instance_of::() { let u = s.cast::()?.indices(*t as isize)?; new_slice.push(SliceInfoElem::Slice { start: u.start, end: Some(u.stop), step: u.step, }); } else if s.is_instance_of::() { if ellipsis.is_some() { return Err(PyErr::new::( "cannot have more than one ellipsis".to_string(), )); } let _ = ellipsis.insert(i); } else if let Ok(arr_like) = s.extract::>() { let pyarr: &Bound> = &arr_like; let mut index = *pyarr.readonly().as_array().into_scalar(); let index0 = index; if index < 0 { index += *t as isize; } if (index < 0) || (index >= *t as isize) { return Err(PyIndexError::new_err(format!( "index {} is out of bounds for axis {} with size {}", index0, i, t ))); } new_slice.push(SliceInfoElem::Index(index)); } else if let Ok(arr_like) = s.extract::>() { let pyarr: &Bound> = &arr_like; let read = pyarr.readonly(); let mut indices = read.as_array().to_vec(); for index in indices.iter_mut() { let index0 = *index; if *index < 0 { *index += *t as isize; } if (*index < 0) || (*index >= *t as isize) { return Err(PyIndexError::new_err(format!( "index {} is out of bounds for axis {} with size {}", index0, i, t ))); } } if indices.is_empty() { new_slice.push(SliceInfoElem::Slice { start: 0, end: Some(0), step: 1, }) } else { let d = indices .windows(2) .map(|i| i[1] - i[0]) .collect::>(); if d.is_empty() { let index = indices[0]; new_slice.push(SliceInfoElem::Slice { start: index, end: Some(index + 1), step: 1, }); } else if d.len() == 1 { new_slice.push(SliceInfoElem::Slice { start: indices[0], end: indices.last().map(|j| j + 1), step: d.into_iter().collect::>()[0], }); } else { return Err(PyValueError::new_err("indices array must regularly spaced")); } }; } else { return Err(PyValueError::new_err(format!( "cannot convert {:?} to slice", s ))); } } if new_slice.len() > shape.len() { return Err(PyValueError::new_err(format!( "got more indices ({}) than dimensions ({})", new_slice.len(), shape.len() ))); } while new_slice.len() < shape.len() { if let Some(i) = ellipsis { new_slice.insert( i, SliceInfoElem::Slice { start: 0, end: None, step: 1, }, ) } else { new_slice.push(SliceInfoElem::Slice { start: 0, end: None, step: 1, }) } } let view = self.view.slice(new_slice.as_slice())?; if view.ndim() == 0 { Self::item(py, view, self.dtype) } else { PyView { view, dtype: self.dtype, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } } #[allow(unused_variables)] #[pyo3(signature = (dtype = None, copy = None))] fn __array__<'py>( &self, py: Python<'py>, #[gen_stub(override_type(type_repr = "numpy.typing.DTypeLike", imports=("numpy", "numpy.typing")))] dtype: Option>, copy: Option, ) -> PyResult> { if let Some(dtype) = dtype { self.as_type(py, dtype)?.as_array(py) } else { self.as_array(py) } } fn __contains__(&self, _item: Bound) -> PyResult { Err(PyNotImplementedError::new_err("contains not implemented")) } fn __lt__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("less")?.call1((&a, &b)) } fn __le__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("less_equal")?.call1((&a, &b)) } fn __eq__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("equal")?.call1((&a, &b)) } fn __ne__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("not_equal")?.call1((&a, &b)) } fn __gt__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("greater")?.call1((&a, &b)) } fn __ge__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("greater_equal")?.call1((&a, &b)) } fn __add__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("add")?.call1((&a, &b)) } fn __radd__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("add")?.call1((&a, &b)) } fn __sub__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("subtract")?.call1((&a, &b)) } fn __rsub__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("subtract")?.call1((&a, &b)) } fn __mul__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("multiply")?.call1((&a, &b)) } fn __rmul__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("multiply")?.call1((&a, &b)) } fn __truediv__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("true_divide")?.call1((&a, &b)) } fn __rtruediv__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("true_divide")?.call1((&a, &b)) } fn __floordiv__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("floor_divide")?.call1((&a, &b)) } fn __rfloordiv__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("floor_divide")?.call1((&a, &b)) } fn __mod__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("remainder")?.call1((&a, &b)) } fn __rmod__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("remainder")?.call1((&a, &b)) } #[gen_stub(skip)] fn __pow__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, r#mod: Option>, ) -> PyResult> { if r#mod.is_some() { return Err(PyNotImplementedError::new_err( "cannot use pow or ** on View with mod != None", )); } let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("power")?.call1((&a, &b)) } #[gen_stub(skip)] fn __rpow__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, r#mod: Option>, ) -> PyResult> { if r#mod.is_some() { return Err(PyNotImplementedError::new_err( "cannot use pow or ** on View with mod != None", )); } let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("power")?.call1((&a, &b)) } fn __matmul__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("matmul")?.call1((&a, &b)) } fn __rmatmul__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("matmul")?.call1((&a, &b)) } fn __and__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_and")?.call1((&a, &b)) } fn __rand__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_and")?.call1((&a, &b)) } fn __or__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_or")?.call1((&a, &b)) } fn __ror__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_or")?.call1((&a, &b)) } fn __xor__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_xor")?.call1((&a, &b)) } fn __rxor__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("bitwise_xor")?.call1((&a, &b)) } fn __lshift__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("left_shift")?.call1((&a, &b)) } fn __rlshift__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("left_shift")?.call1((&a, &b)) } fn __rshift__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; let b = np.getattr("asarray")?.call1((&other,))?; np.getattr("right_shift")?.call1((&a, &b)) } fn __rrshift__<'py>( &self, py: Python<'py>, other: Bound<'py, PyAny>, ) -> PyResult> { let np = PyModule::import(py, "numpy")?; let b = self.as_array(py)?; let a = np.getattr("asarray")?.call1((&other,))?; np.getattr("right_shift")?.call1((&a, &b)) } fn __neg__<'py>(&self, py: Python<'py>) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; np.getattr("negative")?.call1((&a,)) } fn __pos__<'py>(&self, py: Python<'py>) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; np.getattr("positive")?.call1((&a,)) } fn __abs__<'py>(&self, py: Python<'py>) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; np.getattr("absolute")?.call1((&a,)) } fn __invert__<'py>(&self, py: Python<'py>) -> PyResult> { let np = PyModule::import(py, "numpy")?; let a = self.as_array(py)?; np.getattr("invert")?.call1((&a,)) } fn __enter__<'py>(slf: PyRef<'py, Self>) -> PyResult> { Ok(slf) } #[allow(unused_variables)] #[pyo3(signature = (exc_type=None, exc_val=None, exc_tb=None))] fn __exit__( &self, exc_type: Option>, exc_val: Option>, exc_tb: Option>, ) -> PyResult<()> { self.close() } pub(crate) fn __getnewargs__(&self) -> PyResult<(Vec,)> { Ok((to_stdvec(self).map_err(Error::from)?,)) } fn __copy__(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype, ome: self.ome.clone(), index: 0, } } fn __deepcopy__(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype, ome: self.ome.clone(), index: 0, } } fn copy(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype, ome: self.ome.clone(), index: 0, } } fn __iter__(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype, ome: self.ome.clone(), index: 0, } } fn __next__<'py>(&mut self, py: Python<'py>) -> PyResult>> { let shape = self.view.shape(); if shape.is_empty() || (self.index == shape[0]) { self.index = 0; Ok(None) } else { let mut new_slice = vec![SliceInfoElem::Index(self.index as isize)]; self.index += 1; for _ in 1..shape.len() { new_slice.push(SliceInfoElem::Slice { start: 0, end: None, step: 1, }) } let view = self.view.slice(new_slice.as_slice())?; Some(if view.ndim() == 0 { Self::item(py, view, self.dtype) } else { PyView { view, dtype: self.dtype, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) }) .transpose() } } fn __len__(&self) -> PyResult { Ok(self.view.len()) } fn __repr__(&self) -> PyResult { Ok(self.view.summary()?) } fn __str__(&self) -> PyResult { Ok(self.view.path().display().to_string()) } /// retrieve a single frame at czt, sliced accordingly fn get_frame<'py>( &self, py: Python<'py>, c: isize, z: isize, t: isize, ) -> PyResult> { Ok(match self.dtype { PixelType::I8 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::U8 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::I16 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::U16 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::I32 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::U32 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::F32 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::F64 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::I64 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::U64 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::I128 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::U128 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), PixelType::F128 => self .view .get_frame::(c, z, t)? .into_pyarray(py) .into_any(), }) } fn flatten<'py>(&self, py: Python<'py>) -> PyResult> { Ok(match self.dtype { PixelType::I8 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::U8 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::I16 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::U16 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::I32 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::U32 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::F32 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::F64 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::I64 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::U64 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::I128 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::U128 => self.view.flatten::()?.into_pyarray(py).into_any(), PixelType::F128 => self.view.flatten::()?.into_pyarray(py).into_any(), }) } fn to_bytes(&self) -> PyResult> { Ok(match self.dtype { PixelType::I8 => self.view.to_bytes::()?, PixelType::U8 => self.view.to_bytes::()?, PixelType::I16 => self.view.to_bytes::()?, PixelType::U16 => self.view.to_bytes::()?, PixelType::I32 => self.view.to_bytes::()?, PixelType::U32 => self.view.to_bytes::()?, PixelType::F32 => self.view.to_bytes::()?, PixelType::F64 => self.view.to_bytes::()?, PixelType::I64 => self.view.to_bytes::()?, PixelType::U64 => self.view.to_bytes::()?, PixelType::I128 => self.view.to_bytes::()?, PixelType::U128 => self.view.to_bytes::()?, PixelType::F128 => self.view.to_bytes::()?, }) } fn tobytes(&self) -> PyResult> { self.to_bytes() } /// retrieve the ome metadata as an XML string #[gen_stub(skip)] #[getter] fn get_ome(&self) -> Ome { self.ome.clone() } /// the file path #[getter] fn path(&self) -> PyResult { Ok(self.view.path().to_owned()) } /// the series in the file #[getter] fn series(&self) -> PyResult { Ok(self.view.series()) } /// the axes in the view #[getter] fn axes(&self) -> String { self.view.axes().iter().map(|a| format!("{:?}", a)).join("") } /// the shape of the view #[getter] fn shape(&self) -> PyShape { PyShape { inner: self.view.shape(), } } #[getter] fn slice(&self) -> PyResult> { Ok(self .view .get_slice() .iter() .map(|s| format!("{:#?}", s)) .collect()) } /// the number of pixels in the view #[getter] fn size(&self) -> usize { self.view.size() } /// the number of dimensions in the view #[getter] fn ndim(&self) -> usize { self.view.ndim() } /// find the position of an axis fn get_ax( &self, #[gen_stub(override_type(type_repr = "int | str"))] axis: Bound, ) -> PyResult { if axis.is_instance_of::() { let axis = axis .cast_into::()? .extract::()? .parse::() .map_err(Error::from)?; self.view .axes() .iter() .position(|a| *a == axis) .ok_or_else(|| { PyErr::new::(format!("cannot find axis {:?}", axis)) }) } else if axis.is_instance_of::() { Ok(axis.cast_into::()?.extract::()?) } else { Err(PyErr::new::( "cannot convert to axis".to_string(), )) } } /// swap two axes fn swap_axes( &self, #[gen_stub(override_type(type_repr = "int | str"))] ax0: Bound, #[gen_stub(override_type(type_repr = "int | str"))] ax1: Bound, ) -> PyResult { let ax0 = self.get_ax(ax0)?; let ax1 = self.get_ax(ax1)?; let view = self.view.swap_axes(ax0, ax1)?; Ok(PyView { view, dtype: self.dtype, ome: self.ome.clone(), index: 0, }) } /// permute the order of the axes #[pyo3(signature = (axes = None))] fn transpose( &self, #[gen_stub(override_type(type_repr="typing.Optional[typing.Sequence[int | str]]", imports=("typing")))] axes: Option>>, ) -> PyResult { let view = if let Some(axes) = axes { let ax = axes .into_iter() .map(|a| self.get_ax(a)) .collect::, _>>()?; self.view.permute_axes(&ax)? } else { self.view.transpose()? }; Ok(PyView { view, dtype: self.dtype, ome: self.ome.clone(), index: 0, }) } #[allow(non_snake_case)] #[getter] fn T(&self) -> PyResult { self.transpose(None) } /// collect data into a numpy array fn as_array<'py>(&self, py: Python<'py>) -> PyResult> { Ok(match self.dtype { PixelType::I8 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::U8 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::I16 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::U16 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::I32 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::U32 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::F32 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::F64 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::I64 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::U64 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::I128 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::U128 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), PixelType::F128 => self.view.as_array_dyn::()?.into_pyarray(py).into_any(), }) } #[gen_stub(override_return_type(type_repr = "numpy.dtype", imports=("numpy")))] #[getter] fn get_dtype<'py>(&self, py: Python<'py>) -> PyResult> { match self.dtype { PixelType::I8 => Ok(dtype::(py)), PixelType::U8 => Ok(dtype::(py)), PixelType::I16 => Ok(dtype::(py)), PixelType::U16 => Ok(dtype::(py)), PixelType::I32 => Ok(dtype::(py)), PixelType::U32 => Ok(dtype::(py)), PixelType::F32 => Ok(dtype::(py)), PixelType::F64 => Ok(dtype::(py)), PixelType::I64 => Ok(dtype::(py)), PixelType::U64 => Ok(dtype::(py)), PixelType::I128 => Err(PyTypeError::new_err( "type is i128, but this cannot be represented by numpy.dtype", )), PixelType::U128 => Err(PyTypeError::new_err( "type is u128, but this cannot be represented by numpy.dtype", )), PixelType::F128 => Ok(dtype::(py)), } } #[gen_stub(skip)] #[setter] fn set_dtype(&mut self, py: Python, dtype: Bound<'_, PyAny>) -> PyResult<()> { let np = PyModule::import(py, "numpy")?; let dt = np.getattr("dtype")?.call1((&dtype,))?; let name = dt.getattr("name")?; let dtype_str = name.extract::()?; self.dtype = dtype_str.parse()?; Ok(()) } /// 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")] fn max<'py>( &self, py: Python<'py>, axis: Option>, dtype: Option>, out: Option>, keepdims: bool, initial: Option, r#where: bool, ) -> PyResult> { if let Some(i) = initial && i != 0 { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if dtype.is_some() || out.is_some() || keepdims || !r#where { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if let Some(axis) = axis { PyView { dtype: self.dtype, view: self.view.max_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } else { Ok(match self.dtype { PixelType::I8 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::U8 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::I16 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::U16 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::I32 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::U32 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::F32 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::F64 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::I64 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::U64 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::I128 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::U128 => self.view.max::()?.into_pyobject(py)?.into_any(), PixelType::F128 => self.view.max::()?.into_pyobject(py)?.into_any(), }) } } /// 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")] fn min<'py>( &self, py: Python<'py>, axis: Option>, dtype: Option>, out: Option>, keepdims: bool, initial: Option, r#where: bool, ) -> PyResult> { if let Some(i) = initial && i != 0 { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if dtype.is_some() || out.is_some() || keepdims || !r#where { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if let Some(axis) = axis { PyView { dtype: self.dtype, view: self.view.min_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } else { Ok(match self.dtype { PixelType::I8 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::U8 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::I16 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::U16 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::I32 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::U32 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::F32 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::F64 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::I64 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::U64 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::I128 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::U128 => self.view.min::()?.into_pyobject(py)?.into_any(), PixelType::F128 => self.view.min::()?.into_pyobject(py)?.into_any(), }) } } #[gen_stub(skip)] #[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>, axis: Option>, dtype: Option>, out: Option>, keepdims: bool, r#where: bool, ) -> PyResult> { if dtype.is_some() || out.is_some() || keepdims || !r#where { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if let Some(axis) = axis { let dtype = if let PixelType::F32 = self.dtype { PixelType::F32 } else { PixelType::F64 }; PyView { dtype, view: self.view.mean_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } else { Ok(match self.dtype { PixelType::F32 => self.view.mean::()?.into_pyobject(py)?.into_any(), _ => self.view.mean::()?.into_pyobject(py)?.into_any(), }) } } /// 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")] fn sum<'py>( &self, py: Python<'py>, axis: Option>, dtype: Option>, out: Option>, keepdims: bool, initial: Option, r#where: bool, ) -> PyResult> { if let Some(i) = initial && i != 0 { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } if dtype.is_some() || out.is_some() || keepdims || !r#where { Err(Error::NotImplemented( "arguments beyond axis are not implemented".to_string(), ))?; } let dtype = match self.dtype { PixelType::I8 => PixelType::I16, PixelType::U8 => PixelType::U16, PixelType::I16 => PixelType::I32, PixelType::U16 => PixelType::U32, PixelType::I32 => PixelType::I64, PixelType::U32 => PixelType::U64, PixelType::F32 => PixelType::F32, PixelType::F64 => PixelType::F64, PixelType::I64 => PixelType::I128, PixelType::U64 => PixelType::U128, PixelType::I128 => PixelType::I128, PixelType::U128 => PixelType::U128, PixelType::F128 => PixelType::F128, }; if let Some(axis) = axis { PyView { dtype, view: self.view.sum_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), index: 0, } .into_bound_py_any(py) } else { Ok(match self.dtype { PixelType::F32 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::F64 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::I64 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::U64 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::I128 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::U128 => self.view.sum::()?.into_pyobject(py)?.into_any(), PixelType::F128 => self.view.sum::()?.into_pyobject(py)?.into_any(), _ => self.view.sum::()?.into_pyobject(py)?.into_any(), }) } } #[getter] fn z_stack(&self) -> PyResult { if let Some(s) = self.view.size_ax(Axis::Z) { Ok(s > 1) } else { Ok(false) } } /// backwards compatibility #[getter] fn zstack(&self) -> PyResult { if let Some(s) = self.view.size_ax(Axis::Z) { Ok(s > 1) } else { Ok(false) } } #[getter] fn time_series(&self) -> PyResult { if let Some(s) = self.view.size_ax(Axis::T) { Ok(s > 1) } else { Ok(false) } } /// backwards compatibility #[getter] fn timeseries(&self) -> PyResult { if let Some(s) = self.view.size_ax(Axis::T) { Ok(s > 1) } else { Ok(false) } } #[getter] fn pixel_size(&self) -> PyResult> { Ok(self.ome.pixel_size()?) } /// backwards compatibility #[getter] fn pxsize_um(&self) -> PyResult> { Ok(self.ome.pixel_size()?.map(|p| p / 1000.)) } /// backwards compatibility #[getter] fn deltaz_um(&self) -> PyResult> { Ok(self.ome.delta_z()?.map(|p| p / 1000.)) } #[getter] fn delta_z(&self) -> PyResult> { Ok(self.ome.delta_z()?) } #[getter] fn time_interval(&self) -> PyResult> { Ok(self.ome.time_interval()?) } /// backwards compatibility #[getter] fn timeinterval(&self) -> PyResult> { Ok(self.ome.time_interval()?) } fn exposure_time(&self, channel: usize) -> PyResult> { Ok(self.ome.exposure_time(channel)?) } /// backwards compatibility #[getter] fn exposuretime_s(&self) -> PyResult>> { Ok((0..self.view.shape().c) .map(|c| self.ome.exposure_time(c)) .collect::, Error>>()?) } fn binning(&self, channel: usize) -> Option { self.ome.binning(channel) } fn laser_wavelengths(&self, channel: usize) -> PyResult> { Ok(self.ome.laser_wavelengths(channel)?) } fn laser_power(&self, channel: usize) -> PyResult> { Ok(self.ome.laser_powers(channel)?) } #[getter] fn objective_name(&self) -> Option { self.ome.objective_name() } #[getter] fn magnification(&self) -> Option { self.ome.magnification() } #[getter] fn tube_lens_name(&self) -> Option { self.ome.tube_lens_name() } fn filter_set_name(&self, channel: usize) -> Option { self.ome.filter_set_name(channel) } fn gain(&self, channel: usize) -> Option { self.ome.gain(channel) } /// gives a helpful summary of the recorded experiment fn summary(&self) -> PyResult { Ok(self.view.summary()?) } /// get all series contained in the file #[staticmethod] #[pyo3(signature = (path, reader = None))] fn get_available_series<'py>( py: Python<'py>, #[gen_stub(override_type(type_repr="str | pathlib.Path | View", imports=("pathlib")))] path: Bound<'py, PyAny>, reader: Option<&str>, ) -> PyResult> { let path = if path.is_instance_of::() { let py_view: Self = path.cast_into::()?.extract::()?; py_view.view.path().to_owned() } else { let builtins = PyModule::import(py, "builtins")?; let mut path = 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)?) } /// get all series contained in the file #[staticmethod] #[pyo3(signature = (path, series, reader = None))] fn get_available_positions<'py>( py: Python<'py>, #[gen_stub(override_type(type_repr="str | pathlib.Path | View", imports=("pathlib")))] path: Bound<'py, PyAny>, series: usize, reader: Option<&str>, ) -> PyResult> { let path = if path.is_instance_of::() { let py_view: Self = path.cast_into::()?.extract::()?; py_view.view.path().to_owned() } else { let builtins = PyModule::import(py, "builtins")?; let mut path = 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, )?) } #[cfg(feature = "tiffwrite")] #[pyo3(signature = (file, colors = None, overwrite = false, bar = true))] fn save_as_tiff( &self, py: Python, file: PathBuf, colors: Option>, overwrite: bool, bar: bool, ) -> PyResult<()> { let bar = if bar { Some(crate::tiffwrite::get_bar( Some(0), Some("writing tiff file".to_string()), )) } else { None }; let options = crate::tiffwrite::TiffOptions::new(bar, None, colors.unwrap_or_default(), overwrite)?; py.detach(|| match self.dtype { PixelType::I8 => self.view.save_as_tiff_with_type::(file, &options), PixelType::U8 => self.view.save_as_tiff_with_type::(file, &options), PixelType::I16 => self.view.save_as_tiff_with_type::(file, &options), PixelType::U16 => self.view.save_as_tiff_with_type::(file, &options), PixelType::I32 => self.view.save_as_tiff_with_type::(file, &options), PixelType::U32 => self.view.save_as_tiff_with_type::(file, &options), PixelType::I64 => self.view.save_as_tiff_with_type::(file, &options), PixelType::U64 => self.view.save_as_tiff_with_type::(file, &options), PixelType::I128 => self.view.save_as_tiff_with_type::(file, &options), PixelType::U128 => self.view.save_as_tiff_with_type::(file, &options), PixelType::F32 => self.view.save_as_tiff_with_type::(file, &options), PixelType::F64 => self.view.save_as_tiff_with_type::(file, &options), PixelType::F128 => Err(Error::NotImplemented( "saving as f128 is not implemented".to_string(), )), })?; Ok(()) } #[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))] fn save_as_movie( &self, file: PathBuf, speed: f64, brightness: Option>, scale: f64, colors: Option>, overwrite: bool, register: bool, no_scaling: bool, ) -> PyResult<()> { let options = MovieOptions::new( speed, brightness.unwrap_or_default(), scale, colors.unwrap_or_default(), overwrite, register, no_scaling, )?; self.view.save_as_movie(file, &options)?; Ok(()) } } submit! { gen_methods_from_python! { r#" import typing import numpy.typing class PyView: def __pow__(other: View | numpy.typing.NDArray | int | float, mod: typing.Any = None, /) -> numpy.typing.NDArray: ... def __rpow__(other: View | numpy.typing.NDArray | int | float, mod: typing.Any = None, /) -> numpy.typing.NDArray: ... def max(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> View | numpy.typing.NDArray | int | float: """ Return the maximum along a given axis. Arguments beyond axis are not implemented """ def min(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> View | numpy.typing.NDArray | int | float: """ Return the minimum along a given axis. Arguments beyond axis are not implemented """ def mean(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False) -> View | numpy.typing.NDArray | int | float: """ Return the mean along a given axis. Arguments beyond axis are not implemented """ def sum(axis: int | str = None, dtype: numpy.typing.DTypeLike = None, out: typing.Any = None, keepdims: bool = False, initial: int | float = None, where: bool = True) -> View | numpy.typing.NDArray | int | float: """ Return the sum along a given axis. Arguments beyond axis are not implemented """ "# } } #[cfg(feature = "tiffwrite")] #[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))] fn batch_to_tiff( py: Python, files_in: Vec, files_out: Vec, operations: Option>, colors: Option>, overwrite: bool, bar: bool, message: Option, ) -> PyResult<()> { py.detach(|| { crate::tiffwrite::batch_to_tiff( &files_in, &files_out, operations, colors, overwrite, bar, message, ) })?; Ok(()) } #[gen_stub_pyclass] #[pyclass( subclass, from_py_object, frozen, name = "Shape", module = "ndbioimage.ndbioimage_rs" )] #[derive(Clone, Debug, Serialize, Deserialize)] struct PyShape { inner: Shape, } #[gen_stub_pymethods] #[pymethods] impl PyShape { #[new] #[pyo3(signature = (order, c = 1, z = 1, t = 1, y = 1, x = 1))] fn new(order: String, c: usize, z: usize, t: usize, y: usize, x: usize) -> PyResult { Ok(Self { inner: Shape { c, z, t, y, x, order: order .chars() .map(|c| c.to_uppercase().to_string().parse::()) .collect::, _>>() .map_err(Error::from)?, }, }) } #[getter] fn get_c(&self) -> usize { self.inner.c } #[getter] fn get_z(&self) -> usize { self.inner.z } #[getter] fn get_t(&self) -> usize { self.inner.t } #[getter] fn get_y(&self) -> usize { self.inner.y } #[getter] fn get_x(&self) -> usize { self.inner.x } fn __str__(&self) -> String { format!("{}", self.inner) } fn __repr__(&self) -> String { format!( "PyShape({}, {}, {}, {}, {})", self.inner.c, self.inner.z, self.inner.t, self.inner.x, self.inner.y ) } fn __getnewargs__(&self) -> (String, usize, usize, usize, usize, usize) { ( self.inner .order .iter() .map(|axis| format!("{}", axis)) .collect::>() .join(""), self.inner.c, self.inner.z, self.inner.t, self.inner.y, self.inner.x, ) } #[gen_stub(override_return_type(type_repr="typing.Optional[int | list[int]]", imports=("typing")))] fn __getitem__<'py>( &self, py: Python<'py>, #[gen_stub(override_type( type_repr = "str | int | None | Ellipsis | slice | list[int] | tuple[int]" ))] idx: Bound<'py, PyAny>, ) -> PyResult> { let idx = if idx.is_instance_of::() || idx.is_instance_of::() { vec![0, 1, 2, 3, 4] } else if idx.is_instance_of::() { let indices = idx.cast::()?.indices(5)?; if indices.step > 0 { (indices.start..indices.stop) .step_by(indices.step as usize) .map(|i| i as usize) .collect::>() } else { (indices.stop..indices.start) .step_by(-indices.step as usize) .map(|i| i as usize) .collect::>() } } else if idx.is_instance_of::() { idx.cast::()?.extract::>()? } else if idx.is_instance_of::() { idx.cast::()?.extract::>()? } else if idx.is_instance_of::() { let s = idx.cast::()?.extract::()?; s.to_uppercase() .chars() .map(|i| match i { 'C' => Ok(0), 'Z' => Ok(1), 'T' => Ok(2), 'Y' => Ok(3), 'X' => Ok(4), _ => Err(Error::Parse(s.to_string())), }) .collect::, _>>()? } else if idx.is_instance_of::() { vec![idx.cast::()?.extract::()?] } else { return Err(PyErr::new::("Unknown type")); }; let shape = [ self.inner.c, self.inner.z, self.inner.t, self.inner.y, self.inner.x, ]; let shape = idx.into_iter().map(|i| shape[i % 5]).collect::>(); if shape.is_empty() { Ok(PyNone::get(py).into_bound_py_any(py)?) } else if shape.len() == 1 { Ok(shape[0].into_bound_py_any(py)?) } else { Ok(shape.into_bound_py_any(py)?) } } fn to_list(&self) -> Vec { vec![ self.inner.c, self.inner.z, self.inner.t, self.inner.y, self.inner.x, ] } } pub(crate) fn ndbioimage_file() -> PathBuf { let file = Python::attach(|py| { py.import("ndbioimage") .unwrap() .filename() .unwrap() .to_string() }); PathBuf::from(file) } /// generates ndbioimage/__init__.pyi #[pyfunction] pub fn generate_stub(dest_path: String) -> PyResult<()> { Ok(StubInfo::from_project_root( "ndbioimage".to_string(), PathBuf::from(dest_path).join("py"), true, StubGenConfig::default(), )? .generate()?) } #[gen_stub_pyfunction(module = "ndbioimage.ndbioimage_rs")] #[pyfunction] fn main() -> PyResult<()> { Ok(crate::main::main(Some( std::env::args() .skip_while(|arg| !arg.ends_with("ndbioimage")) .collect(), ))?) } #[pymodule] #[pyo3(name = "ndbioimage_rs")] mod ndbioimage_rs { use pyo3::prelude::*; #[pymodule_export] use super::main; #[cfg(feature = "tiffwrite")] #[pymodule_export] use super::batch_to_tiff; #[pymodule_export] use super::generate_stub; #[pymodule_export] use super::PyView; #[pymodule_export] use super::PyShape; #[pymodule_export] use ome_metadata::py::ome_metadata; #[pymodule_init] fn init(_: &Bound<'_, PyModule>) -> PyResult<()> { let _ = color_eyre::install(); Ok(()) } }