Skip to main content

rustdf/data/
meta.rs

1extern crate rusqlite;
2
3use rusqlite::{Connection, Result};
4use std::path::Path;
5
6#[derive(Debug, Clone)]
7pub struct DiaMsMisInfo {
8    pub frame_id: u32,
9    pub window_group: u32,
10}
11
12#[derive(Debug, Clone)]
13pub struct DiaMsMsWindow {
14    pub window_group: u32,
15    pub scan_num_begin: u32,
16    pub scan_num_end: u32,
17    pub isolation_mz: f64,
18    pub isolation_width: f64,
19    pub collision_energy: f64,
20}
21
22#[derive(Debug, Clone)]
23pub struct PasefMsMsMeta {
24    pub frame_id: i64,
25    pub scan_num_begin: i64,
26    pub scan_num_end: i64,
27    pub isolation_mz: f64,
28    pub isolation_width: f64,
29    pub collision_energy: f64,
30    pub precursor_id: i64,
31}
32
33#[derive(Debug, Clone)]
34pub struct DDAPrecursorMeta {
35    pub precursor_id: i64,
36    pub precursor_mz_highest_intensity: f64,
37    pub precursor_mz_average: f64,
38    pub precursor_mz_monoisotopic: Option<f64>,
39    pub precursor_charge: Option<i64>,
40    pub precursor_average_scan_number: f64,
41    pub precursor_total_intensity: f64,
42    pub precursor_frame_id: i64,
43}
44
45#[derive(Debug, Clone)]
46pub struct DDAPrecursor {
47    pub frame_id: i64,
48    pub precursor_id: i64,
49    pub mono_mz: Option<f64>,
50    pub highest_intensity_mz: f64,
51    pub average_mz: f64,
52    pub charge: Option<i64>,
53    pub inverse_ion_mobility: f64,
54    pub collision_energy: f64,
55    pub precuror_total_intensity: f64,
56    pub isolation_mz: f64,
57    pub isolation_width: f64,
58}
59
60#[derive(Debug, Clone)]
61pub struct DDAFragmentInfo {
62    pub frame_id: i64,
63    pub scan_begin: i64,
64    pub scan_end: i64,
65    pub isolation_mz: f64,
66    pub isolation_width: f64,
67    pub collision_energy: f64,
68    pub precursor_id: i64,
69}
70
71pub struct DIAFragmentFrameInfo {}
72
73pub struct DIAWindowGroupInfo {}
74
75/// M/z calibration data from the MzCalibration table.
76/// Used for accurate TOF to m/z conversion without Bruker SDK.
77#[derive(Debug, Clone)]
78pub struct MzCalibration {
79    pub id: i64,
80    pub model_type: i64,
81    pub digitizer_timebase: f64,
82    pub digitizer_delay: f64,
83    pub t1: f64,
84    pub t2: f64,
85    pub dc1: f64,
86    pub dc2: f64,
87    pub c0: f64,
88    pub c1: f64,
89    pub c2: f64,
90    pub c3: f64,
91    pub c4: f64,
92}
93
94/// Ion-mobility (1/K0) calibration data from the `TimsCalibration` table.
95/// Coefficients feed the SDK-free scan <-> 1/K0 conversion (ModelType 2).
96#[derive(Debug, Clone)]
97pub struct TimsCalibration {
98    pub id: i64,
99    pub model_type: i64,
100    pub c0: f64,
101    pub c1: f64,
102    pub c2: f64,
103    pub c3: f64,
104    pub c4: f64,
105    pub c5: f64,
106    pub c6: f64,
107    pub c7: f64,
108    pub c8: f64,
109    pub c9: f64,
110}
111
112#[derive(Debug)]
113pub struct GlobalMetaData {
114    pub schema_type: String,
115    pub schema_version_major: i64,
116    pub schema_version_minor: i64,
117    pub acquisition_software_vendor: String,
118    pub instrument_vendor: String,
119    pub closed_property: i64,
120    pub tims_compression_type: i64,
121    pub max_num_peaks_per_scan: i64,
122    pub mz_acquisition_range_lower: f64,
123    pub mz_acquisition_range_upper: f64,
124    pub one_over_k0_range_lower: f64,
125    pub one_over_k0_range_upper: f64,
126    pub tof_max_index: u32,
127}
128
129#[derive(Debug)]
130pub struct FrameMeta {
131    pub id: i64,
132    pub time: f64,
133    pub polarity: String,
134    pub scan_mode: i64,
135    pub ms_ms_type: i64,
136    pub tims_id: i64,
137    pub max_intensity: f64,
138    pub sum_intensity: f64,
139    pub num_scans: i64,
140    pub num_peaks: i64,
141    pub mz_calibration: i64,
142    pub t_1: f64,
143    pub t_2: f64,
144    pub tims_calibration: i64,
145    pub property_group: i64,
146    pub accumulation_time: f64,
147    pub ramp_time: f64,
148}
149
150struct GlobalMetaInternal {
151    key: String,
152    value: String,
153}
154
155pub fn read_dda_precursor_meta(
156    bruker_d_folder_name: &str,
157) -> Result<Vec<DDAPrecursorMeta>, Box<dyn std::error::Error>> {
158    // Connect to the database
159    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
160    let conn = Connection::open(db_path)?;
161
162    // prepare the query
163    let rows: Vec<&str> = vec![
164        "Id",
165        "LargestPeakMz",
166        "AverageMz",
167        "MonoisotopicMz",
168        "Charge",
169        "ScanNumber",
170        "Intensity",
171        "Parent",
172    ];
173    let query = format!("SELECT {} FROM Precursors", rows.join(", "));
174
175    // execute the query
176    let frames_rows: Result<Vec<DDAPrecursorMeta>, _> = conn
177        .prepare(&query)?
178        .query_map([], |row| {
179            Ok(DDAPrecursorMeta {
180                precursor_id: row.get(0)?,
181                precursor_mz_highest_intensity: row.get(1)?,
182                precursor_mz_average: row.get(2)?,
183                precursor_mz_monoisotopic: row.get(3)?, // Now using Option<f64>
184                precursor_charge: row.get(4)?,          // Now using Option<i64>
185                precursor_average_scan_number: row.get(5)?,
186                precursor_total_intensity: row.get(6)?,
187                precursor_frame_id: row.get(7)?,
188            })
189        })?
190        .collect();
191
192    // return the frames
193    Ok(frames_rows?)
194}
195
196pub fn read_pasef_frame_ms_ms_info(
197    bruker_d_folder_name: &str,
198) -> Result<Vec<PasefMsMsMeta>, Box<dyn std::error::Error>> {
199    // Connect to the database
200    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
201    let conn = Connection::open(db_path)?;
202
203    // prepare the query
204    let rows: Vec<&str> = vec![
205        "Frame",
206        "ScanNumBegin",
207        "ScanNumEnd",
208        "IsolationMz",
209        "IsolationWidth",
210        "CollisionEnergy",
211        "Precursor",
212    ];
213    let query = format!("SELECT {} FROM PasefFrameMsMsInfo", rows.join(", "));
214
215    // execute the query
216    let frames_rows: Result<Vec<PasefMsMsMeta>, _> = conn
217        .prepare(&query)?
218        .query_map([], |row| {
219            Ok(PasefMsMsMeta {
220                frame_id: row.get(0)?,
221                scan_num_begin: row.get(1)?,
222                scan_num_end: row.get(2)?,
223                isolation_mz: row.get(3)?,
224                isolation_width: row.get(4)?,
225                collision_energy: row.get(5)?,
226                precursor_id: row.get(6)?,
227            })
228        })?
229        .collect();
230
231    // return the frames
232    Ok(frames_rows?)
233}
234
235// Read the global meta data from the analysis.tdf file
236pub fn read_global_meta_sql(
237    bruker_d_folder_name: &str,
238) -> Result<GlobalMetaData, Box<dyn std::error::Error>> {
239    // Connect to the database
240    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
241    let conn = Connection::open(db_path)?;
242
243    // execute the query
244    let frames_rows: Result<Vec<GlobalMetaInternal>, _> = conn
245        .prepare("SELECT * FROM GlobalMetadata")?
246        .query_map([], |row| {
247            Ok(GlobalMetaInternal {
248                key: row.get(0)?,
249                value: row.get(1)?,
250            })
251        })?
252        .collect();
253
254    let mut global_meta = GlobalMetaData {
255        schema_type: String::new(),
256        schema_version_major: -1,
257        schema_version_minor: -1,
258        acquisition_software_vendor: String::new(),
259        instrument_vendor: String::new(),
260        closed_property: -1,
261        tims_compression_type: -1,
262        max_num_peaks_per_scan: -1,
263        mz_acquisition_range_lower: -1.0,
264        mz_acquisition_range_upper: -1.0,
265        one_over_k0_range_lower: -1.0,
266        one_over_k0_range_upper: -1.0,
267        tof_max_index: 0,
268    };
269
270    // go over the keys and parse values for the global meta data
271    for row in frames_rows? {
272        match row.key.as_str() {
273            "SchemaType" => global_meta.schema_type = row.value,
274            "SchemaVersionMajor" => {
275                global_meta.schema_version_major = row.value.parse::<i64>().unwrap()
276            }
277            "SchemaVersionMinor" => {
278                global_meta.schema_version_minor = row.value.parse::<i64>().unwrap()
279            }
280            "AcquisitionSoftwareVendor" => global_meta.acquisition_software_vendor = row.value,
281            "InstrumentVendor" => global_meta.instrument_vendor = row.value,
282            "ClosedProperly" => global_meta.closed_property = row.value.parse::<i64>().unwrap(),
283            "TimsCompressionType" => {
284                global_meta.tims_compression_type = row.value.parse::<i64>().unwrap()
285            }
286            "MaxNumPeaksPerScan" => {
287                global_meta.max_num_peaks_per_scan = row.value.parse::<i64>().unwrap()
288            }
289            "MzAcqRangeLower" => {
290                global_meta.mz_acquisition_range_lower = row.value.parse::<f64>().unwrap()
291            }
292            "MzAcqRangeUpper" => {
293                global_meta.mz_acquisition_range_upper = row.value.parse::<f64>().unwrap()
294            }
295            "OneOverK0AcqRangeLower" => {
296                global_meta.one_over_k0_range_lower = row.value.parse::<f64>().unwrap()
297            }
298            "OneOverK0AcqRangeUpper" => {
299                global_meta.one_over_k0_range_upper = row.value.parse::<f64>().unwrap()
300            }
301            "DigitizerNumSamples" => {
302                global_meta.tof_max_index = (row.value.parse::<i64>().unwrap() + 1) as u32
303            }
304            _ => (),
305        }
306    }
307    // return global_meta
308    Ok(global_meta)
309}
310
311// Read the frame meta data from the analysis.tdf file
312pub fn read_meta_data_sql(
313    bruker_d_folder_name: &str,
314) -> Result<Vec<FrameMeta>, Box<dyn std::error::Error>> {
315    // Connect to the database
316    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
317    let conn = Connection::open(db_path)?;
318
319    // prepare the query
320    let rows: Vec<&str> = vec![
321        "Id",
322        "Time",
323        "ScanMode",
324        "Polarity",
325        "MsMsType",
326        "TimsId",
327        "MaxIntensity",
328        "SummedIntensities",
329        "NumScans",
330        "NumPeaks",
331        "MzCalibration",
332        "T1",
333        "T2",
334        "TimsCalibration",
335        "PropertyGroup",
336        "AccumulationTime",
337        "RampTime",
338    ];
339    let query = format!("SELECT {} FROM Frames", rows.join(", "));
340
341    // execute the query
342    let frames_rows: Result<Vec<FrameMeta>, _> = conn
343        .prepare(&query)?
344        .query_map([], |row| {
345            Ok(FrameMeta {
346                id: row.get(0)?,
347                time: row.get(1)?,
348                scan_mode: row.get(2)?,
349                polarity: row.get(3)?,
350                ms_ms_type: row.get(4)?,
351                tims_id: row.get(5)?,
352                max_intensity: row.get(6)?,
353                sum_intensity: row.get(7)?,
354                num_scans: row.get(8)?,
355                num_peaks: row.get(9)?,
356                mz_calibration: row.get(10)?,
357                t_1: row.get(11)?,
358                t_2: row.get(12)?,
359                tims_calibration: row.get(13)?,
360                property_group: row.get(14)?,
361                accumulation_time: row.get(15)?,
362                ramp_time: row.get(16)?,
363            })
364        })?
365        .collect();
366
367    // return the frames
368    Ok(frames_rows?)
369}
370
371pub fn read_dia_ms_ms_info(
372    bruker_d_folder_name: &str,
373) -> Result<Vec<DiaMsMisInfo>, Box<dyn std::error::Error>> {
374    // Connect to the database
375    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
376    let conn = Connection::open(db_path)?;
377
378    // prepare the query
379    let rows: Vec<&str> = vec!["Frame", "WindowGroup"];
380    let query = format!("SELECT {} FROM DiaFrameMsMsInfo", rows.join(", "));
381
382    // execute the query
383    let frames_rows: Result<Vec<DiaMsMisInfo>, _> = conn
384        .prepare(&query)?
385        .query_map([], |row| {
386            Ok(DiaMsMisInfo {
387                frame_id: row.get(0)?,
388                window_group: row.get(1)?,
389            })
390        })?
391        .collect();
392
393    // return the frames
394    Ok(frames_rows?)
395}
396
397pub fn read_dia_ms_ms_windows(
398    bruker_d_folder_name: &str,
399) -> Result<Vec<DiaMsMsWindow>, Box<dyn std::error::Error>> {
400    // Connect to the database
401    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
402    let conn = Connection::open(db_path)?;
403
404    // prepare the query
405    let rows: Vec<&str> = vec![
406        "WindowGroup",
407        "ScanNumBegin",
408        "ScanNumEnd",
409        "IsolationMz",
410        "IsolationWidth",
411        "CollisionEnergy",
412    ];
413    let query = format!("SELECT {} FROM DiaFrameMsMsWindows", rows.join(", "));
414
415    // execute the query
416    let frames_rows: Result<Vec<DiaMsMsWindow>, _> = conn
417        .prepare(&query)?
418        .query_map([], |row| {
419            Ok(DiaMsMsWindow {
420                window_group: row.get(0)?,
421                scan_num_begin: row.get(1)?,
422                scan_num_end: row.get(2)?,
423                isolation_mz: row.get(3)?,
424                isolation_width: row.get(4)?,
425                collision_energy: row.get(5)?,
426            })
427        })?
428        .collect();
429
430    // return the frames
431    Ok(frames_rows?)
432}
433
434/// Read m/z calibration data from the MzCalibration table.
435/// This provides the coefficients needed for accurate TOF to m/z conversion
436/// without requiring the Bruker SDK.
437///
438/// The calibration curve expresses flight time as a function of mass:
439///   tof_time = tof_index * digitizer_timebase + digitizer_delay
440///   tof_time = c0 + b*sqrt(m) + c2*m + c3*m^1.5,   b = sqrt(1e12 / c1)
441/// inverted to give m/z from a TOF index (see `data::calibration::MzCalibrator`).
442///
443/// Model 2 (modern instruments) uses the same `c0 + b*sqrt(m) + c2*m` curve
444/// (c2 IS a curve term; only the c3 cubic term is model-1 only). That base is
445/// accurate to a few ppm; the remaining error is a proprietary correction in
446/// c8..c14 (windowed to [c5, c6]) that is not modelled.
447pub fn read_mz_calibration(
448    bruker_d_folder_name: &str,
449) -> Result<Vec<MzCalibration>, Box<dyn std::error::Error>> {
450    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
451    let conn = Connection::open(db_path)?;
452
453    // Query MzCalibration table
454    let query = "SELECT Id, ModelType, DigitizerTimebase, DigitizerDelay, T1, T2, dC1, dC2, C0, C1, C2, C3, C4 FROM MzCalibration";
455
456    let calibrations: Result<Vec<MzCalibration>, _> = conn
457        .prepare(query)?
458        .query_map([], |row| {
459            Ok(MzCalibration {
460                id: row.get(0)?,
461                model_type: row.get(1)?,
462                digitizer_timebase: row.get(2)?,
463                digitizer_delay: row.get(3)?,
464                t1: row.get(4)?,
465                t2: row.get(5)?,
466                dc1: row.get(6)?,
467                dc2: row.get(7)?,
468                c0: row.get(8)?,
469                c1: row.get(9)?,
470                c2: row.get(10)?,
471                c3: row.get(11)?,
472                c4: row.get(12)?,
473            })
474        })?
475        .collect();
476
477    Ok(calibrations?)
478}
479
480/// Read ion-mobility calibration data from the `TimsCalibration` table.
481/// Provides the C0..C9 coefficients for SDK-free scan <-> 1/K0 conversion.
482pub fn read_tims_calibration(
483    bruker_d_folder_name: &str,
484) -> Result<Vec<TimsCalibration>, Box<dyn std::error::Error>> {
485    let db_path = Path::new(bruker_d_folder_name).join("analysis.tdf");
486    let conn = Connection::open(db_path)?;
487
488    let query =
489        "SELECT Id, ModelType, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9 FROM TimsCalibration";
490
491    let calibrations: Result<Vec<TimsCalibration>, _> = conn
492        .prepare(query)?
493        .query_map([], |row| {
494            Ok(TimsCalibration {
495                id: row.get(0)?,
496                model_type: row.get(1)?,
497                c0: row.get(2)?,
498                c1: row.get(3)?,
499                c2: row.get(4)?,
500                c3: row.get(5)?,
501                c4: row.get(6)?,
502                c5: row.get(7)?,
503                c6: row.get(8)?,
504                c7: row.get(9)?,
505                c8: row.get(10)?,
506                c9: row.get(11)?,
507            })
508        })?
509        .collect();
510
511    Ok(calibrations?)
512}