Skip to main content

rustdf/data/
raw.rs

1use libloading::{Library, Symbol};
2use std::cell::RefCell;
3use std::os::raw::{c_char, c_double, c_float};
4use std::sync::Mutex;
5
6//
7// A struct that holds a handle to the raw data
8//
9// # Example
10//
11// ```
12// let bruker_lib_path = "path/to/libtimsdata.so";
13// let data_path = "path/to/data.d";
14// let tims_data = BrukerTimsDataLibrary::new(bruker_lib_path, data_path);
15// ```
16pub struct BrukerTimsDataLibrary {
17    pub lib: Library,
18    pub handle: u64,
19}
20
21impl BrukerTimsDataLibrary {
22    //
23    // Create a new BrukerTimsDataLibrary struct
24    //
25    // # Arguments
26    //
27    // * `bruker_lib_path` - A string slice that holds the path to the bruker library
28    // * `data_path` - A string slice that holds the path to the data
29    //
30    // # Example
31    //
32    // ```
33    // let bruker_lib_path = "path/to/libtimsdata.so";
34    // let data_path = "path/to/data.d";
35    // let tims_data = BrukerTimsDataLibrary::new(bruker_lib_path, data_path);
36    // ```
37    pub fn new(
38        bruker_lib_path: &str,
39        data_path: &str,
40    ) -> Result<BrukerTimsDataLibrary, Box<dyn std::error::Error>> {
41        // Load the library
42        let lib = unsafe { Library::new(bruker_lib_path)? };
43
44        // create a handle to the raw data
45        let handle = unsafe {
46            let func: Symbol<unsafe extern "C" fn(*const c_char, u32) -> u64> =
47                lib.get(b"tims_open")?;
48            let path = std::ffi::CString::new(data_path)?;
49            let handle = func(path.as_ptr(), 0);
50            handle
51        };
52
53        // return the BrukerTimsDataLibrary struct
54        Ok(BrukerTimsDataLibrary { lib, handle })
55    }
56
57    //
58    // Close the handle to the raw data
59    //
60    // # Example
61    //
62    // ```
63    // let close = tims_data.tims_close();
64    // match close {
65    //     Ok(_) => println!("tims_data closed"),
66    //     Err(e) => println!("error: {}", e),
67    // };
68    // ```
69    pub fn tims_close(&self) -> Result<(), Box<dyn std::error::Error>> {
70        unsafe {
71            let func: Symbol<unsafe extern "C" fn(u64) -> ()> = self.lib.get(b"tims_close")?;
72            func(self.handle);
73        }
74        Ok(())
75    }
76
77    //
78    // Convert the given indices to mz values.
79    //
80    // # Example
81    //
82    // ```
83    // let indices = vec![...];
84    // let mz_values_result = tims_data.tims_index_to_mz(estimation, &mut indices, tof_max_index);
85    // match mz_values_result {
86    //     Ok(mz_values) => println!("{:?}", mz_values),
87    //     Err(e) => println!("error: {}", e),
88    // };
89    // ```
90    pub fn tims_index_to_mz(
91        &self,
92        frame_id: u32,
93        dbl_tofs: &[c_double],
94        mzs: &mut [c_double],
95    ) -> Result<(), Box<dyn std::error::Error>> {
96        unsafe {
97            let func: Symbol<unsafe extern "C" fn(u64, u32, *const c_double, *mut c_double, u32)> =
98                self.lib.get(b"tims_index_to_mz")?;
99            func(
100                self.handle,
101                frame_id,
102                dbl_tofs.as_ptr(),
103                mzs.as_mut_ptr(),
104                dbl_tofs.len() as u32,
105            );
106        }
107        Ok(())
108    }
109
110    //
111    // Convert the given mz values to indices.
112    //
113    // # Example
114    //
115    // ```
116    // let mzs = vec![...];
117    // let indices_result = tims_data.tims_mz_to_index(estimation, &mut mzs);
118    // match indices_result {
119    //     Ok(indices) => println!("{:?}", indices),
120    //     Err(e) => println!("error: {}", e),
121    // };
122    // ```
123    pub fn tims_mz_to_index(
124        &self,
125        frame_id: u32,
126        mzs: &[c_double],
127        indices: &mut [c_double],
128    ) -> Result<(), Box<dyn std::error::Error>> {
129        unsafe {
130            let func: Symbol<unsafe extern "C" fn(u64, u32, *const c_double, *mut c_double, u32)> =
131                self.lib.get(b"tims_mz_to_index")?;
132            func(
133                self.handle,
134                frame_id,
135                mzs.as_ptr(),
136                indices.as_mut_ptr(),
137                mzs.len() as u32,
138            );
139        }
140        Ok(())
141    }
142
143    //
144    // Convert the given indices to inverse mobility values.
145    //
146    // # Example
147    //
148    // ```
149    // let indices = vec![...];
150    // let scan_values_result = tims_data.tims_scan_to_inv_mob(estimation, &mut indices);
151    // match mz_values_result {
152    //     Ok(mz_values) => println!("{:?}", mz_values),
153    //     Err(e) => println!("error: {}", e),
154    // };
155    // ```
156    pub fn tims_scan_to_inv_mob(
157        &self,
158        frame_id: u32,
159        dbl_scans: &[c_double],
160        inv_mob: &mut [c_double],
161    ) -> Result<(), Box<dyn std::error::Error>> {
162        unsafe {
163            let func: Symbol<unsafe extern "C" fn(u64, u32, *const c_double, *mut c_double, u32)> =
164                self.lib.get(b"tims_scannum_to_oneoverk0")?;
165            func(
166                self.handle,
167                frame_id,
168                dbl_scans.as_ptr(),
169                inv_mob.as_mut_ptr(),
170                dbl_scans.len() as u32,
171            );
172        }
173        Ok(())
174    }
175
176    //
177    // Convert the given inverse mobility values to scan values.
178    //
179    // # Example
180    //
181    // ```
182    // let inv_mob = vec![...];
183    // let scan_values_result = tims_data.tims_inv_mob_to_scan(estimation, &mut inv_mob);
184    // match mz_values_result {
185    //     Ok(mz_values) => println!("{:?}", mz_values),
186    //     Err(e) => println!("error: {}", e),
187    // };
188    // ```
189    pub fn inv_mob_to_tims_scan(
190        &self,
191        frame_id: u32,
192        inv_mob: &[c_double],
193        scans: &mut [c_double],
194    ) -> Result<(), Box<dyn std::error::Error>> {
195        unsafe {
196            let func: Symbol<unsafe extern "C" fn(u64, u32, *const c_double, *mut c_double, u32)> =
197                self.lib.get(b"tims_oneoverk0_to_scannum")?;
198            func(
199                self.handle,
200                frame_id,
201                inv_mob.as_ptr(),
202                scans.as_mut_ptr(),
203                inv_mob.len() as u32,
204            );
205        }
206        Ok(())
207    }
208
209    // ----------------------------------------------------------------- //
210    // Centroided spectrum extraction (Bruker's built-in peak picker).
211    //
212    // SDK function:
213    //   uint32_t tims_extract_centroided_spectrum_for_frame_v2(
214    //       uint64_t handle,
215    //       int64_t  frame_id,
216    //       uint32_t scan_begin,
217    //       uint32_t scan_end,
218    //       void (*callback)(int64_t precursor_id, uint32_t num_peaks,
219    //                        double* mzs, float* intensities),
220    //       void* user_data);
221    //
222    // Bruker invokes the callback ONCE per scan-range with the centroided
223    // (m/z, intensity) arrays. We use a thread-local Mutex<Vec<...>> trick
224    // to hand the data back to Rust without dealing with `void*` user_data
225    // closures (libloading + C function pointers don't compose with Rust
226    // closures cleanly).
227    //
228    // Signatures recovered from pyTDFSDK (gtluu/pyTDFSDK init_tdf_sdk.py).
229    // Same callback shape as Bruker's published `MSMS_SPECTRUM_FUNCTOR`.
230    // ----------------------------------------------------------------- //
231
232    /// Extract a centroided spectrum for a (frame, scan-range) tile via
233    /// Bruker's built-in peak picker. Returns `(mz, intensity)` pairs.
234    pub fn tims_extract_centroided_spectrum_for_frame(
235        &self,
236        frame_id: i64,
237        scan_begin: u32,
238        scan_end: u32,
239    ) -> Result<(Vec<f64>, Vec<f32>), Box<dyn std::error::Error>> {
240        // Stash the result in a thread-local-ish global; only one extract
241        // call may run at a time per process. We serialise on a Mutex.
242        let mut result_mz: Vec<f64> = Vec::new();
243        let mut result_int: Vec<f32> = Vec::new();
244        // We trampoline through a global: the C callback writes into
245        // EXTRACT_BUF.
246        let _guard = EXTRACT_BUF
247            .lock()
248            .map_err(|_| "EXTRACT_BUF poisoned")?;
249        EXTRACT_BUF_DATA.with(|buf| *buf.borrow_mut() = Some((Vec::new(), Vec::new())));
250        unsafe {
251            let func: Symbol<
252                unsafe extern "C" fn(
253                    u64, i64, u32, u32,
254                    extern "C" fn(i64, u32, *const f64, *const f32),
255                    *mut std::ffi::c_void,
256                ) -> u32,
257            > = self.lib.get(b"tims_extract_centroided_spectrum_for_frame_v2")?;
258            let rc = func(
259                self.handle,
260                frame_id,
261                scan_begin,
262                scan_end,
263                centroid_trampoline,
264                std::ptr::null_mut(),
265            );
266            if rc == 0 {
267                return Err("tims_extract_centroided_spectrum_for_frame_v2 returned 0".into());
268            }
269        }
270        if let Some((mz, intens)) = EXTRACT_BUF_DATA.with(|buf| buf.borrow_mut().take()) {
271            result_mz = mz;
272            result_int = intens;
273        }
274        Ok((result_mz, result_int))
275    }
276
277    /// PASEF MS/MS centroided peaks for one MS2 frame.
278    /// SDK: tims_read_pasef_msms_for_frame_v2(handle, frame_id, callback, void**)
279    /// Bruker invokes the callback ONCE PER PRECURSOR found in the frame
280    /// (DDA-PASEF). We accumulate all (precursor_id, mz, intensity) hits.
281    pub fn tims_read_pasef_msms_for_frame(
282        &self,
283        frame_id: i64,
284    ) -> Result<Vec<(i64, Vec<f64>, Vec<f32>)>, Box<dyn std::error::Error>> {
285        let _guard = PASEF_BUF.lock().map_err(|_| "PASEF_BUF poisoned")?;
286        PASEF_BUF_DATA.with(|buf| *buf.borrow_mut() = Some(Vec::new()));
287        unsafe {
288            let func: Symbol<
289                unsafe extern "C" fn(
290                    u64, i64,
291                    extern "C" fn(i64, u32, *const f64, *const f32, *mut *mut std::ffi::c_void),
292                    *mut *mut std::ffi::c_void,
293                ) -> u32,
294            > = self.lib.get(b"tims_read_pasef_msms_for_frame_v2")?;
295            let rc = func(
296                self.handle,
297                frame_id,
298                pasef_trampoline,
299                std::ptr::null_mut(),
300            );
301            if rc == 0 {
302                return Err("tims_read_pasef_msms_for_frame_v2 returned 0".into());
303            }
304        }
305        let out = PASEF_BUF_DATA.with(|buf| buf.borrow_mut().take()).unwrap_or_default();
306        Ok(out)
307    }
308}
309
310// Globals for the C-callback trampolines. Locked on each extract call so
311// only one thread runs the SDK at a time per process — same restriction
312// pyTDFSDK + alphatims live with.
313static EXTRACT_BUF: Mutex<()> = Mutex::new(());
314thread_local! {
315    static EXTRACT_BUF_DATA: RefCell<Option<(Vec<f64>, Vec<f32>)>> = const { RefCell::new(None) };
316}
317
318extern "C" fn centroid_trampoline(
319    _precursor_id: i64,
320    n_peaks: u32,
321    mzs: *const f64,
322    intensities: *const f32,
323) {
324    if n_peaks == 0 || mzs.is_null() || intensities.is_null() { return; }
325    let mz_slice = unsafe { std::slice::from_raw_parts(mzs, n_peaks as usize) };
326    let in_slice = unsafe { std::slice::from_raw_parts(intensities, n_peaks as usize) };
327    EXTRACT_BUF_DATA.with(|buf| {
328        if let Some((ref mut mz_acc, ref mut in_acc)) = *buf.borrow_mut() {
329            mz_acc.extend_from_slice(mz_slice);
330            in_acc.extend_from_slice(in_slice);
331        }
332    });
333}
334
335static PASEF_BUF: Mutex<()> = Mutex::new(());
336thread_local! {
337    static PASEF_BUF_DATA: RefCell<Option<Vec<(i64, Vec<f64>, Vec<f32>)>>> = const { RefCell::new(None) };
338}
339
340extern "C" fn pasef_trampoline(
341    precursor_id: i64,
342    n_peaks: u32,
343    mzs: *const f64,
344    intensities: *const f32,
345    _user_data: *mut *mut std::ffi::c_void,
346) {
347    if n_peaks == 0 || mzs.is_null() || intensities.is_null() { return; }
348    let mz_slice = unsafe { std::slice::from_raw_parts(mzs, n_peaks as usize) };
349    let in_slice = unsafe { std::slice::from_raw_parts(intensities, n_peaks as usize) };
350    PASEF_BUF_DATA.with(|buf| {
351        if let Some(ref mut acc) = *buf.borrow_mut() {
352            acc.push((precursor_id, mz_slice.to_vec(), in_slice.to_vec()));
353        }
354    });
355}
356
357// Silence the unused `c_float` warning on platforms where it's only
358// touched by callbacks above.
359#[allow(dead_code)]
360const _: fn() = || { let _: c_float = 0.0; };
361
362impl Drop for BrukerTimsDataLibrary {
363    fn drop(&mut self) {
364        let close = self.tims_close();
365        match close {
366            Ok(_) => (),
367            Err(e) => println!("error: {}", e),
368        };
369    }
370}