1use crate::data::meta::{read_global_meta_sql, read_meta_data_sql, FrameMeta, GlobalMetaData};
2use crate::data::raw::BrukerTimsDataLibrary;
3use crate::data::utility::{
4 flatten_scan_values, parse_decompressed_bruker_binary_data, zstd_decompress,
5};
6use byteorder::{LittleEndian, ReadBytesExt};
7use mscore::data::spectrum::MsType;
8use mscore::timstof::frame::{ImsFrame, RawTimsFrame, TimsFrame};
9use mscore::timstof::slice::TimsSlice;
10use std::fs::File;
11use std::io::{Cursor, Read, Seek, SeekFrom};
12use std::path::PathBuf;
13
14use crate::data::acquisition::AcquisitionMode;
15use rayon::prelude::*;
16use rayon::ThreadPoolBuilder;
17
18use std::error::Error;
19
20fn derive_mz_calibration(
28 bruker_lib_path: &str,
29 data_path: &str,
30 tof_max_index: u32,
31) -> Option<(f64, f64)> {
32 if bruker_lib_path.is_empty()
35 || bruker_lib_path == "NO_SDK"
36 || bruker_lib_path == "CALIBRATED"
37 || !std::path::Path::new(bruker_lib_path).exists()
38 {
39 return None;
40 }
41
42 let sdk_converter = match BrukerLibTimsDataConverter::try_new(bruker_lib_path, data_path) {
47 Ok(converter) => converter,
48 Err(_) => return None,
49 };
50
51 let n_points = 1000;
53 let step = tof_max_index / n_points;
54 let tof_indices: Vec<u32> = (0..n_points).map(|i| i * step + step / 2).collect();
55
56 let mz_values = sdk_converter.tof_to_mz(1, &tof_indices);
58
59 let valid_pairs: Vec<(f64, f64)> = tof_indices
61 .iter()
62 .zip(mz_values.iter())
63 .filter(|(_, mz)| **mz > 0.0)
64 .map(|(tof, mz)| (*tof as f64, mz.sqrt()))
65 .collect();
66
67 if valid_pairs.len() < 10 {
68 return None;
69 }
70
71 let n = valid_pairs.len() as f64;
74 let sum_x: f64 = valid_pairs.iter().map(|(x, _)| x).sum();
75 let sum_y: f64 = valid_pairs.iter().map(|(_, y)| y).sum();
76 let sum_xy: f64 = valid_pairs.iter().map(|(x, y)| x * y).sum();
77 let sum_xx: f64 = valid_pairs.iter().map(|(x, _)| x * x).sum();
78
79 let mean_x = sum_x / n;
80 let mean_y = sum_y / n;
81
82 let slope = (sum_xy - n * mean_x * mean_y) / (sum_xx - n * mean_x * mean_x);
83 let intercept = mean_y - slope * mean_x;
84
85 Some((intercept, slope))
86}
87
88fn build_lookup_converter(
98 bruker_lib_path: &str,
99 data_path: &str,
100 tof_max_index: u32,
101 mz_lower: f64,
102 mz_upper: f64,
103 im_lookup: Vec<f64>,
104) -> LookupIndexConverter {
105 match derive_mz_calibration(bruker_lib_path, data_path, tof_max_index) {
106 Some((intercept, slope)) => {
107 LookupIndexConverter::with_mz_fit(intercept, slope, im_lookup)
108 }
109 None => {
110 if !bruker_lib_path.is_empty() && bruker_lib_path != "NO_SDK" {
111 eprintln!(
112 "Warning: Could not derive m/z calibration from SDK at '{}'. \
113 Falling back to the 2-point boundary model, which may have a \
114 large m/z error on some datasets.",
115 bruker_lib_path
116 );
117 }
118 LookupIndexConverter::new(mz_lower, mz_upper, tof_max_index, im_lookup)
119 }
120 }
121}
122
123fn lzf_decompress(data: &[u8], max_output_size: usize) -> Result<Vec<u8>, Box<dyn Error>> {
124 let decompressed_data = lzf::decompress(data, max_output_size)
125 .map_err(|e| format!("LZF decompression failed: {}", e))?;
126 Ok(decompressed_data)
127}
128
129fn parse_decompressed_bruker_binary_type1(
130 decompressed_bytes: &[u8],
131 scan_indices: &mut [i64],
132 tof_indices: &mut [u32],
133 intensities: &mut [u16],
134 scan_start: usize,
135 scan_index: usize,
136) -> usize {
137 let int_count = decompressed_bytes.len() / 4;
139 let buffer =
140 unsafe { std::slice::from_raw_parts(decompressed_bytes.as_ptr() as *const i32, int_count) };
141
142 let mut tof_index = 0i32;
143 let mut previous_was_intensity = true;
144 let mut current_index = scan_start;
145
146 for &value in buffer {
147 if value >= 0 {
148 if previous_was_intensity {
150 tof_index += 1;
151 }
152 tof_indices[current_index] = tof_index as u32;
153 intensities[current_index] = value as u16;
154 previous_was_intensity = true;
155 current_index += 1;
156 } else {
157 tof_index -= value; previous_was_intensity = false;
160 }
161 }
162
163 let scan_size = current_index - scan_start;
164 scan_indices[scan_index] = scan_size as i64;
165 scan_size
166}
167
168pub struct TimsRawDataLayout {
169 pub raw_data_path: String,
170 pub global_meta_data: GlobalMetaData,
171 pub frame_meta_data: Vec<FrameMeta>,
172 pub max_scan_count: i64,
173 pub frame_id_ptr: Vec<i64>,
174 pub tims_offset_values: Vec<i64>,
175 pub acquisition_mode: AcquisitionMode,
176}
177
178impl TimsRawDataLayout {
179 pub fn new(data_path: &str) -> Self {
180 let global_meta_data = read_global_meta_sql(data_path).unwrap();
182 let frame_meta_data = read_meta_data_sql(data_path).unwrap();
183
184 let max_scan_count = frame_meta_data.iter().map(|x| x.num_scans).max().unwrap();
186
187 let mut frame_id_ptr: Vec<i64> = Vec::new();
188 frame_id_ptr.resize(frame_meta_data.len() + 1, 0);
189
190 for (i, row) in frame_meta_data.iter().enumerate() {
192 frame_id_ptr[i + 1] = row.num_peaks + frame_id_ptr[i];
193 }
194
195 let tims_offset_values = frame_meta_data
197 .iter()
198 .map(|x| x.tims_id)
199 .collect::<Vec<i64>>();
200
201 let acquisition_mode = match frame_meta_data[0].scan_mode {
203 8 => AcquisitionMode::DDA,
204 9 => AcquisitionMode::DIA,
205 _ => AcquisitionMode::Unknown,
206 };
207
208 TimsRawDataLayout {
209 raw_data_path: data_path.to_string(),
210 global_meta_data,
211 frame_meta_data,
212 max_scan_count,
213 frame_id_ptr,
214 tims_offset_values,
215 acquisition_mode,
216 }
217 }
218}
219
220pub trait TimsData {
221 fn get_frame(&self, frame_id: u32) -> TimsFrame;
222 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame;
223 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice;
224 fn get_acquisition_mode(&self) -> AcquisitionMode;
225 fn get_frame_count(&self) -> i32;
226 fn get_data_path(&self) -> &str;
227}
228
229pub trait IndexConverter {
230 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64>;
231 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32>;
232 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64>;
233 fn inverse_mobility_to_scan(
234 &self,
235 frame_id: u32,
236 inverse_mobility_values: &Vec<f64>,
237 ) -> Vec<u32>;
238}
239
240pub struct BrukerLibTimsDataConverter {
241 pub bruker_lib: BrukerTimsDataLibrary,
242}
243
244impl BrukerLibTimsDataConverter {
245 pub fn new(bruker_lib_path: &str, data_path: &str) -> Self {
246 Self::try_new(bruker_lib_path, data_path).unwrap()
247 }
248 pub fn try_new(
252 bruker_lib_path: &str,
253 data_path: &str,
254 ) -> Result<Self, Box<dyn std::error::Error>> {
255 let bruker_lib = BrukerTimsDataLibrary::new(bruker_lib_path, data_path)?;
256 Ok(BrukerLibTimsDataConverter { bruker_lib })
257 }
258}
259impl IndexConverter for BrukerLibTimsDataConverter {
260 fn tof_to_mz(&self, frame_id: u32, tof: &Vec<u32>) -> Vec<f64> {
272 let mut dbl_tofs: Vec<f64> = Vec::new();
273 dbl_tofs.resize(tof.len(), 0.0);
274
275 for (i, &val) in tof.iter().enumerate() {
276 dbl_tofs[i] = val as f64;
277 }
278
279 let mut mz_values: Vec<f64> = Vec::new();
280 mz_values.resize(tof.len(), 0.0);
281
282 self.bruker_lib
283 .tims_index_to_mz(frame_id, &dbl_tofs, &mut mz_values)
284 .expect("Bruker binary call failed at: tims_index_to_mz;");
285
286 mz_values
287 }
288
289 fn mz_to_tof(&self, frame_id: u32, mz: &Vec<f64>) -> Vec<u32> {
290 let mut dbl_mz: Vec<f64> = Vec::new();
291 dbl_mz.resize(mz.len(), 0.0);
292
293 for (i, &val) in mz.iter().enumerate() {
294 dbl_mz[i] = val;
295 }
296
297 let mut tof_values: Vec<f64> = Vec::new();
298 tof_values.resize(mz.len(), 0.0);
299
300 self.bruker_lib
301 .tims_mz_to_index(frame_id, &dbl_mz, &mut tof_values)
302 .expect("Bruker binary call failed at: tims_mz_to_index;");
303
304 tof_values.iter().map(|&x| x.round() as u32).collect()
305 }
306
307 fn scan_to_inverse_mobility(&self, frame_id: u32, scan: &Vec<u32>) -> Vec<f64> {
319 let mut dbl_scans: Vec<f64> = Vec::new();
320 dbl_scans.resize(scan.len(), 0.0);
321
322 for (i, &val) in scan.iter().enumerate() {
323 dbl_scans[i] = val as f64;
324 }
325
326 let mut inv_mob: Vec<f64> = Vec::new();
327 inv_mob.resize(scan.len(), 0.0);
328
329 self.bruker_lib
330 .tims_scan_to_inv_mob(frame_id, &dbl_scans, &mut inv_mob)
331 .expect("Bruker binary call failed at: tims_scannum_to_oneoverk0;");
332
333 inv_mob
334 }
335
336 fn inverse_mobility_to_scan(&self, frame_id: u32, inv_mob: &Vec<f64>) -> Vec<u32> {
348 let mut dbl_inv_mob: Vec<f64> = Vec::new();
349 dbl_inv_mob.resize(inv_mob.len(), 0.0);
350
351 for (i, &val) in inv_mob.iter().enumerate() {
352 dbl_inv_mob[i] = val;
353 }
354
355 let mut scan_values: Vec<f64> = Vec::new();
356 scan_values.resize(inv_mob.len(), 0.0);
357
358 self.bruker_lib
359 .inv_mob_to_tims_scan(frame_id, &dbl_inv_mob, &mut scan_values)
360 .expect("Bruker binary call failed at: tims_oneoverk0_to_scannum;");
361
362 scan_values.iter().map(|&x| x.round() as u32).collect()
363 }
364}
365
366pub struct BrukerFormulaConverter {
390 pub mz: crate::data::calibration::MzCalibrator,
392 pub im: crate::data::calibration::MobilityCalibrator,
394 pub mz_model_type: i64,
396 mz_rows: std::collections::HashMap<i64, crate::data::meta::MzCalibration>,
398 im_rows: std::collections::HashMap<i64, crate::data::calibration::MobilityCalibrator>,
400 frames: std::collections::HashMap<u32, FrameCalibrationRef>,
402 temp_spread: (f64, f64),
404}
405
406struct FrameCalibrationRef {
408 mz_id: i64,
409 tims_id: i64,
410 t1: f64,
411 t2: f64,
412}
413
414impl BrukerFormulaConverter {
415 pub fn from_d_folder(
422 data_path: &str,
423 fallback_frame_id: u32,
424 ) -> Result<Self, Box<dyn std::error::Error>> {
425 use crate::data::calibration::{MobilityCalibrator, MzCalibrator};
426 use crate::data::meta::{read_mz_calibration, read_tims_calibration};
427 use std::collections::HashMap;
428
429 let tdf = PathBuf::from(data_path).join("analysis.tdf");
430 let con = rusqlite::Connection::open(&tdf)?;
431
432 let mut stmt =
436 con.prepare("SELECT Id, T1, T2, MzCalibration, TimsCalibration FROM Frames")?;
437 let rows = stmt.query_map([], |r| {
438 Ok((
439 r.get::<_, i64>(0)?,
440 r.get::<_, Option<f64>>(1)?,
441 r.get::<_, Option<f64>>(2)?,
442 r.get::<_, i64>(3)?,
443 r.get::<_, i64>(4)?,
444 ))
445 })?;
446
447 let mut frames: HashMap<u32, FrameCalibrationRef> = HashMap::new();
448 let (mut t1_min, mut t1_max) = (f64::INFINITY, f64::NEG_INFINITY);
449 let (mut t2_min, mut t2_max) = (f64::INFINITY, f64::NEG_INFINITY);
450 for row in rows {
451 let (id, t1, t2, mz_id, tims_id) = row?;
452 let (t1, t2) = match (t1, t2) {
453 (Some(t1), Some(t2)) => (t1, t2),
454 _ => continue,
455 };
456 t1_min = t1_min.min(t1);
457 t1_max = t1_max.max(t1);
458 t2_min = t2_min.min(t2);
459 t2_max = t2_max.max(t2);
460 frames.insert(
461 id as u32,
462 FrameCalibrationRef {
463 mz_id,
464 tims_id,
465 t1,
466 t2,
467 },
468 );
469 }
470 drop(stmt);
471
472 let fallback = frames
473 .get(&fallback_frame_id)
474 .ok_or("fallback frame has no calibration/temperature row in Frames")?;
475
476 let mz_all = read_mz_calibration(data_path)?;
477 let tims_all = read_tims_calibration(data_path)?;
478
479 let mzc = mz_all
480 .iter()
481 .find(|c| c.id == fallback.mz_id)
482 .ok_or("MzCalibration row not found")?;
483 let tc = tims_all
484 .iter()
485 .find(|c| c.id == fallback.tims_id)
486 .ok_or("TimsCalibration row not found")?;
487
488 if mzc.model_type != 1 && mzc.model_type != 2 {
491 return Err(format!("unsupported MzCalibration ModelType {}", mzc.model_type).into());
492 }
493 if tc.model_type != 2 {
494 return Err(format!("unsupported TimsCalibration ModelType {}", tc.model_type).into());
495 }
496
497 let mz = MzCalibrator::from_calibration(mzc, fallback.t1, fallback.t2);
498 let im = MobilityCalibrator::from_calibration(tc);
499 let mz_model_type = mzc.model_type;
500
501 let mz_rows: HashMap<i64, crate::data::meta::MzCalibration> = mz_all
504 .into_iter()
505 .filter(|c| c.model_type == 1 || c.model_type == 2)
506 .map(|c| (c.id, c))
507 .collect();
508 let im_rows: HashMap<i64, MobilityCalibrator> = tims_all
509 .iter()
510 .filter(|c| c.model_type == 2)
511 .map(|c| (c.id, MobilityCalibrator::from_calibration(c)))
512 .collect();
513
514 let temp_spread = if frames.is_empty() {
515 (0.0, 0.0)
516 } else {
517 (t1_max - t1_min, t2_max - t2_min)
518 };
519
520 Ok(Self {
521 mz,
522 im,
523 mz_model_type,
524 mz_rows,
525 im_rows,
526 frames,
527 temp_spread,
528 })
529 }
530
531 pub fn temperature_spread(&self) -> (f64, f64) {
537 self.temp_spread
538 }
539
540 fn mz_for(&self, frame_id: u32) -> crate::data::calibration::MzCalibrator {
542 self.frames
543 .get(&frame_id)
544 .and_then(|f| {
545 self.mz_rows.get(&f.mz_id).map(|row| {
546 crate::data::calibration::MzCalibrator::from_calibration(row, f.t1, f.t2)
547 })
548 })
549 .unwrap_or_else(|| self.mz.clone())
550 }
551
552 fn im_for(&self, frame_id: u32) -> &crate::data::calibration::MobilityCalibrator {
554 self.frames
555 .get(&frame_id)
556 .and_then(|f| self.im_rows.get(&f.tims_id))
557 .unwrap_or(&self.im)
558 }
559}
560
561impl IndexConverter for BrukerFormulaConverter {
562 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
563 let mz = self.mz_for(frame_id);
564 tof_values.iter().map(|&t| mz.tof_to_mz(t)).collect()
565 }
566 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
567 let mz = self.mz_for(frame_id);
568 mz_values.iter().map(|&m| mz.mz_to_tof(m)).collect()
569 }
570 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
571 let im = self.im_for(frame_id);
572 scan_values
573 .iter()
574 .map(|&s| im.scan_to_one_over_k0(s))
575 .collect()
576 }
577 fn inverse_mobility_to_scan(
578 &self,
579 frame_id: u32,
580 inverse_mobility_values: &Vec<f64>,
581 ) -> Vec<u32> {
582 let im = self.im_for(frame_id);
583 inverse_mobility_values
584 .iter()
585 .map(|&v| im.one_over_k0_to_scan(v))
586 .collect()
587 }
588}
589
590pub enum TimsIndexConverter {
591 Simple(SimpleIndexConverter),
592 Calibrated(CalibratedIndexConverter),
593 BrukerLib(BrukerLibTimsDataConverter),
594 Lookup(LookupIndexConverter),
595 BrukerFormula(BrukerFormulaConverter),
596}
597
598impl IndexConverter for TimsIndexConverter {
599 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
600 match self {
601 TimsIndexConverter::Simple(converter) => converter.tof_to_mz(frame_id, tof_values),
602 TimsIndexConverter::Calibrated(converter) => converter.tof_to_mz(frame_id, tof_values),
603 TimsIndexConverter::BrukerLib(converter) => converter.tof_to_mz(frame_id, tof_values),
604 TimsIndexConverter::Lookup(converter) => converter.tof_to_mz(frame_id, tof_values),
605 TimsIndexConverter::BrukerFormula(converter) => converter.tof_to_mz(frame_id, tof_values),
606 }
607 }
608
609 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
610 match self {
611 TimsIndexConverter::Simple(converter) => converter.mz_to_tof(frame_id, mz_values),
612 TimsIndexConverter::Calibrated(converter) => converter.mz_to_tof(frame_id, mz_values),
613 TimsIndexConverter::BrukerLib(converter) => converter.mz_to_tof(frame_id, mz_values),
614 TimsIndexConverter::Lookup(converter) => converter.mz_to_tof(frame_id, mz_values),
615 TimsIndexConverter::BrukerFormula(converter) => converter.mz_to_tof(frame_id, mz_values),
616 }
617 }
618
619 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
620 match self {
621 TimsIndexConverter::Simple(converter) => {
622 converter.scan_to_inverse_mobility(frame_id, scan_values)
623 }
624 TimsIndexConverter::Calibrated(converter) => {
625 converter.scan_to_inverse_mobility(frame_id, scan_values)
626 }
627 TimsIndexConverter::BrukerLib(converter) => {
628 converter.scan_to_inverse_mobility(frame_id, scan_values)
629 }
630 TimsIndexConverter::Lookup(converter) => {
631 converter.scan_to_inverse_mobility(frame_id, scan_values)
632 }
633 TimsIndexConverter::BrukerFormula(converter) => {
634 converter.scan_to_inverse_mobility(frame_id, scan_values)
635 }
636 }
637 }
638
639 fn inverse_mobility_to_scan(
640 &self,
641 frame_id: u32,
642 inverse_mobility_values: &Vec<f64>,
643 ) -> Vec<u32> {
644 match self {
645 TimsIndexConverter::Simple(converter) => {
646 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
647 }
648 TimsIndexConverter::Calibrated(converter) => {
649 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
650 }
651 TimsIndexConverter::BrukerLib(converter) => {
652 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
653 }
654 TimsIndexConverter::Lookup(converter) => {
655 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
656 }
657 TimsIndexConverter::BrukerFormula(converter) => {
658 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
659 }
660 }
661 }
662}
663
664pub struct TimsLazyLoder {
665 pub raw_data_layout: TimsRawDataLayout,
666 pub index_converter: TimsIndexConverter,
667}
668
669impl TimsData for TimsLazyLoder {
670 fn get_frame(&self, frame_id: u32) -> TimsFrame {
671 let frame_index = (frame_id - 1) as usize;
672
673 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
675
676 if num_peaks == 0 {
677
678 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
679
680 let ms_type = match ms_type_raw {
681 0 => MsType::Precursor,
682 8 => MsType::FragmentDda,
683 9 => MsType::FragmentDia,
684 _ => MsType::Unknown,
685 };
686
687 return TimsFrame {
688 frame_id: frame_id as i32,
689 ms_type,
690 scan: Vec::new(),
691 tof: Vec::new(),
692 ims_frame: ImsFrame::new(
693 self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
694 Vec::new(),
695 Vec::new(),
696 Vec::new(),
697 ),
698 };
699 }
700
701 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
702
703 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
704 file_path.push("analysis.tdf_bin");
705 let mut infile = File::open(&file_path).unwrap();
706
707 infile.seek(SeekFrom::Start(offset)).unwrap();
708
709 let mut bin_buffer = [0u8; 4];
710 infile.read_exact(&mut bin_buffer).unwrap();
711 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
712
713 infile.read_exact(&mut bin_buffer).unwrap();
714
715 match self.raw_data_layout.global_meta_data.tims_compression_type {
716 1 => {
717 let scan_count =
718 self.raw_data_layout.frame_meta_data[frame_index].num_scans as usize;
719 let num_peaks = num_peaks as usize;
720 let compression_offset = 8 + (scan_count + 1) * 4;
721
722 let mut scan_offsets_buffer = vec![0u8; (scan_count + 1) * 4];
723 infile.read_exact(&mut scan_offsets_buffer).unwrap();
724
725 let mut scan_offsets = Vec::with_capacity(scan_count + 1);
726 {
727 let mut rdr = Cursor::new(&scan_offsets_buffer);
728 for _ in 0..(scan_count + 1) {
729 scan_offsets.push(rdr.read_i32::<LittleEndian>().unwrap());
730 }
731 }
732
733 for offs in &mut scan_offsets {
734 *offs -= compression_offset as i32;
735 }
736
737 let remaining_size = (bin_size as usize - compression_offset) as usize;
738 let mut compressed_data = vec![0u8; remaining_size];
739 infile.read_exact(&mut compressed_data).unwrap();
740
741 let mut scan_indices_ = vec![0i64; scan_count];
742 let mut tof_indices_ = vec![0u32; num_peaks];
743 let mut intensities_ = vec![0u16; num_peaks];
744
745 let mut scan_start = 0usize;
746
747 for scan_index in 0..scan_count {
748 let start = scan_offsets[scan_index] as usize;
749 let end = scan_offsets[scan_index + 1] as usize;
750
751 if start == end {
752 continue;
753 }
754
755 let max_output_size = num_peaks * 8;
756 let decompressed_bytes =
757 lzf_decompress(&compressed_data[start..end], max_output_size)
758 .expect("LZF decompression failed.");
759
760 scan_start += parse_decompressed_bruker_binary_type1(
761 &decompressed_bytes,
762 &mut scan_indices_,
763 &mut tof_indices_,
764 &mut intensities_,
765 scan_start,
766 scan_index,
767 );
768 }
769
770 let mut scan = Vec::with_capacity(num_peaks);
772 {
773 let mut current_scan_index = 0u32;
774 for &size in &scan_indices_ {
775 let sz = size as usize;
776 for _ in 0..sz {
777 scan.push(current_scan_index);
778 }
779 current_scan_index += 1;
780 }
781 }
782
783 let intensity_dbl = intensities_.iter().map(|&x| x as f64).collect::<Vec<f64>>();
784 let tof_i32 = tof_indices_.iter().map(|&x| x as i32).collect::<Vec<i32>>();
785
786 let mz = self.index_converter.tof_to_mz(frame_id, &tof_indices_);
787 let inv_mobility = self
788 .index_converter
789 .scan_to_inverse_mobility(frame_id, &scan);
790
791 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
792 let ms_type = match ms_type_raw {
793 0 => MsType::Precursor,
794 8 => MsType::FragmentDda,
795 9 => MsType::FragmentDia,
796 _ => MsType::Unknown,
797 };
798
799 TimsFrame {
800 frame_id: frame_id as i32,
801 ms_type,
802 scan: scan.iter().map(|&x| x as i32).collect(),
803 tof: tof_i32,
804 ims_frame: ImsFrame::new(
805 self.raw_data_layout.frame_meta_data[frame_index].time,
806 inv_mobility,
807 mz,
808 intensity_dbl,
809 ),
810 }
811 }
812
813 2 => {
815 let mut compressed_data = vec![0u8; bin_size as usize - 8];
816 infile.read_exact(&mut compressed_data).unwrap();
817
818 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
819
820 let (scan, tof, intensity) =
821 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
822 let intensity_dbl = intensity.iter().map(|&x| x as f64).collect();
823 let tof_i32 = tof.iter().map(|&x| x as i32).collect();
824 let scan = flatten_scan_values(&scan, true);
825
826 let mz = self.index_converter.tof_to_mz(frame_id, &tof);
827 let inv_mobility = self
828 .index_converter
829 .scan_to_inverse_mobility(frame_id, &scan);
830
831 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
832
833 let ms_type = match ms_type_raw {
834 0 => MsType::Precursor,
835 8 => MsType::FragmentDda,
836 9 => MsType::FragmentDia,
837 _ => MsType::Unknown,
838 };
839
840 TimsFrame {
841 frame_id: frame_id as i32,
842 ms_type,
843 scan: scan.iter().map(|&x| x as i32).collect(),
844 tof: tof_i32,
845 ims_frame: ImsFrame::new(
846 self.raw_data_layout.frame_meta_data[frame_index].time,
847 inv_mobility,
848 mz,
849 intensity_dbl,
850 ),
851 }
852 }
853
854 _ => {
855 panic!("TimsCompressionType is not 1 or 2.")
856 }
857 }
858 }
859
860 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
861 let frame_index = (frame_id - 1) as usize;
862 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
863
864 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
866
867 if num_peaks == 0 {
868 return RawTimsFrame {
869 frame_id: frame_id as i32,
870 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
871 ms_type: MsType::Unknown,
872 scan: Vec::new(),
873 tof: Vec::new(),
874 intensity: Vec::new(),
875 };
876 }
877
878 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
879 file_path.push("analysis.tdf_bin");
880 let mut infile = File::open(&file_path).unwrap();
881
882 infile.seek(SeekFrom::Start(offset)).unwrap();
883
884 let mut bin_buffer = [0u8; 4];
885 infile.read_exact(&mut bin_buffer).unwrap();
886 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
887
888 infile.read_exact(&mut bin_buffer).unwrap();
889
890 match self.raw_data_layout.global_meta_data.tims_compression_type {
891 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 1 => {
892 panic!("Decompression Type1 not implemented.");
893 }
894
895 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 2 => {
897 let mut compressed_data = vec![0u8; bin_size as usize - 8];
898 infile.read_exact(&mut compressed_data).unwrap();
899
900 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
901
902 let (scan, tof, intensity) =
903 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
904
905 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
906
907 let ms_type = match ms_type_raw {
908 0 => MsType::Precursor,
909 8 => MsType::FragmentDda,
910 9 => MsType::FragmentDia,
911 _ => MsType::Unknown,
912 };
913
914 let frame = RawTimsFrame {
915 frame_id: frame_id as i32,
916 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize]
917 .time,
918 ms_type,
919 scan,
920 tof,
921 intensity: intensity.iter().map(|&x| x as f64).collect(),
922 };
923
924 return frame;
925 }
926
927 _ => {
929 panic!("TimsCompressionType is not 1 or 2.")
930 }
931 }
932 }
933
934 fn get_slice(&self, frame_ids: Vec<u32>, _num_threads: usize) -> TimsSlice {
935 let result: Vec<TimsFrame> = frame_ids.into_iter().map(|f| self.get_frame(f)).collect();
936
937 TimsSlice { frames: result }
938 }
939
940 fn get_acquisition_mode(&self) -> AcquisitionMode {
941 self.raw_data_layout.acquisition_mode.clone()
942 }
943
944 fn get_frame_count(&self) -> i32 {
945 self.raw_data_layout.frame_meta_data.len() as i32
946 }
947
948 fn get_data_path(&self) -> &str {
949 &self.raw_data_layout.raw_data_path
950 }
951}
952
953pub struct TimsInMemoryLoader {
954 pub raw_data_layout: TimsRawDataLayout,
955 pub index_converter: TimsIndexConverter,
956 compressed_data: Vec<u8>,
957}
958
959impl TimsData for TimsInMemoryLoader {
960 fn get_frame(&self, frame_id: u32) -> TimsFrame {
961 let raw_frame = self.get_raw_frame(frame_id);
962
963 let raw_frame = match raw_frame.ms_type {
964 MsType::FragmentDda => raw_frame.smooth(1).centroid(1),
965 _ => raw_frame,
966 };
967
968 if raw_frame.scan.is_empty() {
970 return TimsFrame::default();
971 }
972
973 let tof_i32 = raw_frame.tof.iter().map(|&x| x as i32).collect();
974 let scan = flatten_scan_values(&raw_frame.scan, true);
975
976 let mz = self.index_converter.tof_to_mz(frame_id, &raw_frame.tof);
977 let inverse_mobility = self
978 .index_converter
979 .scan_to_inverse_mobility(frame_id, &scan);
980
981 let ims_frame = ImsFrame::new(
982 raw_frame.retention_time,
983 inverse_mobility,
984 mz,
985 raw_frame.intensity,
986 );
987
988 TimsFrame {
989 frame_id: frame_id as i32,
990 ms_type: raw_frame.ms_type,
991 scan: scan.iter().map(|&x| x as i32).collect(),
992 tof: tof_i32,
993 ims_frame,
994 }
995 }
996
997 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
998 let frame_index = (frame_id - 1) as usize;
999 let offset = self.raw_data_layout.tims_offset_values[frame_index] as usize;
1000
1001 let bin_size_offset = offset + 4; let bin_size = Cursor::new(&self.compressed_data[offset..bin_size_offset])
1003 .read_i32::<LittleEndian>()
1004 .unwrap();
1005
1006 let data_offset = bin_size_offset + 4; let frame_data = &self.compressed_data[data_offset..data_offset + bin_size as usize - 8];
1008
1009 let decompressed_bytes = zstd_decompress(&frame_data).unwrap();
1010
1011 let (scan, tof, intensity) =
1012 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
1013
1014 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
1015
1016 let ms_type = match ms_type_raw {
1017 0 => MsType::Precursor,
1018 8 => MsType::FragmentDda,
1019 9 => MsType::FragmentDia,
1020 _ => MsType::Unknown,
1021 };
1022
1023 let raw_frame = RawTimsFrame {
1024 frame_id: frame_id as i32,
1025 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
1026 ms_type,
1027 scan,
1028 tof,
1029 intensity: intensity.iter().map(|&x| x as f64).collect(),
1030 };
1031
1032 raw_frame
1033 }
1034
1035 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1036 let pool = ThreadPoolBuilder::new()
1037 .num_threads(num_threads)
1038 .build()
1039 .unwrap();
1040 let frames = pool.install(|| {
1041 frame_ids
1042 .par_iter()
1043 .map(|&frame_id| self.get_frame(frame_id))
1044 .collect()
1045 });
1046
1047 TimsSlice { frames }
1048 }
1049
1050 fn get_acquisition_mode(&self) -> AcquisitionMode {
1051 self.raw_data_layout.acquisition_mode.clone()
1052 }
1053
1054 fn get_frame_count(&self) -> i32 {
1055 self.raw_data_layout.frame_meta_data.len() as i32
1056 }
1057
1058 fn get_data_path(&self) -> &str {
1059 &self.raw_data_layout.raw_data_path
1060 }
1061}
1062
1063pub enum TimsDataLoader {
1064 InMemory(TimsInMemoryLoader),
1065 Lazy(TimsLazyLoder),
1066}
1067
1068fn build_index_converter(
1079 bruker_lib_path: &str,
1080 data_path: &str,
1081 use_bruker_sdk: bool,
1082 scan_max_index: u32,
1083 im_lower: f64,
1084 im_upper: f64,
1085 tof_max_index: u32,
1086 mz_lower: f64,
1087 mz_upper: f64,
1088) -> TimsIndexConverter {
1089 if use_bruker_sdk {
1090 match BrukerLibTimsDataConverter::try_new(bruker_lib_path, data_path) {
1091 Ok(converter) => return TimsIndexConverter::BrukerLib(converter),
1092 Err(e) => eprintln!(
1093 "Warning: Bruker SDK requested but failed to load ({e}); \
1094 falling back to derived/simple m/z calibration."
1095 ),
1096 }
1097 }
1098 match BrukerFormulaConverter::from_d_folder(data_path, 1) {
1101 Ok(converter) => return TimsIndexConverter::BrukerFormula(converter),
1102 Err(e) => eprintln!(
1103 "Warning: could not build the SDK-free Bruker formula converter ({e}); \
1104 falling back to derived/simple m/z calibration."
1105 ),
1106 }
1107
1108 match derive_mz_calibration(bruker_lib_path, data_path, tof_max_index) {
1110 Some((intercept, slope)) => TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1111 intercept,
1112 slope,
1113 im_lower,
1114 im_upper,
1115 scan_max_index,
1116 )),
1117 None => {
1118 eprintln!(
1119 "Warning: Could not derive m/z calibration from SDK. \
1120 Using simple boundary model which may have ~5 Da error on some datasets. \
1121 This typically happens on macOS where Bruker SDK is not available."
1122 );
1123 TimsIndexConverter::Simple(SimpleIndexConverter::from_boundaries(
1124 mz_lower,
1125 mz_upper,
1126 tof_max_index,
1127 im_lower,
1128 im_upper,
1129 scan_max_index,
1130 ))
1131 }
1132 }
1133}
1134
1135impl TimsDataLoader {
1136 pub fn new_lazy(
1137 bruker_lib_path: &str,
1138 data_path: &str,
1139 use_bruker_sdk: bool,
1140 scan_max_index: u32,
1141 im_lower: f64,
1142 im_upper: f64,
1143 tof_max_index: u32,
1144 mz_lower: f64,
1145 mz_upper: f64,
1146 ) -> Self {
1147 let raw_data_layout = TimsRawDataLayout::new(data_path);
1148 let index_converter = build_index_converter(
1149 bruker_lib_path,
1150 data_path,
1151 use_bruker_sdk,
1152 scan_max_index,
1153 im_lower,
1154 im_upper,
1155 tof_max_index,
1156 mz_lower,
1157 mz_upper,
1158 );
1159
1160 TimsDataLoader::Lazy(TimsLazyLoder {
1161 raw_data_layout,
1162 index_converter,
1163 })
1164 }
1165
1166 pub fn new_in_memory(
1167 bruker_lib_path: &str,
1168 data_path: &str,
1169 use_bruker_sdk: bool,
1170 scan_max_index: u32,
1171 im_lower: f64,
1172 im_upper: f64,
1173 tof_max_index: u32,
1174 mz_lower: f64,
1175 mz_upper: f64,
1176 ) -> Self {
1177 let raw_data_layout = TimsRawDataLayout::new(data_path);
1178 let index_converter = build_index_converter(
1179 bruker_lib_path,
1180 data_path,
1181 use_bruker_sdk,
1182 scan_max_index,
1183 im_lower,
1184 im_upper,
1185 tof_max_index,
1186 mz_lower,
1187 mz_upper,
1188 );
1189
1190 let mut file_path = PathBuf::from(data_path);
1191 file_path.push("analysis.tdf_bin");
1192 let mut infile = File::open(file_path).unwrap();
1193 let mut data = Vec::new();
1194 infile.read_to_end(&mut data).unwrap();
1195
1196 TimsDataLoader::InMemory(TimsInMemoryLoader {
1197 raw_data_layout,
1198 index_converter,
1199 compressed_data: data,
1200 })
1201 }
1202
1203 pub fn new_lazy_with_calibration(
1221 data_path: &str,
1222 bruker_lib_path: &str,
1223 tof_max_index: u32,
1224 mz_lower: f64,
1225 mz_upper: f64,
1226 im_lookup: Vec<f64>,
1227 ) -> Self {
1228 let raw_data_layout = TimsRawDataLayout::new(data_path);
1229
1230 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1231 bruker_lib_path,
1232 data_path,
1233 tof_max_index,
1234 mz_lower,
1235 mz_upper,
1236 im_lookup,
1237 ));
1238
1239 TimsDataLoader::Lazy(TimsLazyLoder {
1240 raw_data_layout,
1241 index_converter,
1242 })
1243 }
1244
1245 pub fn new_in_memory_with_calibration(
1263 data_path: &str,
1264 bruker_lib_path: &str,
1265 tof_max_index: u32,
1266 mz_lower: f64,
1267 mz_upper: f64,
1268 im_lookup: Vec<f64>,
1269 ) -> Self {
1270 let raw_data_layout = TimsRawDataLayout::new(data_path);
1271
1272 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1273 bruker_lib_path,
1274 data_path,
1275 tof_max_index,
1276 mz_lower,
1277 mz_upper,
1278 im_lookup,
1279 ));
1280
1281 let mut file_path = PathBuf::from(data_path);
1282 file_path.push("analysis.tdf_bin");
1283 let mut infile = File::open(file_path).unwrap();
1284 let mut data = Vec::new();
1285 infile.read_to_end(&mut data).unwrap();
1286
1287 TimsDataLoader::InMemory(TimsInMemoryLoader {
1288 raw_data_layout,
1289 index_converter,
1290 compressed_data: data,
1291 })
1292 }
1293
1294 pub fn new_lazy_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1302 let raw_data_layout = TimsRawDataLayout::new(data_path);
1303 let index_converter = TimsIndexConverter::BrukerFormula(
1304 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1305 );
1306 TimsDataLoader::Lazy(TimsLazyLoder {
1307 raw_data_layout,
1308 index_converter,
1309 })
1310 }
1311
1312 pub fn new_in_memory_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1314 let raw_data_layout = TimsRawDataLayout::new(data_path);
1315 let index_converter = TimsIndexConverter::BrukerFormula(
1316 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1317 );
1318
1319 let mut file_path = PathBuf::from(data_path);
1320 file_path.push("analysis.tdf_bin");
1321 let mut infile = File::open(file_path).unwrap();
1322 let mut data = Vec::new();
1323 infile.read_to_end(&mut data).unwrap();
1324
1325 TimsDataLoader::InMemory(TimsInMemoryLoader {
1326 raw_data_layout,
1327 index_converter,
1328 compressed_data: data,
1329 })
1330 }
1331
1332 pub fn new_lazy_with_mz_calibration(
1345 data_path: &str,
1346 tof_intercept: f64,
1347 tof_slope: f64,
1348 im_min: f64,
1349 im_max: f64,
1350 scan_max_index: u32,
1351 ) -> Self {
1352 let raw_data_layout = TimsRawDataLayout::new(data_path);
1353
1354 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1355 tof_intercept,
1356 tof_slope,
1357 im_min,
1358 im_max,
1359 scan_max_index,
1360 ));
1361
1362 TimsDataLoader::Lazy(TimsLazyLoder {
1363 raw_data_layout,
1364 index_converter,
1365 })
1366 }
1367
1368 pub fn new_in_memory_with_mz_calibration(
1373 data_path: &str,
1374 tof_intercept: f64,
1375 tof_slope: f64,
1376 im_min: f64,
1377 im_max: f64,
1378 scan_max_index: u32,
1379 ) -> Self {
1380 let raw_data_layout = TimsRawDataLayout::new(data_path);
1381
1382 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1383 tof_intercept,
1384 tof_slope,
1385 im_min,
1386 im_max,
1387 scan_max_index,
1388 ));
1389
1390 let mut file_path = PathBuf::from(data_path);
1391 file_path.push("analysis.tdf_bin");
1392 let mut infile = File::open(file_path).unwrap();
1393 let mut data = Vec::new();
1394 infile.read_to_end(&mut data).unwrap();
1395
1396 TimsDataLoader::InMemory(TimsInMemoryLoader {
1397 raw_data_layout,
1398 index_converter,
1399 compressed_data: data,
1400 })
1401 }
1402
1403 pub fn get_index_converter(&self) -> &dyn IndexConverter {
1404 match self {
1405 TimsDataLoader::InMemory(loader) => &loader.index_converter,
1406 TimsDataLoader::Lazy(loader) => &loader.index_converter,
1407 }
1408 }
1409
1410 pub fn uses_bruker_sdk(&self) -> bool {
1414 match self {
1415 TimsDataLoader::InMemory(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1416 TimsDataLoader::Lazy(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1417 }
1418 }
1419}
1420
1421impl TimsData for TimsDataLoader {
1422 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1423 match self {
1424 TimsDataLoader::InMemory(loader) => loader.get_frame(frame_id),
1425 TimsDataLoader::Lazy(loader) => loader.get_frame(frame_id),
1426 }
1427 }
1428 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1429 match self {
1430 TimsDataLoader::InMemory(loader) => loader.get_raw_frame(frame_id),
1431 TimsDataLoader::Lazy(loader) => loader.get_raw_frame(frame_id),
1432 }
1433 }
1434
1435 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1436 match self {
1437 TimsDataLoader::InMemory(loader) => loader.get_slice(frame_ids, num_threads),
1438 TimsDataLoader::Lazy(loader) => loader.get_slice(frame_ids, num_threads),
1439 }
1440 }
1441
1442 fn get_acquisition_mode(&self) -> AcquisitionMode {
1443 match self {
1444 TimsDataLoader::InMemory(loader) => loader.get_acquisition_mode(),
1445 TimsDataLoader::Lazy(loader) => loader.get_acquisition_mode(),
1446 }
1447 }
1448
1449 fn get_frame_count(&self) -> i32 {
1450 match self {
1451 TimsDataLoader::InMemory(loader) => loader.get_frame_count(),
1452 TimsDataLoader::Lazy(loader) => loader.get_frame_count(),
1453 }
1454 }
1455
1456 fn get_data_path(&self) -> &str {
1457 match self {
1458 TimsDataLoader::InMemory(loader) => loader.get_data_path(),
1459 TimsDataLoader::Lazy(loader) => loader.get_data_path(),
1460 }
1461 }
1462}
1463
1464pub struct SimpleIndexConverter {
1465 pub tof_intercept: f64,
1466 pub tof_slope: f64,
1467 pub scan_intercept: f64,
1468 pub scan_slope: f64,
1469}
1470
1471impl SimpleIndexConverter {
1472 pub fn from_boundaries(
1473 mz_min: f64,
1474 mz_max: f64,
1475 tof_max_index: u32,
1476 im_min: f64,
1477 im_max: f64,
1478 scan_max_index: u32,
1479 ) -> Self {
1480 let tof_intercept: f64 = mz_min.sqrt();
1481 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1482
1483 let scan_intercept: f64 = im_max;
1484 let scan_slope: f64 = (im_min - scan_intercept) / scan_max_index as f64;
1485 Self {
1486 tof_intercept,
1487 tof_slope,
1488 scan_intercept,
1489 scan_slope,
1490 }
1491 }
1492}
1493
1494impl IndexConverter for SimpleIndexConverter {
1495 fn tof_to_mz(&self, _frame_id: u32, _tof_values: &Vec<u32>) -> Vec<f64> {
1496 let mut mz_values: Vec<f64> = Vec::new();
1497 mz_values.resize(_tof_values.len(), 0.0);
1498
1499 for (i, &val) in _tof_values.iter().enumerate() {
1500 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1501 }
1502
1503 mz_values
1504 }
1505
1506 fn mz_to_tof(&self, _frame_id: u32, _mz_values: &Vec<f64>) -> Vec<u32> {
1507 let mut tof_values: Vec<u32> = Vec::new();
1508 tof_values.resize(_mz_values.len(), 0);
1509
1510 for (i, &val) in _mz_values.iter().enumerate() {
1511 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1512 }
1513
1514 tof_values
1515 }
1516
1517 fn scan_to_inverse_mobility(&self, _frame_id: u32, _scan_values: &Vec<u32>) -> Vec<f64> {
1518 let mut inv_mobility_values: Vec<f64> = Vec::new();
1519 inv_mobility_values.resize(_scan_values.len(), 0.0);
1520
1521 for (i, &val) in _scan_values.iter().enumerate() {
1522 inv_mobility_values[i] = self.scan_intercept + self.scan_slope * val as f64;
1523 }
1524
1525 inv_mobility_values
1526 }
1527
1528 fn inverse_mobility_to_scan(
1529 &self,
1530 _frame_id: u32,
1531 _inverse_mobility_values: &Vec<f64>,
1532 ) -> Vec<u32> {
1533 let mut scan_values: Vec<u32> = Vec::new();
1534 scan_values.resize(_inverse_mobility_values.len(), 0);
1535
1536 for (i, &val) in _inverse_mobility_values.iter().enumerate() {
1537 scan_values[i] = ((val - self.scan_intercept) / self.scan_slope) as u32;
1538 }
1539
1540 scan_values
1541 }
1542}
1543
1544pub struct CalibratedIndexConverter {
1555 pub tof_intercept: f64,
1556 pub tof_slope: f64,
1557 pub scan_intercept: f64,
1558 pub scan_slope: f64,
1559}
1560
1561impl CalibratedIndexConverter {
1562 pub fn new(
1571 tof_intercept: f64,
1572 tof_slope: f64,
1573 im_min: f64,
1574 im_max: f64,
1575 scan_max_index: u32,
1576 ) -> Self {
1577 let scan_intercept = im_max;
1578 let scan_slope = (im_min - scan_intercept) / scan_max_index as f64;
1579 Self {
1580 tof_intercept,
1581 tof_slope,
1582 scan_intercept,
1583 scan_slope,
1584 }
1585 }
1586}
1587
1588impl IndexConverter for CalibratedIndexConverter {
1589 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1590 let mut mz_values: Vec<f64> = Vec::with_capacity(tof_values.len());
1591
1592 for &tof_index in tof_values.iter() {
1593 let sqrt_mz = self.tof_intercept + self.tof_slope * tof_index as f64;
1595 mz_values.push(sqrt_mz * sqrt_mz);
1596 }
1597
1598 mz_values
1599 }
1600
1601 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1602 let mut tof_values: Vec<u32> = Vec::with_capacity(mz_values.len());
1603
1604 for &mz in mz_values.iter() {
1605 let sqrt_mz = mz.sqrt();
1606 tof_values.push(((sqrt_mz - self.tof_intercept) / self.tof_slope) as u32);
1608 }
1609
1610 tof_values
1611 }
1612
1613 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1614 let mut inv_mobility_values: Vec<f64> = Vec::with_capacity(scan_values.len());
1615
1616 for &val in scan_values.iter() {
1617 inv_mobility_values.push(self.scan_intercept + self.scan_slope * val as f64);
1618 }
1619
1620 inv_mobility_values
1621 }
1622
1623 fn inverse_mobility_to_scan(
1624 &self,
1625 _frame_id: u32,
1626 inverse_mobility_values: &Vec<f64>,
1627 ) -> Vec<u32> {
1628 let mut scan_values: Vec<u32> = Vec::with_capacity(inverse_mobility_values.len());
1629
1630 for &val in inverse_mobility_values.iter() {
1631 scan_values.push(((val - self.scan_intercept) / self.scan_slope) as u32);
1632 }
1633
1634 scan_values
1635 }
1636}
1637
1638pub struct LookupIndexConverter {
1651 pub tof_intercept: f64,
1653 pub tof_slope: f64,
1654
1655 pub im_lookup: Vec<f64>,
1658
1659 pub im_min: f64,
1662 pub im_max: f64,
1663}
1664
1665impl LookupIndexConverter {
1666 pub fn new(
1677 mz_min: f64,
1678 mz_max: f64,
1679 tof_max_index: u32,
1680 im_lookup: Vec<f64>,
1681 ) -> Self {
1682 let tof_intercept: f64 = mz_min.sqrt();
1683 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1684
1685 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1687 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1688
1689 Self {
1690 tof_intercept,
1691 tof_slope,
1692 im_lookup,
1693 im_min,
1694 im_max,
1695 }
1696 }
1697
1698 pub fn with_mz_fit(tof_intercept: f64, tof_slope: f64, im_lookup: Vec<f64>) -> Self {
1709 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1711 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1712
1713 Self {
1714 tof_intercept,
1715 tof_slope,
1716 im_lookup,
1717 im_min,
1718 im_max,
1719 }
1720 }
1721}
1722
1723impl IndexConverter for LookupIndexConverter {
1724 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1725 let mut mz_values: Vec<f64> = Vec::new();
1726 mz_values.resize(tof_values.len(), 0.0);
1727
1728 for (i, &val) in tof_values.iter().enumerate() {
1729 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1730 }
1731
1732 mz_values
1733 }
1734
1735 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1736 let mut tof_values: Vec<u32> = Vec::new();
1737 tof_values.resize(mz_values.len(), 0);
1738
1739 for (i, &val) in mz_values.iter().enumerate() {
1740 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1741 }
1742
1743 tof_values
1744 }
1745
1746 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1747 scan_values
1749 .iter()
1750 .map(|&s| {
1751 self.im_lookup
1752 .get(s as usize)
1753 .copied()
1754 .unwrap_or(f64::NAN)
1755 })
1756 .collect()
1757 }
1758
1759 fn inverse_mobility_to_scan(
1760 &self,
1761 _frame_id: u32,
1762 inverse_mobility_values: &Vec<f64>,
1763 ) -> Vec<u32> {
1764 inverse_mobility_values
1767 .iter()
1768 .map(|&im| {
1769 if im.is_nan() || self.im_lookup.is_empty() {
1770 return 0;
1771 }
1772
1773 let mut best_scan = 0usize;
1776 let mut best_diff = f64::INFINITY;
1777
1778 for (scan, &lookup_im) in self.im_lookup.iter().enumerate() {
1779 let diff = (lookup_im - im).abs();
1780 if diff < best_diff {
1781 best_diff = diff;
1782 best_scan = scan;
1783 }
1784 }
1785
1786 best_scan as u32
1787 })
1788 .collect()
1789 }
1790}