use crate::axes::Axis; use crate::bioformats::download_bioformats; use crate::error::Error; use crate::metadata::Metadata; use crate::reader::{PixelType, Reader}; use crate::view::{Item, View}; use itertools::Itertools; use ndarray::{Ix0, IxDyn, SliceInfoElem}; use numpy::IntoPyArray; use ome_metadata::Ome; use pyo3::IntoPyObjectExt; use pyo3::exceptions::{PyNotImplementedError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyEllipsis, PyInt, PyList, PySlice, PySliceMethods, PyString, PyTuple}; use serde::{Deserialize, Serialize}; use serde_json::{from_str, to_string}; use std::path::PathBuf; use std::sync::Arc; impl From for PyErr { fn from(err: crate::error::Error) -> PyErr { PyErr::new::(err.to_string()) } } #[pyclass(module = "ndbioimage.ndbioimage_rs")] struct ViewConstructor; #[pymethods] impl ViewConstructor { #[new] fn new() -> Self { Self } fn __getnewargs__<'py>(&self, py: Python<'py>) -> Bound<'py, PyTuple> { PyTuple::empty(py) } #[staticmethod] fn __call__(state: String) -> PyResult { if let Ok(new) = from_str(&state) { Ok(new) } else { Err(PyErr::new::( "cannot parse state".to_string(), )) } } } #[pyclass(subclass, module = "ndbioimage.ndbioimage_rs")] #[pyo3(name = "View")] #[derive(Clone, Debug, Serialize, Deserialize)] struct PyView { view: View, dtype: PixelType, ome: Arc, } #[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, series = 0, dtype = "uint16", axes = "cztyx"))] fn new<'py>( py: Python<'py>, path: Bound<'py, PyAny>, series: usize, dtype: &str, axes: &str, ) -> PyResult { if path.is_instance_of::() { Ok(path.cast_into::()?.extract::()?) } 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() { let p = file.path(); if file.path().is_file() && (p.extension() == Some("tif".as_ref())) { path = p; break; } } } let axes = axes .chars() .map(|a| a.to_string().parse()) .collect::, Error>>()?; let reader = Reader::new(&path, series)?; let view = View::new_with_axes(Arc::new(reader), axes)?; let dtype = dtype.parse()?; let ome = Arc::new(view.get_ome()?); Ok(Self { view, dtype, ome }) } } 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.clone(), ome: self.ome.clone(), } .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, dtype: &str) -> PyResult { Ok(PyView { view: self.view.clone(), dtype: dtype.parse()?, ome: self.ome.clone(), }) } /// change the data type of the view: (u)int(8/16/32) or float(32/64) fn astype(&self, dtype: &str) -> PyResult { self.as_type(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_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 { return Err(PyErr::new::(format!( "cannot convert {:?} to slice", s ))); } } if new_slice.len() > shape.len() { return Err(PyErr::new::(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 { 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::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(), }) } else { PyView { view, dtype: self.dtype.clone(), ome: self.ome.clone(), } .into_bound_py_any(py) } } #[pyo3(signature = (dtype = None))] fn __array__<'py>(&self, py: Python<'py>, dtype: Option<&str>) -> PyResult> { if let Some(dtype) = dtype { self.as_type(dtype)?.as_array(py) } else { self.as_array(py) } } fn __contains__(&self, _item: Bound) -> PyResult { Err(PyNotImplementedError::new_err("contains not implemented")) } 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() } fn __reduce__(&self) -> PyResult<(ViewConstructor, (String,))> { if let Ok(s) = to_string(self) { Ok((ViewConstructor, (s,))) } else { Err(PyErr::new::( "cannot get state".to_string(), )) } } fn __copy__(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype.clone(), ome: self.ome.clone(), } } fn copy(&self) -> Self { Self { view: self.view.clone(), dtype: self.dtype.clone(), ome: self.ome.clone(), } } 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 fn get_ome_xml(&self) -> PyResult { Ok(self.view.get_ome_xml()?) } /// the file path #[getter] fn path(&self) -> PyResult { Ok(self.view.path.display().to_string()) } /// 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) -> Vec { 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 #[pyo3(text_signature = "axis: str | int")] fn get_ax(&self, axis: Bound) -> PyResult { if axis.is_instance_of::() { let axis = axis .cast_into::()? .extract::()? .parse::()?; 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 #[pyo3(text_signature = "ax0: str | int, ax1: str | int")] fn swap_axes(&self, ax0: Bound, 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.clone(), ome: self.ome.clone(), }) } /// permute the order of the axes #[pyo3(signature = (axes = None), text_signature = "axes: list[str | int] = None")] fn transpose(&self, 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.clone(), ome: self.ome.clone(), }) } #[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(), }) } #[getter] fn get_dtype(&self) -> PyResult<&str> { Ok(match self.dtype { PixelType::I8 => "int8", PixelType::U8 => "uint8", PixelType::I16 => "int16", PixelType::U16 => "uint16", PixelType::I32 => "int32", PixelType::U32 => "uint32", PixelType::F32 => "float32", PixelType::F64 => "float64", PixelType::I64 => "int64", PixelType::U64 => "uint64", PixelType::I128 => "int128", PixelType::U128 => "uint128", PixelType::F128 => "float128", }) } #[setter] fn set_dtype(&mut self, dtype: String) -> PyResult<()> { self.dtype = dtype.parse()?; Ok(()) } /// get the maximum overall or along a given axis #[allow(clippy::too_many_arguments)] #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=0, 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.clone(), view: self.view.max_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), } .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)] #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=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.clone(), view: self.view.min_proj(self.get_ax(axis)?)?, ome: self.ome.clone(), } .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(), }) } } #[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(), } .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)] #[pyo3(signature = (axis=None, dtype=None, out=None, keepdims=false, initial=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(), } .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) } } #[getter] fn time_series(&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()?) } #[getter] fn delta_z(&self) -> PyResult> { Ok(self.ome.delta_z()?) } #[getter] fn time_interval(&self) -> PyResult> { Ok(self.ome.time_interval()?) } fn exposure_time(&self, channel: usize) -> PyResult> { Ok(self.ome.exposure_time(channel)?) } 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()?) } } pub(crate) fn ndbioimage_file() -> PathBuf { let file = Python::attach(|py| { py.import("ndbioimage") .unwrap() .filename() .unwrap() .to_string() }); PathBuf::from(file) } #[pyfunction] #[pyo3(name = "download_bioformats")] fn py_download_bioformats(gpl_formats: bool) -> PyResult<()> { download_bioformats(gpl_formats)?; Ok(()) } #[pymodule] #[pyo3(name = "ndbioimage_rs")] fn ndbioimage_rs(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_function(wrap_pyfunction!(py_download_bioformats, m)?)?; Ok(()) }