ff7cd562af
- implement more readers - fix downloading of bioformats jar - (mostly) compatible with python version
2059 lines
68 KiB
Rust
2059 lines
68 KiB
Rust
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<crate::error::Error> 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<IxDyn, DynReader>,
|
|
dtype: PixelType,
|
|
ome: Ome,
|
|
index: usize,
|
|
}
|
|
|
|
unsafe impl Send for PyView {}
|
|
unsafe impl Sync for PyView {}
|
|
|
|
impl PyView {
|
|
fn item(py: Python, view: View<IxDyn>, dtype: PixelType) -> PyResult<Bound<PyAny>> {
|
|
Ok(match dtype {
|
|
PixelType::I8 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i8>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U8 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u8>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I16 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i16>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U16 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u16>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i128>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u128>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f64>()?
|
|
.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<Bound<'py, PyAny>>,
|
|
axes: &str,
|
|
reader: Option<&str>,
|
|
) -> PyResult<Self> {
|
|
if path.is_instance_of::<Self>() {
|
|
Ok(path.cast_into::<Self>()?.extract::<Self>()?)
|
|
} else if path.is_instance_of::<PyBytes>() {
|
|
Ok(from_bytes(&path.extract::<Vec<u8>>()?).map_err(Error::from)?)
|
|
} else {
|
|
let builtins = PyModule::import(py, "builtins")?;
|
|
let path = PathBuf::from(
|
|
builtins
|
|
.getattr("str")?
|
|
.call1((path,))?
|
|
.cast_into::<PyString>()?
|
|
.extract::<String>()?,
|
|
);
|
|
let axes = axes
|
|
.chars()
|
|
.map(|a| a.to_string().parse().map_err(Error::from))
|
|
.collect::<Result<Vec<Axis>, 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::<String>()?;
|
|
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<HashSet<usize>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>>,
|
|
bead_files: Option<Bound<'py, PyAny>>,
|
|
) -> PyResult<PyView> {
|
|
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<Bound<'py, PyAny>> {
|
|
let view = self.view.squeeze()?;
|
|
if view.ndim() == 0 {
|
|
Ok(match self.dtype {
|
|
PixelType::I8 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i8>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U8 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u8>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I16 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i16>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U16 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u16>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::I128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<i64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::U128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<u64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F32 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f32>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F64 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f64>()?
|
|
.into_pyobject(py)?
|
|
.into_any(),
|
|
PixelType::F128 => view
|
|
.into_dimensionality::<Ix0>()?
|
|
.item::<f64>()?
|
|
.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<Self> {
|
|
let np = PyModule::import(py, "numpy")?;
|
|
let dt = np.getattr("dtype")?.call1((&dtype,))?;
|
|
let name = dt.getattr("name")?;
|
|
let dtype_str = name.extract::<String>()?;
|
|
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> {
|
|
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<Bound<'py, PyAny>> {
|
|
let slice: Vec<_> = if n.is_instance_of::<PyTuple>() {
|
|
n.cast_into::<PyTuple>()?.into_iter().collect()
|
|
} else if n.is_instance_of::<PyList>() {
|
|
n.cast_into::<PyList>()?.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::<PyInt>() {
|
|
new_slice.push(SliceInfoElem::Index(s.cast::<PyInt>()?.extract::<isize>()?));
|
|
} else if s.is_instance_of::<PySlice>() {
|
|
let u = s.cast::<PySlice>()?.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::<PyEllipsis>() {
|
|
if ellipsis.is_some() {
|
|
return Err(PyErr::new::<PyValueError, _>(
|
|
"cannot have more than one ellipsis".to_string(),
|
|
));
|
|
}
|
|
let _ = ellipsis.insert(i);
|
|
} else if let Ok(arr_like) = s.extract::<PyArrayLike0<isize, AllowTypeChange>>() {
|
|
let pyarr: &Bound<PyArray<isize, Ix0>> = &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::<PyArrayLike1<isize, AllowTypeChange>>() {
|
|
let pyarr: &Bound<PyArray<isize, Ix1>> = &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::<HashSet<_>>();
|
|
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::<Vec<_>>()[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<Bound<'py, PyAny>>,
|
|
copy: Option<bool>,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
if let Some(dtype) = dtype {
|
|
self.as_type(py, dtype)?.as_array(py)
|
|
} else {
|
|
self.as_array(py)
|
|
}
|
|
}
|
|
|
|
fn __contains__(&self, _item: Bound<PyAny>) -> PyResult<bool> {
|
|
Err(PyNotImplementedError::new_err("contains not implemented"))
|
|
}
|
|
|
|
fn __lt__<'py>(
|
|
&self,
|
|
py: Python<'py>,
|
|
other: Bound<'py, PyAny>,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>>,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>>,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<Bound<'py, PyAny>> {
|
|
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<PyRef<'py, Self>> {
|
|
Ok(slf)
|
|
}
|
|
|
|
#[allow(unused_variables)]
|
|
#[pyo3(signature = (exc_type=None, exc_val=None, exc_tb=None))]
|
|
fn __exit__(
|
|
&self,
|
|
exc_type: Option<Bound<PyAny>>,
|
|
exc_val: Option<Bound<PyAny>>,
|
|
exc_tb: Option<Bound<PyAny>>,
|
|
) -> PyResult<()> {
|
|
self.close()
|
|
}
|
|
|
|
pub(crate) fn __getnewargs__(&self) -> PyResult<(Vec<u8>,)> {
|
|
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<Option<Bound<'py, PyAny>>> {
|
|
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<usize> {
|
|
Ok(self.view.len())
|
|
}
|
|
|
|
fn __repr__(&self) -> PyResult<String> {
|
|
Ok(self.view.summary()?)
|
|
}
|
|
|
|
fn __str__(&self) -> PyResult<String> {
|
|
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<Bound<'py, PyAny>> {
|
|
Ok(match self.dtype {
|
|
PixelType::I8 => self
|
|
.view
|
|
.get_frame::<i8, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::U8 => self
|
|
.view
|
|
.get_frame::<u8, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::I16 => self
|
|
.view
|
|
.get_frame::<i16, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::U16 => self
|
|
.view
|
|
.get_frame::<u16, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::I32 => self
|
|
.view
|
|
.get_frame::<i32, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::U32 => self
|
|
.view
|
|
.get_frame::<u32, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::F32 => self
|
|
.view
|
|
.get_frame::<f32, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::F64 => self
|
|
.view
|
|
.get_frame::<f64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::I64 => self
|
|
.view
|
|
.get_frame::<i64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::U64 => self
|
|
.view
|
|
.get_frame::<u64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::I128 => self
|
|
.view
|
|
.get_frame::<i64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::U128 => self
|
|
.view
|
|
.get_frame::<u64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
PixelType::F128 => self
|
|
.view
|
|
.get_frame::<f64, _>(c, z, t)?
|
|
.into_pyarray(py)
|
|
.into_any(),
|
|
})
|
|
}
|
|
|
|
fn flatten<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
|
Ok(match self.dtype {
|
|
PixelType::I8 => self.view.flatten::<i8>()?.into_pyarray(py).into_any(),
|
|
PixelType::U8 => self.view.flatten::<u8>()?.into_pyarray(py).into_any(),
|
|
PixelType::I16 => self.view.flatten::<i16>()?.into_pyarray(py).into_any(),
|
|
PixelType::U16 => self.view.flatten::<u16>()?.into_pyarray(py).into_any(),
|
|
PixelType::I32 => self.view.flatten::<i32>()?.into_pyarray(py).into_any(),
|
|
PixelType::U32 => self.view.flatten::<u32>()?.into_pyarray(py).into_any(),
|
|
PixelType::F32 => self.view.flatten::<f32>()?.into_pyarray(py).into_any(),
|
|
PixelType::F64 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
|
|
PixelType::I64 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
|
|
PixelType::U64 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
|
|
PixelType::I128 => self.view.flatten::<i64>()?.into_pyarray(py).into_any(),
|
|
PixelType::U128 => self.view.flatten::<u64>()?.into_pyarray(py).into_any(),
|
|
PixelType::F128 => self.view.flatten::<f64>()?.into_pyarray(py).into_any(),
|
|
})
|
|
}
|
|
|
|
fn to_bytes(&self) -> PyResult<Vec<u8>> {
|
|
Ok(match self.dtype {
|
|
PixelType::I8 => self.view.to_bytes::<i8>()?,
|
|
PixelType::U8 => self.view.to_bytes::<u8>()?,
|
|
PixelType::I16 => self.view.to_bytes::<i16>()?,
|
|
PixelType::U16 => self.view.to_bytes::<u16>()?,
|
|
PixelType::I32 => self.view.to_bytes::<i32>()?,
|
|
PixelType::U32 => self.view.to_bytes::<u32>()?,
|
|
PixelType::F32 => self.view.to_bytes::<f32>()?,
|
|
PixelType::F64 => self.view.to_bytes::<f64>()?,
|
|
PixelType::I64 => self.view.to_bytes::<i64>()?,
|
|
PixelType::U64 => self.view.to_bytes::<u64>()?,
|
|
PixelType::I128 => self.view.to_bytes::<i64>()?,
|
|
PixelType::U128 => self.view.to_bytes::<u64>()?,
|
|
PixelType::F128 => self.view.to_bytes::<f64>()?,
|
|
})
|
|
}
|
|
|
|
fn tobytes(&self) -> PyResult<Vec<u8>> {
|
|
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<PathBuf> {
|
|
Ok(self.view.path().to_owned())
|
|
}
|
|
|
|
/// the series in the file
|
|
#[getter]
|
|
fn series(&self) -> PyResult<usize> {
|
|
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<Vec<String>> {
|
|
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<PyAny>,
|
|
) -> PyResult<usize> {
|
|
if axis.is_instance_of::<PyString>() {
|
|
let axis = axis
|
|
.cast_into::<PyString>()?
|
|
.extract::<String>()?
|
|
.parse::<Axis>()
|
|
.map_err(Error::from)?;
|
|
self.view
|
|
.axes()
|
|
.iter()
|
|
.position(|a| *a == axis)
|
|
.ok_or_else(|| {
|
|
PyErr::new::<PyValueError, _>(format!("cannot find axis {:?}", axis))
|
|
})
|
|
} else if axis.is_instance_of::<PyInt>() {
|
|
Ok(axis.cast_into::<PyInt>()?.extract::<usize>()?)
|
|
} else {
|
|
Err(PyErr::new::<PyValueError, _>(
|
|
"cannot convert to axis".to_string(),
|
|
))
|
|
}
|
|
}
|
|
|
|
/// swap two axes
|
|
fn swap_axes(
|
|
&self,
|
|
#[gen_stub(override_type(type_repr = "int | str"))] ax0: Bound<PyAny>,
|
|
#[gen_stub(override_type(type_repr = "int | str"))] ax1: Bound<PyAny>,
|
|
) -> PyResult<Self> {
|
|
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<Vec<Bound<PyAny>>>,
|
|
) -> PyResult<Self> {
|
|
let view = if let Some(axes) = axes {
|
|
let ax = axes
|
|
.into_iter()
|
|
.map(|a| self.get_ax(a))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
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<PyView> {
|
|
self.transpose(None)
|
|
}
|
|
|
|
/// collect data into a numpy array
|
|
fn as_array<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
|
Ok(match self.dtype {
|
|
PixelType::I8 => self.view.as_array_dyn::<i8>()?.into_pyarray(py).into_any(),
|
|
PixelType::U8 => self.view.as_array_dyn::<u8>()?.into_pyarray(py).into_any(),
|
|
PixelType::I16 => self.view.as_array_dyn::<i16>()?.into_pyarray(py).into_any(),
|
|
PixelType::U16 => self.view.as_array_dyn::<u16>()?.into_pyarray(py).into_any(),
|
|
PixelType::I32 => self.view.as_array_dyn::<i32>()?.into_pyarray(py).into_any(),
|
|
PixelType::U32 => self.view.as_array_dyn::<u32>()?.into_pyarray(py).into_any(),
|
|
PixelType::F32 => self.view.as_array_dyn::<f32>()?.into_pyarray(py).into_any(),
|
|
PixelType::F64 => self.view.as_array_dyn::<f64>()?.into_pyarray(py).into_any(),
|
|
PixelType::I64 => self.view.as_array_dyn::<i64>()?.into_pyarray(py).into_any(),
|
|
PixelType::U64 => self.view.as_array_dyn::<u64>()?.into_pyarray(py).into_any(),
|
|
PixelType::I128 => self.view.as_array_dyn::<i64>()?.into_pyarray(py).into_any(),
|
|
PixelType::U128 => self.view.as_array_dyn::<u64>()?.into_pyarray(py).into_any(),
|
|
PixelType::F128 => self.view.as_array_dyn::<f64>()?.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<Bound<'py, PyArrayDescr>> {
|
|
match self.dtype {
|
|
PixelType::I8 => Ok(dtype::<i8>(py)),
|
|
PixelType::U8 => Ok(dtype::<u8>(py)),
|
|
PixelType::I16 => Ok(dtype::<i16>(py)),
|
|
PixelType::U16 => Ok(dtype::<u16>(py)),
|
|
PixelType::I32 => Ok(dtype::<i32>(py)),
|
|
PixelType::U32 => Ok(dtype::<u32>(py)),
|
|
PixelType::F32 => Ok(dtype::<f32>(py)),
|
|
PixelType::F64 => Ok(dtype::<f64>(py)),
|
|
PixelType::I64 => Ok(dtype::<i64>(py)),
|
|
PixelType::U64 => Ok(dtype::<u64>(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::<f64>(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::<String>()?;
|
|
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<Bound<'py, PyAny>>,
|
|
dtype: Option<Bound<'py, PyAny>>,
|
|
out: Option<Bound<'py, PyAny>>,
|
|
keepdims: bool,
|
|
initial: Option<usize>,
|
|
r#where: bool,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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::<i8>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U8 => self.view.max::<u8>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I16 => self.view.max::<i16>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U16 => self.view.max::<u16>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I32 => self.view.max::<i32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U32 => self.view.max::<u32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F32 => self.view.max::<f32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F64 => self.view.max::<f64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I64 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U64 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I128 => self.view.max::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U128 => self.view.max::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F128 => self.view.max::<f64>()?.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<Bound<'py, PyAny>>,
|
|
dtype: Option<Bound<'py, PyAny>>,
|
|
out: Option<Bound<'py, PyAny>>,
|
|
keepdims: bool,
|
|
initial: Option<usize>,
|
|
r#where: bool,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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::<i8>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U8 => self.view.min::<u8>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I16 => self.view.min::<i16>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U16 => self.view.min::<u16>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I32 => self.view.min::<i32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U32 => self.view.min::<u32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F32 => self.view.min::<f32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F64 => self.view.min::<f64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I64 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U64 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I128 => self.view.min::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U128 => self.view.min::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F128 => self.view.min::<f64>()?.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<Bound<'py, PyAny>>,
|
|
dtype: Option<Bound<'py, PyAny>>,
|
|
out: Option<Bound<'py, PyAny>>,
|
|
keepdims: bool,
|
|
r#where: bool,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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::<f32>()?.into_pyobject(py)?.into_any(),
|
|
_ => self.view.mean::<f64>()?.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<Bound<'py, PyAny>>,
|
|
dtype: Option<Bound<'py, PyAny>>,
|
|
out: Option<Bound<'py, PyAny>>,
|
|
keepdims: bool,
|
|
initial: Option<usize>,
|
|
r#where: bool,
|
|
) -> PyResult<Bound<'py, PyAny>> {
|
|
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::<f32>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F64 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I64 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U64 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::I128 => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::U128 => self.view.sum::<u64>()?.into_pyobject(py)?.into_any(),
|
|
PixelType::F128 => self.view.sum::<f64>()?.into_pyobject(py)?.into_any(),
|
|
_ => self.view.sum::<i64>()?.into_pyobject(py)?.into_any(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[getter]
|
|
fn z_stack(&self) -> PyResult<bool> {
|
|
if let Some(s) = self.view.size_ax(Axis::Z) {
|
|
Ok(s > 1)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn zstack(&self) -> PyResult<bool> {
|
|
if let Some(s) = self.view.size_ax(Axis::Z) {
|
|
Ok(s > 1)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
#[getter]
|
|
fn time_series(&self) -> PyResult<bool> {
|
|
if let Some(s) = self.view.size_ax(Axis::T) {
|
|
Ok(s > 1)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn timeseries(&self) -> PyResult<bool> {
|
|
if let Some(s) = self.view.size_ax(Axis::T) {
|
|
Ok(s > 1)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
#[getter]
|
|
fn pixel_size(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.pixel_size()?)
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn pxsize_um(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.pixel_size()?.map(|p| p / 1000.))
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn deltaz_um(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.delta_z()?.map(|p| p / 1000.))
|
|
}
|
|
|
|
#[getter]
|
|
fn delta_z(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.delta_z()?)
|
|
}
|
|
|
|
#[getter]
|
|
fn time_interval(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.time_interval()?)
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn timeinterval(&self) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.time_interval()?)
|
|
}
|
|
|
|
fn exposure_time(&self, channel: usize) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.exposure_time(channel)?)
|
|
}
|
|
|
|
/// backwards compatibility
|
|
#[getter]
|
|
fn exposuretime_s(&self) -> PyResult<Vec<Option<f64>>> {
|
|
Ok((0..self.view.shape().c)
|
|
.map(|c| self.ome.exposure_time(c))
|
|
.collect::<Result<Vec<_>, Error>>()?)
|
|
}
|
|
|
|
fn binning(&self, channel: usize) -> Option<usize> {
|
|
self.ome.binning(channel)
|
|
}
|
|
|
|
fn laser_wavelengths(&self, channel: usize) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.laser_wavelengths(channel)?)
|
|
}
|
|
|
|
fn laser_power(&self, channel: usize) -> PyResult<Option<f64>> {
|
|
Ok(self.ome.laser_powers(channel)?)
|
|
}
|
|
|
|
#[getter]
|
|
fn objective_name(&self) -> Option<String> {
|
|
self.ome.objective_name()
|
|
}
|
|
|
|
#[getter]
|
|
fn magnification(&self) -> Option<f64> {
|
|
self.ome.magnification()
|
|
}
|
|
|
|
#[getter]
|
|
fn tube_lens_name(&self) -> Option<String> {
|
|
self.ome.tube_lens_name()
|
|
}
|
|
|
|
fn filter_set_name(&self, channel: usize) -> Option<String> {
|
|
self.ome.filter_set_name(channel)
|
|
}
|
|
|
|
fn gain(&self, channel: usize) -> Option<f64> {
|
|
self.ome.gain(channel)
|
|
}
|
|
|
|
/// gives a helpful summary of the recorded experiment
|
|
fn summary(&self) -> PyResult<String> {
|
|
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<HashSet<usize>> {
|
|
let path = if path.is_instance_of::<Self>() {
|
|
let py_view: Self = path.cast_into::<Self>()?.extract::<Self>()?;
|
|
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::<PyString>()?
|
|
.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)?)
|
|
}
|
|
|
|
/// 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<HashSet<usize>> {
|
|
let path = if path.is_instance_of::<Self>() {
|
|
let py_view: Self = path.cast_into::<Self>()?.extract::<Self>()?;
|
|
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::<PyString>()?
|
|
.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(
|
|
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<Vec<String>>,
|
|
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::<i8, _>(file, &options),
|
|
PixelType::U8 => self.view.save_as_tiff_with_type::<u8, _>(file, &options),
|
|
PixelType::I16 => self.view.save_as_tiff_with_type::<i16, _>(file, &options),
|
|
PixelType::U16 => self.view.save_as_tiff_with_type::<u16, _>(file, &options),
|
|
PixelType::I32 => self.view.save_as_tiff_with_type::<i32, _>(file, &options),
|
|
PixelType::U32 => self.view.save_as_tiff_with_type::<u32, _>(file, &options),
|
|
PixelType::I64 => self.view.save_as_tiff_with_type::<i64, _>(file, &options),
|
|
PixelType::U64 => self.view.save_as_tiff_with_type::<u64, _>(file, &options),
|
|
PixelType::I128 => self.view.save_as_tiff_with_type::<i128, _>(file, &options),
|
|
PixelType::U128 => self.view.save_as_tiff_with_type::<u128, _>(file, &options),
|
|
PixelType::F32 => self.view.save_as_tiff_with_type::<f32, _>(file, &options),
|
|
PixelType::F64 => self.view.save_as_tiff_with_type::<f64, _>(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<Vec<f64>>,
|
|
scale: f64,
|
|
colors: Option<Vec<String>>,
|
|
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<PathBuf>,
|
|
files_out: Vec<PathBuf>,
|
|
operations: Option<Vec<(String, String)>>,
|
|
colors: Option<Vec<String>>,
|
|
overwrite: bool,
|
|
bar: bool,
|
|
message: Option<String>,
|
|
) -> 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<Self> {
|
|
Ok(Self {
|
|
inner: Shape {
|
|
c,
|
|
z,
|
|
t,
|
|
y,
|
|
x,
|
|
order: order
|
|
.chars()
|
|
.map(|c| c.to_uppercase().to_string().parse::<Axis>())
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.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::<Vec<_>>()
|
|
.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<Bound<'py, PyAny>> {
|
|
let idx = if idx.is_instance_of::<PyNone>() || idx.is_instance_of::<PyEllipsis>() {
|
|
vec![0, 1, 2, 3, 4]
|
|
} else if idx.is_instance_of::<PySlice>() {
|
|
let indices = idx.cast::<PySlice>()?.indices(5)?;
|
|
if indices.step > 0 {
|
|
(indices.start..indices.stop)
|
|
.step_by(indices.step as usize)
|
|
.map(|i| i as usize)
|
|
.collect::<Vec<_>>()
|
|
} else {
|
|
(indices.stop..indices.start)
|
|
.step_by(-indices.step as usize)
|
|
.map(|i| i as usize)
|
|
.collect::<Vec<_>>()
|
|
}
|
|
} else if idx.is_instance_of::<PyList>() {
|
|
idx.cast::<PyList>()?.extract::<Vec<usize>>()?
|
|
} else if idx.is_instance_of::<PyTuple>() {
|
|
idx.cast::<PyTuple>()?.extract::<Vec<usize>>()?
|
|
} else if idx.is_instance_of::<PyString>() {
|
|
let s = idx.cast::<PyString>()?.extract::<String>()?;
|
|
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::<Result<Vec<_>, _>>()?
|
|
} else if idx.is_instance_of::<PyInt>() {
|
|
vec![idx.cast::<PyInt>()?.extract::<usize>()?]
|
|
} else {
|
|
return Err(PyErr::new::<PyTypeError, _>("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::<Vec<_>>();
|
|
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<usize> {
|
|
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(())
|
|
}
|
|
}
|