mod backed;
mod dataset;
pub mod memory;
pub use backed::AnnData;
pub use dataset::AnnDataSet;
pub use memory::PyAnnData;
use anndata;
use anndata::concat::JoinType;
use anndata::Backend;
use anndata_hdf5::H5;
use anyhow::Result;
use pyo3::prelude::*;
use std::{
collections::HashMap,
ops::Deref,
path::{Path, PathBuf},
};
pub(crate) fn get_backend<P: AsRef<Path>>(filename: P, backend: Option<&str>) -> &str {
if let Some(backend) = backend {
backend
} else {
if let Some(ext) = filename.as_ref().extension() {
match ext.to_str().unwrap() {
"h5ad" | "h5" | "h5ads" => H5::NAME,
_ => H5::NAME,
}
} else {
H5::NAME
}
}
}
#[pyfunction]
#[pyo3(
signature = (filename, backed="r+", backend=None),
text_signature = "(filename, backed='r+', backend=None)",
)]
pub fn read<'py>(
py: Python<'py>,
filename: PathBuf,
backed: Option<&str>,
backend: Option<&str>,
) -> Result<Bound<'py, PyAny>> {
let adata = match backed {
Some(m) => {
let backend = get_backend(&filename, backend);
AnnData::new_from(filename, m, backend)
.unwrap()
.into_pyobject(py)?
.into_any()
}
None => PyModule::import(py, "anndata")?
.getattr("read_h5ad")?
.call1((filename,))?
.into_pyobject(py)?
.into_any(),
};
Ok(adata)
}
#[pyfunction]
#[pyo3(
signature = (adatas, *, join="inner", label=None, keys=None, file=None, backend=None),
text_signature = "(adatas, *, join='inner', label=None, keys=None, file=None, backend=None)",
)]
pub fn concat<'py>(
py: Python<'py>,
adatas: Vec<PyObject>,
join: &str,
label: Option<&str>,
keys: Option<Vec<String>>,
file: Option<PathBuf>,
backend: Option<&str>,
) -> Result<Bound<'py, PyAny>> {
let join = match join {
"inner" => JoinType::Inner,
"outer" => JoinType::Outer,
_ => panic!("Unknown join type"),
};
enum T<'a> {
H5(anndata::AnnData<H5>),
Py(PyAnnData<'a>),
}
let keys = keys.as_ref().map(|x| x.as_slice());
let out = if let Some(file) = file {
let backend = get_backend(&file, backend);
match backend {
H5::NAME => {
let adata = anndata::AnnData::<H5>::new(file)?;
T::H5(adata)
}
backend => todo!("Backend {} is not supported", backend),
}
} else {
T::Py(PyAnnData::new(py)?)
};
if !adatas.is_empty() {
if let Ok(adata) = adatas[0].extract::<AnnData>(py) {
match adata.backend().as_str() {
H5::NAME => {
let adatas = adatas
.into_iter()
.map(|x| x.extract::<AnnData>(py).unwrap())
.collect::<Vec<_>>();
let adatas: Vec<_> = adatas.iter().map(|x| x.inner_ref::<H5>()).collect();
let adatas: Vec<_> = adatas.iter().map(|x| x.deref()).collect();
match &out {
T::H5(out) => anndata::concat::concat(&adatas, join, label, keys, out)?,
T::Py(out) => anndata::concat::concat(&adatas, join, label, keys, out)?,
}
}
_ => todo!(),
}
} else {
let adatas = adatas
.into_iter()
.map(|x| x.extract::<PyAnnData>(py).unwrap())
.collect::<Vec<_>>();
match &out {
T::H5(out) => anndata::concat::concat(&adatas, join, label, keys, out)?,
T::Py(out) => anndata::concat::concat(&adatas, join, label, keys, out)?,
}
}
}
match out {
T::H5(adata) => Ok(AnnData::from(adata).into_pyobject(py)?.into_any()),
T::Py(adata) => Ok(adata.into_pyobject(py)?.into_any()),
}
}
#[pyfunction]
#[pyo3(
signature = (mtx_file, *, obs_names=None, var_names=None, file=None, backend=None, sorted=false),
text_signature = "(mtx_file, *, obs_names=None, var_names=None, file=None, backend=None, sorted=False)",
)]
pub fn read_mtx<'py>(
py: Python<'py>,
mtx_file: PathBuf,
obs_names: Option<PathBuf>,
var_names: Option<PathBuf>,
file: Option<PathBuf>,
backend: Option<&str>,
sorted: bool,
) -> Result<Bound<'py, PyAny>> {
let mut reader = anndata::reader::MMReader::from_path(mtx_file)?;
if let Some(obs_names) = obs_names {
reader = reader.obs_names(obs_names)?;
}
if let Some(var_names) = var_names {
reader = reader.var_names(var_names)?;
}
if sorted {
reader = reader.is_sorted();
}
if let Some(file) = file {
let backend = get_backend(&file, backend);
match backend {
H5::NAME => {
let adata = anndata::AnnData::<H5>::new(file)?;
reader.finish(&adata)?;
Ok(AnnData::from(adata).into_pyobject(py)?.into_any())
}
backend => todo!("Backend {} is not supported", backend),
}
} else {
let adata = PyAnnData::new(py)?;
reader.finish(&adata)?;
Ok(adata.into_pyobject(py)?)
}
}
#[pyfunction]
#[pyo3(
signature = (filename, *, adata_files_update=None, mode="r+", backend=None),
text_signature = "(filename, *, adata_files_update=None, mode='r+', backend=None)",
)]
pub fn read_dataset(
filename: PathBuf,
adata_files_update: Option<LocationUpdate>,
mode: &str,
backend: Option<&str>,
) -> Result<AnnDataSet> {
let adata_files_update = match adata_files_update {
Some(LocationUpdate::Map(map)) => Some(Ok(map)),
Some(LocationUpdate::Dir(dir)) => Some(Err(dir)),
None => None,
};
let backend = get_backend(&filename, backend);
match backend {
H5::NAME => {
let file = match mode {
"r" => H5::open(filename)?,
"r+" => H5::open_rw(filename)?,
_ => panic!("Unkown mode"),
};
Ok(anndata::AnnDataSet::<H5>::open(file, adata_files_update)?.into())
}
_ => todo!(),
}
}
#[derive(FromPyObject)]
pub enum LocationUpdate {
Map(HashMap<String, PathBuf>),
Dir(PathBuf),
}