Skip to main content

rustdf/data/
raw.rs

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