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}