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 is_sdk_exact(&self) -> bool {
546 if self.frames.is_empty() {
547 return false;
548 }
549 self.frames.values().all(|f| {
550 self.mz_rows.get(&f.mz_id).map(|r| r.model_type == 1).unwrap_or(false)
551 && self.im_rows.contains_key(&f.tims_id)
552 })
553 }
554
555 pub fn temperature_spread(&self) -> (f64, f64) {
561 self.temp_spread
562 }
563
564 fn mz_for(&self, frame_id: u32) -> crate::data::calibration::MzCalibrator {
566 self.frames
567 .get(&frame_id)
568 .and_then(|f| {
569 self.mz_rows.get(&f.mz_id).map(|row| {
570 crate::data::calibration::MzCalibrator::from_calibration(row, f.t1, f.t2)
571 })
572 })
573 .unwrap_or_else(|| self.mz.clone())
574 }
575
576 fn im_for(&self, frame_id: u32) -> &crate::data::calibration::MobilityCalibrator {
578 self.frames
579 .get(&frame_id)
580 .and_then(|f| self.im_rows.get(&f.tims_id))
581 .unwrap_or(&self.im)
582 }
583}
584
585impl IndexConverter for BrukerFormulaConverter {
586 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
587 let mz = self.mz_for(frame_id);
588 tof_values.iter().map(|&t| mz.tof_to_mz(t)).collect()
589 }
590 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
591 let mz = self.mz_for(frame_id);
592 mz_values.iter().map(|&m| mz.mz_to_tof(m)).collect()
593 }
594 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
595 let im = self.im_for(frame_id);
596 scan_values
597 .iter()
598 .map(|&s| im.scan_to_one_over_k0(s))
599 .collect()
600 }
601 fn inverse_mobility_to_scan(
602 &self,
603 frame_id: u32,
604 inverse_mobility_values: &Vec<f64>,
605 ) -> Vec<u32> {
606 let im = self.im_for(frame_id);
607 inverse_mobility_values
608 .iter()
609 .map(|&v| im.one_over_k0_to_scan(v))
610 .collect()
611 }
612}
613
614pub enum TimsIndexConverter {
615 Simple(SimpleIndexConverter),
616 Calibrated(CalibratedIndexConverter),
617 BrukerLib(BrukerLibTimsDataConverter),
618 Lookup(LookupIndexConverter),
619 BrukerFormula(BrukerFormulaConverter),
620}
621
622impl IndexConverter for TimsIndexConverter {
623 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
624 match self {
625 TimsIndexConverter::Simple(converter) => converter.tof_to_mz(frame_id, tof_values),
626 TimsIndexConverter::Calibrated(converter) => converter.tof_to_mz(frame_id, tof_values),
627 TimsIndexConverter::BrukerLib(converter) => converter.tof_to_mz(frame_id, tof_values),
628 TimsIndexConverter::Lookup(converter) => converter.tof_to_mz(frame_id, tof_values),
629 TimsIndexConverter::BrukerFormula(converter) => converter.tof_to_mz(frame_id, tof_values),
630 }
631 }
632
633 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
634 match self {
635 TimsIndexConverter::Simple(converter) => converter.mz_to_tof(frame_id, mz_values),
636 TimsIndexConverter::Calibrated(converter) => converter.mz_to_tof(frame_id, mz_values),
637 TimsIndexConverter::BrukerLib(converter) => converter.mz_to_tof(frame_id, mz_values),
638 TimsIndexConverter::Lookup(converter) => converter.mz_to_tof(frame_id, mz_values),
639 TimsIndexConverter::BrukerFormula(converter) => converter.mz_to_tof(frame_id, mz_values),
640 }
641 }
642
643 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
644 match self {
645 TimsIndexConverter::Simple(converter) => {
646 converter.scan_to_inverse_mobility(frame_id, scan_values)
647 }
648 TimsIndexConverter::Calibrated(converter) => {
649 converter.scan_to_inverse_mobility(frame_id, scan_values)
650 }
651 TimsIndexConverter::BrukerLib(converter) => {
652 converter.scan_to_inverse_mobility(frame_id, scan_values)
653 }
654 TimsIndexConverter::Lookup(converter) => {
655 converter.scan_to_inverse_mobility(frame_id, scan_values)
656 }
657 TimsIndexConverter::BrukerFormula(converter) => {
658 converter.scan_to_inverse_mobility(frame_id, scan_values)
659 }
660 }
661 }
662
663 fn inverse_mobility_to_scan(
664 &self,
665 frame_id: u32,
666 inverse_mobility_values: &Vec<f64>,
667 ) -> Vec<u32> {
668 match self {
669 TimsIndexConverter::Simple(converter) => {
670 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
671 }
672 TimsIndexConverter::Calibrated(converter) => {
673 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
674 }
675 TimsIndexConverter::BrukerLib(converter) => {
676 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
677 }
678 TimsIndexConverter::Lookup(converter) => {
679 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
680 }
681 TimsIndexConverter::BrukerFormula(converter) => {
682 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
683 }
684 }
685 }
686}
687
688pub struct TimsLazyLoder {
689 pub raw_data_layout: TimsRawDataLayout,
690 pub index_converter: TimsIndexConverter,
691}
692
693impl TimsLazyLoder {
694 pub fn get_frame_with_converter(&self, frame_id: u32, conv: &dyn IndexConverter) -> TimsFrame {
698 let frame_index = (frame_id - 1) as usize;
699
700 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
702
703 if num_peaks == 0 {
704
705 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
706
707 let ms_type = match ms_type_raw {
708 0 => MsType::Precursor,
709 8 => MsType::FragmentDda,
710 9 => MsType::FragmentDia,
711 _ => MsType::Unknown,
712 };
713
714 return TimsFrame {
715 frame_id: frame_id as i32,
716 ms_type,
717 scan: Vec::new(),
718 tof: Vec::new(),
719 ims_frame: ImsFrame::new(
720 self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
721 Vec::new(),
722 Vec::new(),
723 Vec::new(),
724 ),
725 };
726 }
727
728 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
729
730 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
731 file_path.push("analysis.tdf_bin");
732 let mut infile = File::open(&file_path).unwrap();
733
734 infile.seek(SeekFrom::Start(offset)).unwrap();
735
736 let mut bin_buffer = [0u8; 4];
737 infile.read_exact(&mut bin_buffer).unwrap();
738 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
739
740 infile.read_exact(&mut bin_buffer).unwrap();
741
742 match self.raw_data_layout.global_meta_data.tims_compression_type {
743 1 => {
744 let scan_count =
745 self.raw_data_layout.frame_meta_data[frame_index].num_scans as usize;
746 let num_peaks = num_peaks as usize;
747 let compression_offset = 8 + (scan_count + 1) * 4;
748
749 let mut scan_offsets_buffer = vec![0u8; (scan_count + 1) * 4];
750 infile.read_exact(&mut scan_offsets_buffer).unwrap();
751
752 let mut scan_offsets = Vec::with_capacity(scan_count + 1);
753 {
754 let mut rdr = Cursor::new(&scan_offsets_buffer);
755 for _ in 0..(scan_count + 1) {
756 scan_offsets.push(rdr.read_i32::<LittleEndian>().unwrap());
757 }
758 }
759
760 for offs in &mut scan_offsets {
761 *offs -= compression_offset as i32;
762 }
763
764 let remaining_size = (bin_size as usize - compression_offset) as usize;
765 let mut compressed_data = vec![0u8; remaining_size];
766 infile.read_exact(&mut compressed_data).unwrap();
767
768 let mut scan_indices_ = vec![0i64; scan_count];
769 let mut tof_indices_ = vec![0u32; num_peaks];
770 let mut intensities_ = vec![0u16; num_peaks];
771
772 let mut scan_start = 0usize;
773
774 for scan_index in 0..scan_count {
775 let start = scan_offsets[scan_index] as usize;
776 let end = scan_offsets[scan_index + 1] as usize;
777
778 if start == end {
779 continue;
780 }
781
782 let max_output_size = num_peaks * 8;
783 let decompressed_bytes =
784 lzf_decompress(&compressed_data[start..end], max_output_size)
785 .expect("LZF decompression failed.");
786
787 scan_start += parse_decompressed_bruker_binary_type1(
788 &decompressed_bytes,
789 &mut scan_indices_,
790 &mut tof_indices_,
791 &mut intensities_,
792 scan_start,
793 scan_index,
794 );
795 }
796
797 let mut scan = Vec::with_capacity(num_peaks);
799 {
800 let mut current_scan_index = 0u32;
801 for &size in &scan_indices_ {
802 let sz = size as usize;
803 for _ in 0..sz {
804 scan.push(current_scan_index);
805 }
806 current_scan_index += 1;
807 }
808 }
809
810 let intensity_dbl = intensities_.iter().map(|&x| x as f64).collect::<Vec<f64>>();
811 let tof_i32 = tof_indices_.iter().map(|&x| x as i32).collect::<Vec<i32>>();
812
813 let mz = conv.tof_to_mz(frame_id, &tof_indices_);
814 let inv_mobility = conv.scan_to_inverse_mobility(frame_id, &scan);
815
816 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
817 let ms_type = match ms_type_raw {
818 0 => MsType::Precursor,
819 8 => MsType::FragmentDda,
820 9 => MsType::FragmentDia,
821 _ => MsType::Unknown,
822 };
823
824 TimsFrame {
825 frame_id: frame_id as i32,
826 ms_type,
827 scan: scan.iter().map(|&x| x as i32).collect(),
828 tof: tof_i32,
829 ims_frame: ImsFrame::new(
830 self.raw_data_layout.frame_meta_data[frame_index].time,
831 inv_mobility,
832 mz,
833 intensity_dbl,
834 ),
835 }
836 }
837
838 2 => {
840 let mut compressed_data = vec![0u8; bin_size as usize - 8];
841 infile.read_exact(&mut compressed_data).unwrap();
842
843 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
844
845 let (scan, tof, intensity) =
846 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
847 let intensity_dbl = intensity.iter().map(|&x| x as f64).collect();
848 let tof_i32 = tof.iter().map(|&x| x as i32).collect();
849 let scan = flatten_scan_values(&scan, true);
850
851 let mz = conv.tof_to_mz(frame_id, &tof);
852 let inv_mobility = conv.scan_to_inverse_mobility(frame_id, &scan);
853
854 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
855
856 let ms_type = match ms_type_raw {
857 0 => MsType::Precursor,
858 8 => MsType::FragmentDda,
859 9 => MsType::FragmentDia,
860 _ => MsType::Unknown,
861 };
862
863 TimsFrame {
864 frame_id: frame_id as i32,
865 ms_type,
866 scan: scan.iter().map(|&x| x as i32).collect(),
867 tof: tof_i32,
868 ims_frame: ImsFrame::new(
869 self.raw_data_layout.frame_meta_data[frame_index].time,
870 inv_mobility,
871 mz,
872 intensity_dbl,
873 ),
874 }
875 }
876
877 _ => {
878 panic!("TimsCompressionType is not 1 or 2.")
879 }
880 }
881 }
882}
883
884impl TimsData for TimsLazyLoder {
885 fn get_frame(&self, frame_id: u32) -> TimsFrame {
886 self.get_frame_with_converter(frame_id, &self.index_converter)
887 }
888
889 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
890 let frame_index = (frame_id - 1) as usize;
891 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
892
893 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
895
896 if num_peaks == 0 {
897 return RawTimsFrame {
898 frame_id: frame_id as i32,
899 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
900 ms_type: MsType::Unknown,
901 scan: Vec::new(),
902 tof: Vec::new(),
903 intensity: Vec::new(),
904 };
905 }
906
907 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
908 file_path.push("analysis.tdf_bin");
909 let mut infile = File::open(&file_path).unwrap();
910
911 infile.seek(SeekFrom::Start(offset)).unwrap();
912
913 let mut bin_buffer = [0u8; 4];
914 infile.read_exact(&mut bin_buffer).unwrap();
915 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
916
917 infile.read_exact(&mut bin_buffer).unwrap();
918
919 match self.raw_data_layout.global_meta_data.tims_compression_type {
920 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 1 => {
921 panic!("Decompression Type1 not implemented.");
922 }
923
924 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 2 => {
926 let mut compressed_data = vec![0u8; bin_size as usize - 8];
927 infile.read_exact(&mut compressed_data).unwrap();
928
929 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
930
931 let (scan, tof, intensity) =
932 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
933
934 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
935
936 let ms_type = match ms_type_raw {
937 0 => MsType::Precursor,
938 8 => MsType::FragmentDda,
939 9 => MsType::FragmentDia,
940 _ => MsType::Unknown,
941 };
942
943 let frame = RawTimsFrame {
944 frame_id: frame_id as i32,
945 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize]
946 .time,
947 ms_type,
948 scan,
949 tof,
950 intensity: intensity.iter().map(|&x| x as f64).collect(),
951 };
952
953 return frame;
954 }
955
956 _ => {
958 panic!("TimsCompressionType is not 1 or 2.")
959 }
960 }
961 }
962
963 fn get_slice(&self, frame_ids: Vec<u32>, _num_threads: usize) -> TimsSlice {
964 let result: Vec<TimsFrame> = frame_ids.into_iter().map(|f| self.get_frame(f)).collect();
965
966 TimsSlice { frames: result }
967 }
968
969 fn get_acquisition_mode(&self) -> AcquisitionMode {
970 self.raw_data_layout.acquisition_mode.clone()
971 }
972
973 fn get_frame_count(&self) -> i32 {
974 self.raw_data_layout.frame_meta_data.len() as i32
975 }
976
977 fn get_data_path(&self) -> &str {
978 &self.raw_data_layout.raw_data_path
979 }
980}
981
982pub struct TimsInMemoryLoader {
983 pub raw_data_layout: TimsRawDataLayout,
984 pub index_converter: TimsIndexConverter,
985 compressed_data: Vec<u8>,
986}
987
988impl TimsInMemoryLoader {
989 pub fn get_frame_with_converter(&self, frame_id: u32, conv: &dyn IndexConverter) -> TimsFrame {
993 let raw_frame = self.get_raw_frame(frame_id);
994
995 let raw_frame = match raw_frame.ms_type {
996 MsType::FragmentDda => raw_frame.smooth(1).centroid(1),
997 _ => raw_frame,
998 };
999
1000 if raw_frame.scan.is_empty() {
1002 return TimsFrame::default();
1003 }
1004
1005 let tof_i32 = raw_frame.tof.iter().map(|&x| x as i32).collect();
1006 let scan = flatten_scan_values(&raw_frame.scan, true);
1007
1008 let mz = conv.tof_to_mz(frame_id, &raw_frame.tof);
1009 let inverse_mobility = conv.scan_to_inverse_mobility(frame_id, &scan);
1010
1011 let ims_frame = ImsFrame::new(
1012 raw_frame.retention_time,
1013 inverse_mobility,
1014 mz,
1015 raw_frame.intensity,
1016 );
1017
1018 TimsFrame {
1019 frame_id: frame_id as i32,
1020 ms_type: raw_frame.ms_type,
1021 scan: scan.iter().map(|&x| x as i32).collect(),
1022 tof: tof_i32,
1023 ims_frame,
1024 }
1025 }
1026}
1027
1028impl TimsData for TimsInMemoryLoader {
1029 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1030 self.get_frame_with_converter(frame_id, &self.index_converter)
1031 }
1032
1033 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1034 let frame_index = (frame_id - 1) as usize;
1035 let offset = self.raw_data_layout.tims_offset_values[frame_index] as usize;
1036
1037 let bin_size_offset = offset + 4; let bin_size = Cursor::new(&self.compressed_data[offset..bin_size_offset])
1039 .read_i32::<LittleEndian>()
1040 .unwrap();
1041
1042 let data_offset = bin_size_offset + 4; let frame_data = &self.compressed_data[data_offset..data_offset + bin_size as usize - 8];
1044
1045 let decompressed_bytes = zstd_decompress(&frame_data).unwrap();
1046
1047 let (scan, tof, intensity) =
1048 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
1049
1050 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
1051
1052 let ms_type = match ms_type_raw {
1053 0 => MsType::Precursor,
1054 8 => MsType::FragmentDda,
1055 9 => MsType::FragmentDia,
1056 _ => MsType::Unknown,
1057 };
1058
1059 let raw_frame = RawTimsFrame {
1060 frame_id: frame_id as i32,
1061 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
1062 ms_type,
1063 scan,
1064 tof,
1065 intensity: intensity.iter().map(|&x| x as f64).collect(),
1066 };
1067
1068 raw_frame
1069 }
1070
1071 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1072 let pool = ThreadPoolBuilder::new()
1073 .num_threads(num_threads)
1074 .build()
1075 .unwrap();
1076 let frames = pool.install(|| {
1077 frame_ids
1078 .par_iter()
1079 .map(|&frame_id| self.get_frame(frame_id))
1080 .collect()
1081 });
1082
1083 TimsSlice { frames }
1084 }
1085
1086 fn get_acquisition_mode(&self) -> AcquisitionMode {
1087 self.raw_data_layout.acquisition_mode.clone()
1088 }
1089
1090 fn get_frame_count(&self) -> i32 {
1091 self.raw_data_layout.frame_meta_data.len() as i32
1092 }
1093
1094 fn get_data_path(&self) -> &str {
1095 &self.raw_data_layout.raw_data_path
1096 }
1097}
1098
1099pub enum TimsDataLoader {
1100 InMemory(TimsInMemoryLoader),
1101 Lazy(TimsLazyLoder),
1102}
1103
1104fn build_index_converter(
1115 bruker_lib_path: &str,
1116 data_path: &str,
1117 use_bruker_sdk: bool,
1118 scan_max_index: u32,
1119 im_lower: f64,
1120 im_upper: f64,
1121 tof_max_index: u32,
1122 mz_lower: f64,
1123 mz_upper: f64,
1124) -> TimsIndexConverter {
1125 if use_bruker_sdk {
1126 match BrukerLibTimsDataConverter::try_new(bruker_lib_path, data_path) {
1127 Ok(converter) => return TimsIndexConverter::BrukerLib(converter),
1128 Err(e) => eprintln!(
1129 "Warning: Bruker SDK requested but failed to load ({e}); \
1130 falling back to derived/simple m/z calibration."
1131 ),
1132 }
1133 }
1134 match BrukerFormulaConverter::from_d_folder(data_path, 1) {
1137 Ok(converter) => return TimsIndexConverter::BrukerFormula(converter),
1138 Err(e) => eprintln!(
1139 "Warning: could not build the SDK-free Bruker formula converter ({e}); \
1140 falling back to derived/simple m/z calibration."
1141 ),
1142 }
1143
1144 match derive_mz_calibration(bruker_lib_path, data_path, tof_max_index) {
1146 Some((intercept, slope)) => TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1147 intercept,
1148 slope,
1149 im_lower,
1150 im_upper,
1151 scan_max_index,
1152 )),
1153 None => {
1154 eprintln!(
1155 "Warning: Could not derive m/z calibration from SDK. \
1156 Using simple boundary model which may have ~5 Da error on some datasets. \
1157 This typically happens on macOS where Bruker SDK is not available."
1158 );
1159 TimsIndexConverter::Simple(SimpleIndexConverter::from_boundaries(
1160 mz_lower,
1161 mz_upper,
1162 tof_max_index,
1163 im_lower,
1164 im_upper,
1165 scan_max_index,
1166 ))
1167 }
1168 }
1169}
1170
1171impl TimsDataLoader {
1172 pub fn new_lazy(
1173 bruker_lib_path: &str,
1174 data_path: &str,
1175 use_bruker_sdk: bool,
1176 scan_max_index: u32,
1177 im_lower: f64,
1178 im_upper: f64,
1179 tof_max_index: u32,
1180 mz_lower: f64,
1181 mz_upper: f64,
1182 ) -> Self {
1183 let raw_data_layout = TimsRawDataLayout::new(data_path);
1184 let index_converter = build_index_converter(
1185 bruker_lib_path,
1186 data_path,
1187 use_bruker_sdk,
1188 scan_max_index,
1189 im_lower,
1190 im_upper,
1191 tof_max_index,
1192 mz_lower,
1193 mz_upper,
1194 );
1195
1196 TimsDataLoader::Lazy(TimsLazyLoder {
1197 raw_data_layout,
1198 index_converter,
1199 })
1200 }
1201
1202 pub fn new_in_memory(
1203 bruker_lib_path: &str,
1204 data_path: &str,
1205 use_bruker_sdk: bool,
1206 scan_max_index: u32,
1207 im_lower: f64,
1208 im_upper: f64,
1209 tof_max_index: u32,
1210 mz_lower: f64,
1211 mz_upper: f64,
1212 ) -> Self {
1213 let raw_data_layout = TimsRawDataLayout::new(data_path);
1214 let index_converter = build_index_converter(
1215 bruker_lib_path,
1216 data_path,
1217 use_bruker_sdk,
1218 scan_max_index,
1219 im_lower,
1220 im_upper,
1221 tof_max_index,
1222 mz_lower,
1223 mz_upper,
1224 );
1225
1226 let mut file_path = PathBuf::from(data_path);
1227 file_path.push("analysis.tdf_bin");
1228 let mut infile = File::open(file_path).unwrap();
1229 let mut data = Vec::new();
1230 infile.read_to_end(&mut data).unwrap();
1231
1232 TimsDataLoader::InMemory(TimsInMemoryLoader {
1233 raw_data_layout,
1234 index_converter,
1235 compressed_data: data,
1236 })
1237 }
1238
1239 pub fn new_lazy_with_calibration(
1257 data_path: &str,
1258 bruker_lib_path: &str,
1259 tof_max_index: u32,
1260 mz_lower: f64,
1261 mz_upper: f64,
1262 im_lookup: Vec<f64>,
1263 ) -> Self {
1264 let raw_data_layout = TimsRawDataLayout::new(data_path);
1265
1266 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1267 bruker_lib_path,
1268 data_path,
1269 tof_max_index,
1270 mz_lower,
1271 mz_upper,
1272 im_lookup,
1273 ));
1274
1275 TimsDataLoader::Lazy(TimsLazyLoder {
1276 raw_data_layout,
1277 index_converter,
1278 })
1279 }
1280
1281 pub fn new_in_memory_with_calibration(
1299 data_path: &str,
1300 bruker_lib_path: &str,
1301 tof_max_index: u32,
1302 mz_lower: f64,
1303 mz_upper: f64,
1304 im_lookup: Vec<f64>,
1305 ) -> Self {
1306 let raw_data_layout = TimsRawDataLayout::new(data_path);
1307
1308 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1309 bruker_lib_path,
1310 data_path,
1311 tof_max_index,
1312 mz_lower,
1313 mz_upper,
1314 im_lookup,
1315 ));
1316
1317 let mut file_path = PathBuf::from(data_path);
1318 file_path.push("analysis.tdf_bin");
1319 let mut infile = File::open(file_path).unwrap();
1320 let mut data = Vec::new();
1321 infile.read_to_end(&mut data).unwrap();
1322
1323 TimsDataLoader::InMemory(TimsInMemoryLoader {
1324 raw_data_layout,
1325 index_converter,
1326 compressed_data: data,
1327 })
1328 }
1329
1330 pub fn new_lazy_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1338 let raw_data_layout = TimsRawDataLayout::new(data_path);
1339 let index_converter = TimsIndexConverter::BrukerFormula(
1340 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1341 );
1342 TimsDataLoader::Lazy(TimsLazyLoder {
1343 raw_data_layout,
1344 index_converter,
1345 })
1346 }
1347
1348 pub fn new_in_memory_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1350 let raw_data_layout = TimsRawDataLayout::new(data_path);
1351 let index_converter = TimsIndexConverter::BrukerFormula(
1352 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1353 );
1354
1355 let mut file_path = PathBuf::from(data_path);
1356 file_path.push("analysis.tdf_bin");
1357 let mut infile = File::open(file_path).unwrap();
1358 let mut data = Vec::new();
1359 infile.read_to_end(&mut data).unwrap();
1360
1361 TimsDataLoader::InMemory(TimsInMemoryLoader {
1362 raw_data_layout,
1363 index_converter,
1364 compressed_data: data,
1365 })
1366 }
1367
1368 pub fn new_lazy_with_mz_calibration(
1381 data_path: &str,
1382 tof_intercept: f64,
1383 tof_slope: f64,
1384 im_min: f64,
1385 im_max: f64,
1386 scan_max_index: u32,
1387 ) -> Self {
1388 let raw_data_layout = TimsRawDataLayout::new(data_path);
1389
1390 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1391 tof_intercept,
1392 tof_slope,
1393 im_min,
1394 im_max,
1395 scan_max_index,
1396 ));
1397
1398 TimsDataLoader::Lazy(TimsLazyLoder {
1399 raw_data_layout,
1400 index_converter,
1401 })
1402 }
1403
1404 pub fn new_in_memory_with_mz_calibration(
1409 data_path: &str,
1410 tof_intercept: f64,
1411 tof_slope: f64,
1412 im_min: f64,
1413 im_max: f64,
1414 scan_max_index: u32,
1415 ) -> Self {
1416 let raw_data_layout = TimsRawDataLayout::new(data_path);
1417
1418 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1419 tof_intercept,
1420 tof_slope,
1421 im_min,
1422 im_max,
1423 scan_max_index,
1424 ));
1425
1426 let mut file_path = PathBuf::from(data_path);
1427 file_path.push("analysis.tdf_bin");
1428 let mut infile = File::open(file_path).unwrap();
1429 let mut data = Vec::new();
1430 infile.read_to_end(&mut data).unwrap();
1431
1432 TimsDataLoader::InMemory(TimsInMemoryLoader {
1433 raw_data_layout,
1434 index_converter,
1435 compressed_data: data,
1436 })
1437 }
1438
1439 pub fn get_index_converter(&self) -> &dyn IndexConverter {
1440 match self {
1441 TimsDataLoader::InMemory(loader) => &loader.index_converter,
1442 TimsDataLoader::Lazy(loader) => &loader.index_converter,
1443 }
1444 }
1445
1446 pub fn get_frame_with_converter(&self, frame_id: u32, conv: &dyn IndexConverter) -> TimsFrame {
1448 match self {
1449 TimsDataLoader::InMemory(loader) => loader.get_frame_with_converter(frame_id, conv),
1450 TimsDataLoader::Lazy(loader) => loader.get_frame_with_converter(frame_id, conv),
1451 }
1452 }
1453
1454 pub fn raw_data_path(&self) -> &str {
1456 match self {
1457 TimsDataLoader::InMemory(loader) => &loader.raw_data_layout.raw_data_path,
1458 TimsDataLoader::Lazy(loader) => &loader.raw_data_layout.raw_data_path,
1459 }
1460 }
1461
1462 pub fn sync_index_converter(&self) -> Option<&(dyn IndexConverter + Sync)> {
1466 let converter = match self {
1467 TimsDataLoader::InMemory(loader) => &loader.index_converter,
1468 TimsDataLoader::Lazy(loader) => &loader.index_converter,
1469 };
1470 match converter {
1471 TimsIndexConverter::BrukerLib(_) => None,
1472 TimsIndexConverter::Simple(c) => Some(c),
1473 TimsIndexConverter::Calibrated(c) => Some(c),
1474 TimsIndexConverter::Lookup(c) => Some(c),
1475 TimsIndexConverter::BrukerFormula(c) => Some(c),
1476 }
1477 }
1478
1479 pub fn sdk_free_converter(&self) -> Option<TimsIndexConverter> {
1486 if !self.uses_bruker_sdk() {
1487 return None;
1488 }
1489 BrukerFormulaConverter::from_d_folder(self.raw_data_path(), 1)
1490 .ok()
1491 .filter(|c| c.is_sdk_exact())
1494 .map(TimsIndexConverter::BrukerFormula)
1495 }
1496
1497 pub fn uses_bruker_sdk(&self) -> bool {
1501 match self {
1502 TimsDataLoader::InMemory(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1503 TimsDataLoader::Lazy(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1504 }
1505 }
1506}
1507
1508impl TimsData for TimsDataLoader {
1509 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1510 match self {
1511 TimsDataLoader::InMemory(loader) => loader.get_frame(frame_id),
1512 TimsDataLoader::Lazy(loader) => loader.get_frame(frame_id),
1513 }
1514 }
1515 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1516 match self {
1517 TimsDataLoader::InMemory(loader) => loader.get_raw_frame(frame_id),
1518 TimsDataLoader::Lazy(loader) => loader.get_raw_frame(frame_id),
1519 }
1520 }
1521
1522 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1523 match self {
1524 TimsDataLoader::InMemory(loader) => loader.get_slice(frame_ids, num_threads),
1525 TimsDataLoader::Lazy(loader) => loader.get_slice(frame_ids, num_threads),
1526 }
1527 }
1528
1529 fn get_acquisition_mode(&self) -> AcquisitionMode {
1530 match self {
1531 TimsDataLoader::InMemory(loader) => loader.get_acquisition_mode(),
1532 TimsDataLoader::Lazy(loader) => loader.get_acquisition_mode(),
1533 }
1534 }
1535
1536 fn get_frame_count(&self) -> i32 {
1537 match self {
1538 TimsDataLoader::InMemory(loader) => loader.get_frame_count(),
1539 TimsDataLoader::Lazy(loader) => loader.get_frame_count(),
1540 }
1541 }
1542
1543 fn get_data_path(&self) -> &str {
1544 match self {
1545 TimsDataLoader::InMemory(loader) => loader.get_data_path(),
1546 TimsDataLoader::Lazy(loader) => loader.get_data_path(),
1547 }
1548 }
1549}
1550
1551pub struct SimpleIndexConverter {
1552 pub tof_intercept: f64,
1553 pub tof_slope: f64,
1554 pub scan_intercept: f64,
1555 pub scan_slope: f64,
1556}
1557
1558impl SimpleIndexConverter {
1559 pub fn from_boundaries(
1560 mz_min: f64,
1561 mz_max: f64,
1562 tof_max_index: u32,
1563 im_min: f64,
1564 im_max: f64,
1565 scan_max_index: u32,
1566 ) -> Self {
1567 let tof_intercept: f64 = mz_min.sqrt();
1568 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1569
1570 let scan_intercept: f64 = im_max;
1571 let scan_slope: f64 = (im_min - scan_intercept) / scan_max_index as f64;
1572 Self {
1573 tof_intercept,
1574 tof_slope,
1575 scan_intercept,
1576 scan_slope,
1577 }
1578 }
1579}
1580
1581impl IndexConverter for SimpleIndexConverter {
1582 fn tof_to_mz(&self, _frame_id: u32, _tof_values: &Vec<u32>) -> Vec<f64> {
1583 let mut mz_values: Vec<f64> = Vec::new();
1584 mz_values.resize(_tof_values.len(), 0.0);
1585
1586 for (i, &val) in _tof_values.iter().enumerate() {
1587 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1588 }
1589
1590 mz_values
1591 }
1592
1593 fn mz_to_tof(&self, _frame_id: u32, _mz_values: &Vec<f64>) -> Vec<u32> {
1594 let mut tof_values: Vec<u32> = Vec::new();
1595 tof_values.resize(_mz_values.len(), 0);
1596
1597 for (i, &val) in _mz_values.iter().enumerate() {
1598 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1599 }
1600
1601 tof_values
1602 }
1603
1604 fn scan_to_inverse_mobility(&self, _frame_id: u32, _scan_values: &Vec<u32>) -> Vec<f64> {
1605 let mut inv_mobility_values: Vec<f64> = Vec::new();
1606 inv_mobility_values.resize(_scan_values.len(), 0.0);
1607
1608 for (i, &val) in _scan_values.iter().enumerate() {
1609 inv_mobility_values[i] = self.scan_intercept + self.scan_slope * val as f64;
1610 }
1611
1612 inv_mobility_values
1613 }
1614
1615 fn inverse_mobility_to_scan(
1616 &self,
1617 _frame_id: u32,
1618 _inverse_mobility_values: &Vec<f64>,
1619 ) -> Vec<u32> {
1620 let mut scan_values: Vec<u32> = Vec::new();
1621 scan_values.resize(_inverse_mobility_values.len(), 0);
1622
1623 for (i, &val) in _inverse_mobility_values.iter().enumerate() {
1624 scan_values[i] = ((val - self.scan_intercept) / self.scan_slope) as u32;
1625 }
1626
1627 scan_values
1628 }
1629}
1630
1631pub struct CalibratedIndexConverter {
1642 pub tof_intercept: f64,
1643 pub tof_slope: f64,
1644 pub scan_intercept: f64,
1645 pub scan_slope: f64,
1646}
1647
1648impl CalibratedIndexConverter {
1649 pub fn new(
1658 tof_intercept: f64,
1659 tof_slope: f64,
1660 im_min: f64,
1661 im_max: f64,
1662 scan_max_index: u32,
1663 ) -> Self {
1664 let scan_intercept = im_max;
1665 let scan_slope = (im_min - scan_intercept) / scan_max_index as f64;
1666 Self {
1667 tof_intercept,
1668 tof_slope,
1669 scan_intercept,
1670 scan_slope,
1671 }
1672 }
1673}
1674
1675impl IndexConverter for CalibratedIndexConverter {
1676 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1677 let mut mz_values: Vec<f64> = Vec::with_capacity(tof_values.len());
1678
1679 for &tof_index in tof_values.iter() {
1680 let sqrt_mz = self.tof_intercept + self.tof_slope * tof_index as f64;
1682 mz_values.push(sqrt_mz * sqrt_mz);
1683 }
1684
1685 mz_values
1686 }
1687
1688 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1689 let mut tof_values: Vec<u32> = Vec::with_capacity(mz_values.len());
1690
1691 for &mz in mz_values.iter() {
1692 let sqrt_mz = mz.sqrt();
1693 tof_values.push(((sqrt_mz - self.tof_intercept) / self.tof_slope) as u32);
1695 }
1696
1697 tof_values
1698 }
1699
1700 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1701 let mut inv_mobility_values: Vec<f64> = Vec::with_capacity(scan_values.len());
1702
1703 for &val in scan_values.iter() {
1704 inv_mobility_values.push(self.scan_intercept + self.scan_slope * val as f64);
1705 }
1706
1707 inv_mobility_values
1708 }
1709
1710 fn inverse_mobility_to_scan(
1711 &self,
1712 _frame_id: u32,
1713 inverse_mobility_values: &Vec<f64>,
1714 ) -> Vec<u32> {
1715 let mut scan_values: Vec<u32> = Vec::with_capacity(inverse_mobility_values.len());
1716
1717 for &val in inverse_mobility_values.iter() {
1718 scan_values.push(((val - self.scan_intercept) / self.scan_slope) as u32);
1719 }
1720
1721 scan_values
1722 }
1723}
1724
1725pub struct LookupIndexConverter {
1738 pub tof_intercept: f64,
1740 pub tof_slope: f64,
1741
1742 pub im_lookup: Vec<f64>,
1745
1746 pub im_min: f64,
1749 pub im_max: f64,
1750}
1751
1752impl LookupIndexConverter {
1753 pub fn new(
1764 mz_min: f64,
1765 mz_max: f64,
1766 tof_max_index: u32,
1767 im_lookup: Vec<f64>,
1768 ) -> Self {
1769 let tof_intercept: f64 = mz_min.sqrt();
1770 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1771
1772 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1774 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1775
1776 Self {
1777 tof_intercept,
1778 tof_slope,
1779 im_lookup,
1780 im_min,
1781 im_max,
1782 }
1783 }
1784
1785 pub fn with_mz_fit(tof_intercept: f64, tof_slope: f64, im_lookup: Vec<f64>) -> Self {
1796 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1798 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1799
1800 Self {
1801 tof_intercept,
1802 tof_slope,
1803 im_lookup,
1804 im_min,
1805 im_max,
1806 }
1807 }
1808}
1809
1810impl IndexConverter for LookupIndexConverter {
1811 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1812 let mut mz_values: Vec<f64> = Vec::new();
1813 mz_values.resize(tof_values.len(), 0.0);
1814
1815 for (i, &val) in tof_values.iter().enumerate() {
1816 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1817 }
1818
1819 mz_values
1820 }
1821
1822 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1823 let mut tof_values: Vec<u32> = Vec::new();
1824 tof_values.resize(mz_values.len(), 0);
1825
1826 for (i, &val) in mz_values.iter().enumerate() {
1827 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1828 }
1829
1830 tof_values
1831 }
1832
1833 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1834 scan_values
1836 .iter()
1837 .map(|&s| {
1838 self.im_lookup
1839 .get(s as usize)
1840 .copied()
1841 .unwrap_or(f64::NAN)
1842 })
1843 .collect()
1844 }
1845
1846 fn inverse_mobility_to_scan(
1847 &self,
1848 _frame_id: u32,
1849 inverse_mobility_values: &Vec<f64>,
1850 ) -> Vec<u32> {
1851 inverse_mobility_values
1854 .iter()
1855 .map(|&im| {
1856 if im.is_nan() || self.im_lookup.is_empty() {
1857 return 0;
1858 }
1859
1860 let mut best_scan = 0usize;
1863 let mut best_diff = f64::INFINITY;
1864
1865 for (scan, &lookup_im) in self.im_lookup.iter().enumerate() {
1866 let diff = (lookup_im - im).abs();
1867 if diff < best_diff {
1868 best_diff = diff;
1869 best_scan = scan;
1870 }
1871 }
1872
1873 best_scan as u32
1874 })
1875 .collect()
1876 }
1877}