From d8b2d1934e1a33a0661ea2b4808c847dba3cb698 Mon Sep 17 00:00:00 2001 From: theGreatHerrLebert Date: Wed, 20 Sep 2023 16:36:04 +0200 Subject: [PATCH] started implementing python binding --- mscore/src/mz_spectrum.rs | 41 +++++++++-- pyims/.github/workflows/CI.yml | 120 +++++++++++++++++++++++++++++++++ pyims/.gitignore | 72 ++++++++++++++++++++ pyims/Cargo.toml | 15 +++++ pyims/pyproject.toml | 16 +++++ pyims/src/lib.rs | 14 ++++ pyims/src/py_mz_spectrum.rs | 83 +++++++++++++++++++++++ pyims/src/pyhandle.rs | 32 +++++++++ rustdf/Cargo.toml | 2 +- rustdf/src/data/handle.rs | 14 ++-- rustdf/src/data/raw.rs | 6 +- rustdf/src/main.rs | 6 +- 12 files changed, 399 insertions(+), 22 deletions(-) create mode 100644 pyims/.github/workflows/CI.yml create mode 100644 pyims/.gitignore create mode 100644 pyims/Cargo.toml create mode 100644 pyims/pyproject.toml create mode 100644 pyims/src/lib.rs create mode 100644 pyims/src/py_mz_spectrum.rs create mode 100644 pyims/src/pyhandle.rs diff --git a/mscore/src/mz_spectrum.rs b/mscore/src/mz_spectrum.rs index b712a2c2..4c8efc60 100644 --- a/mscore/src/mz_spectrum.rs +++ b/mscore/src/mz_spectrum.rs @@ -18,17 +18,46 @@ pub struct MzSpectrum { /// #[derive(Clone)] pub enum MsType { - PRECURSOR, - FRAGMENT, - UNKNOWN, + Precursor, + FragmentDda, + FragmentDia, + Unknown, +} + +impl MsType { + /// Returns the `MsType` enum corresponding to the given integer value. + /// + /// # Arguments + /// + /// * `ms_type` - An integer value corresponding to the `MsType` enum. + /// + pub fn new(ms_type: i32) -> MsType { + match ms_type { + 0 => MsType::Precursor, + 8 => MsType::FragmentDda, + 9 => MsType::FragmentDia, + _ => MsType::Unknown, + } + } + + /// Returns the integer value corresponding to the `MsType` enum. + pub fn to_i32(&self) -> i32 { + match self { + MsType::Precursor => 0, + MsType::FragmentDda => 8, + MsType::FragmentDia => 9, + MsType::Unknown => -1, + } + } } impl Display for MsType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - MsType::PRECURSOR => write!(f, "PRECURSOR"), - MsType::FRAGMENT => write!(f, "FRAGMENT"), - MsType::UNKNOWN => write!(f, "UNKNOWN"), + MsType::Precursor => write!(f, "Precursor"), + MsType::FragmentDda => write!(f, "FragmentDda"), + MsType::FragmentDia => write!(f, "FragmentDia"), + MsType::Unknown => write!(f, "Unknown"), } } } diff --git a/pyims/.github/workflows/CI.yml b/pyims/.github/workflows/CI.yml new file mode 100644 index 00000000..f5796a50 --- /dev/null +++ b/pyims/.github/workflows/CI.yml @@ -0,0 +1,120 @@ +# This file is autogenerated by maturin v1.2.3 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [linux, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing * diff --git a/pyims/.gitignore b/pyims/.gitignore new file mode 100644 index 00000000..af3ca5ef --- /dev/null +++ b/pyims/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version \ No newline at end of file diff --git a/pyims/Cargo.toml b/pyims/Cargo.toml new file mode 100644 index 00000000..166457fd --- /dev/null +++ b/pyims/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pyims" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "pyims" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.19.2", features = ["extension-module"] } +numpy = "0.19.0" +mscore = {path = "../mscore"} +rustdf = {path = "../rustdf"} diff --git a/pyims/pyproject.toml b/pyims/pyproject.toml new file mode 100644 index 00000000..bf04ff29 --- /dev/null +++ b/pyims/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["maturin>=1.2,<2.0"] +build-backend = "maturin" + +[project] +name = "pyims" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + + +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/pyims/src/lib.rs b/pyims/src/lib.rs new file mode 100644 index 00000000..0cc28378 --- /dev/null +++ b/pyims/src/lib.rs @@ -0,0 +1,14 @@ +mod pyhandle; +mod py_mz_spectrum; + +use pyo3::prelude::*; +use crate::pyhandle::PyTimsDataset; +use crate::py_mz_spectrum::PyMzSpectrum; + +/// A Python module implemented in Rust. +#[pymodule] +fn pyims(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + Ok(()) +} \ No newline at end of file diff --git a/pyims/src/py_mz_spectrum.rs b/pyims/src/py_mz_spectrum.rs new file mode 100644 index 00000000..372709c3 --- /dev/null +++ b/pyims/src/py_mz_spectrum.rs @@ -0,0 +1,83 @@ +use pyo3::prelude::*; +use numpy::{PyArray1, IntoPyArray}; +use mscore::{MzSpectrum, TimsFrame, MsType}; + +#[pyclass] +pub struct PyMzSpectrum { + inner: MzSpectrum, +} + +#[pymethods] +impl PyMzSpectrum { + #[new] + pub unsafe fn new(mz: &PyArray1, intensity: &PyArray1) -> PyResult { + Ok(PyMzSpectrum { + inner: MzSpectrum { + mz: mz.as_slice()?.to_vec(), + intensity: intensity.as_slice()?.to_vec(), + }, + }) + } + + #[getter] + pub fn mz(&self, py: Python) -> Py> { + self.inner.mz.clone().into_pyarray(py).to_owned() + } + + #[getter] + pub fn intensity(&self, py: Python) -> Py> { + self.inner.intensity.clone().into_pyarray(py).to_owned() + } +} + +#[pyclass] +pub struct PyTimsFrame { + pub inner: TimsFrame, +} + +#[pymethods] +impl PyTimsFrame { + #[new] + pub unsafe fn new(frame_id: i32, ms_type: i32, retention_time: f64, scan: &PyArray1, inv_mobility: &PyArray1, tof: &PyArray1, mz: &PyArray1, intensity: &PyArray1) -> PyResult { + Ok(PyTimsFrame { + inner: TimsFrame { + frame_id, + ms_type: MsType::new(ms_type), + retention_time, + scan: scan.as_slice()?.to_vec(), + inv_mobility: inv_mobility.as_slice()?.to_vec(), + tof: tof.as_slice()?.to_vec(), + mz: mz.as_slice()?.to_vec(), + intensity: intensity.as_slice()?.to_vec(), + }, + }) + } + #[getter] + pub fn mz(&self, py: Python) -> Py> { + self.inner.mz.clone().into_pyarray(py).to_owned() + } + #[getter] + pub fn intensity(&self, py: Python) -> Py> { + self.inner.intensity.clone().into_pyarray(py).to_owned() + } + #[getter] + pub fn scan(&self, py: Python) -> Py> { + self.inner.scan.clone().into_pyarray(py).to_owned() + } + #[getter] + pub fn inv_mobility(&self, py: Python) -> Py> { + self.inner.inv_mobility.clone().into_pyarray(py).to_owned() + } + #[getter] + pub fn tof(&self, py: Python) -> Py> { + self.inner.tof.clone().into_pyarray(py).to_owned() + } + #[getter] + pub fn frame_id(&self) -> i32 { + self.inner.frame_id + } + #[getter] + pub fn ms_type(&self) -> i32 { + self.inner.ms_type.to_i32() + } +} \ No newline at end of file diff --git a/pyims/src/pyhandle.rs b/pyims/src/pyhandle.rs new file mode 100644 index 00000000..25598eb4 --- /dev/null +++ b/pyims/src/pyhandle.rs @@ -0,0 +1,32 @@ +use pyo3::prelude::*; + +use rustdf::data::handle::{TimsDataset}; +use crate::py_mz_spectrum::PyTimsFrame; + +#[pyclass] +pub struct PyTimsDataset { + inner: TimsDataset, +} + +#[pymethods] +impl PyTimsDataset { + #[new] + pub fn new(data_path: &str, bruker_lib_path: &str) -> Self { + let dataset = TimsDataset::new(bruker_lib_path, data_path).unwrap(); + PyTimsDataset { inner: dataset } + } + #[getter] + pub fn get_data_path(&self) -> &str { + &self.inner.data_path + } + #[getter] + pub fn get_bruker_lib_path(&self) -> &str { + &self.inner.bruker_lib_path + } + + + pub fn get_frame(&self, frame_id: u32) -> PyResult { + let frame = self.inner.get_frame(frame_id).unwrap(); + Ok(PyTimsFrame { inner: frame }) + } +} \ No newline at end of file diff --git a/rustdf/Cargo.toml b/rustdf/Cargo.toml index 703cf809..70ae3b65 100644 --- a/rustdf/Cargo.toml +++ b/rustdf/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust_tdf" +name = "rustdf" version = "0.1.0" edition = "2021" diff --git a/rustdf/src/data/handle.rs b/rustdf/src/data/handle.rs index 63a9c379..8c5df67d 100644 --- a/rustdf/src/data/handle.rs +++ b/rustdf/src/data/handle.rs @@ -114,7 +114,7 @@ pub struct TimsDataset { pub bruker_lib: BrukerTimsDataLibrary, pub global_meta_data: GlobalMetaData, pub frame_meta_data: Vec, - pub aquisition_mode: AcquisitionMode, + pub acquisition_mode: AcquisitionMode, pub max_scan_count: i64, pub frame_idptr: Vec, pub tims_offset_values: Vec, @@ -154,7 +154,7 @@ impl TimsDataset { let tims_offset_values = frame_meta_data.iter().map(|x| x.tims_id).collect::>(); // get the acquisition mode - let aquisition_mode = match frame_meta_data[0].scan_mode { + let acquisition_mode = match frame_meta_data[0].scan_mode { 8 => AcquisitionMode::DDA, 9 => AcquisitionMode::DIA, 10 => AcquisitionMode::MIDIA, @@ -167,7 +167,7 @@ impl TimsDataset { bruker_lib, global_meta_data, frame_meta_data, - aquisition_mode, + acquisition_mode, max_scan_count, frame_idptr, tims_offset_values, @@ -298,10 +298,10 @@ impl TimsDataset { let ms_type_raw = self.frame_meta_data[frame_index].ms_ms_type; let ms_type = match ms_type_raw { - 0 => MsType::PRECURSOR, - 8 => MsType::FRAGMENT, - 9 => MsType::FRAGMENT, - _ => MsType::UNKNOWN, + 0 => MsType::Precursor, + 8 => MsType::FragmentDda, + 9 => MsType::FragmentDia, + _ => MsType::Unknown, }; Ok(TimsFrame { diff --git a/rustdf/src/data/raw.rs b/rustdf/src/data/raw.rs index da51c7d9..fa7f714a 100644 --- a/rustdf/src/data/raw.rs +++ b/rustdf/src/data/raw.rs @@ -29,8 +29,6 @@ impl BrukerTimsDataLibrary { let lib = unsafe { Library::new(bruker_lib_path)? }; - - println!("bruker binary successfully loaded library."); // create a handle to the raw data let handle = unsafe { @@ -40,8 +38,6 @@ impl BrukerTimsDataLibrary { handle }; - println!("bruker library created handle to TDF data."); - // return the BrukerTimsDataLibrary struct Ok(BrukerTimsDataLibrary { lib, @@ -116,7 +112,7 @@ impl Drop for BrukerTimsDataLibrary { fn drop(&mut self) { let close = self.tims_close(); match close { - Ok(_) => println!("bruker library closed handle to TDF data."), + Ok(_) => (), Err(e) => println!("error: {}", e), }; } diff --git a/rustdf/src/main.rs b/rustdf/src/main.rs index c0c77520..90872711 100644 --- a/rustdf/src/main.rs +++ b/rustdf/src/main.rs @@ -1,4 +1,4 @@ -use rust_tdf::data::handle::TimsDataset; +use rustdf::data::handle::TimsDataset; fn main() { let data_path = "/media/hd01/CCSPred/M210115_001_Slot1-1_1_850.d"; @@ -6,7 +6,7 @@ fn main() { let tims_data = TimsDataset::new(bruker_lib_path, data_path); match tims_data { Ok(tims_data) => { - for i in 1..2_000 { + for i in 1..5_000 { let frame = tims_data.get_frame(i); match frame { Ok(frame) => { @@ -18,4 +18,4 @@ fn main() { }, Err(e) => println!("error: {}", e), }; -} \ No newline at end of file +}