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 {
384 pub mz: crate::data::calibration::MzCalibrator,
385 pub im: crate::data::calibration::MobilityCalibrator,
386 pub mz_model_type: i64,
387}
388
389impl BrukerFormulaConverter {
390 pub fn from_d_folder(
393 data_path: &str,
394 frame_id: u32,
395 ) -> Result<Self, Box<dyn std::error::Error>> {
396 use crate::data::calibration::{MobilityCalibrator, MzCalibrator};
397 use crate::data::meta::{read_mz_calibration, read_tims_calibration};
398
399 let tdf = PathBuf::from(data_path).join("analysis.tdf");
400 let con = rusqlite::Connection::open(&tdf)?;
401 let (t1, t2, mz_id, tims_id): (f64, f64, i64, i64) = con.query_row(
402 "SELECT T1, T2, MzCalibration, TimsCalibration FROM Frames WHERE Id = ?1",
403 [frame_id],
404 |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
405 )?;
406
407 let mzc = read_mz_calibration(data_path)?
408 .into_iter()
409 .find(|c| c.id == mz_id)
410 .ok_or("MzCalibration row not found")?;
411 let tc = read_tims_calibration(data_path)?
412 .into_iter()
413 .find(|c| c.id == tims_id)
414 .ok_or("TimsCalibration row not found")?;
415
416 if mzc.model_type != 1 && mzc.model_type != 2 {
419 return Err(format!("unsupported MzCalibration ModelType {}", mzc.model_type).into());
420 }
421 if tc.model_type != 2 {
422 return Err(format!("unsupported TimsCalibration ModelType {}", tc.model_type).into());
423 }
424
425 let mz = MzCalibrator::new(
426 mzc.model_type,
427 mzc.digitizer_timebase,
428 mzc.digitizer_delay,
429 mzc.t1,
430 mzc.t2,
431 mzc.dc1,
432 mzc.dc2,
433 mzc.c0,
434 mzc.c1,
435 mzc.c2,
436 mzc.c3,
437 mzc.c4,
438 t1,
439 t2,
440 );
441 let im = MobilityCalibrator::new(
442 tc.c0, tc.c1, tc.c2, tc.c3, tc.c4, tc.c5, tc.c6, tc.c7, tc.c8, tc.c9,
443 );
444 Ok(Self { mz, im, mz_model_type: mzc.model_type })
445 }
446}
447
448impl IndexConverter for BrukerFormulaConverter {
449 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
450 tof_values.iter().map(|&t| self.mz.tof_to_mz(t)).collect()
451 }
452 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
453 mz_values.iter().map(|&m| self.mz.mz_to_tof(m)).collect()
454 }
455 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
456 scan_values
457 .iter()
458 .map(|&s| self.im.scan_to_one_over_k0(s))
459 .collect()
460 }
461 fn inverse_mobility_to_scan(
462 &self,
463 _frame_id: u32,
464 inverse_mobility_values: &Vec<f64>,
465 ) -> Vec<u32> {
466 inverse_mobility_values
467 .iter()
468 .map(|&v| self.im.one_over_k0_to_scan(v))
469 .collect()
470 }
471}
472
473pub enum TimsIndexConverter {
474 Simple(SimpleIndexConverter),
475 Calibrated(CalibratedIndexConverter),
476 BrukerLib(BrukerLibTimsDataConverter),
477 Lookup(LookupIndexConverter),
478 BrukerFormula(BrukerFormulaConverter),
479}
480
481impl IndexConverter for TimsIndexConverter {
482 fn tof_to_mz(&self, frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
483 match self {
484 TimsIndexConverter::Simple(converter) => converter.tof_to_mz(frame_id, tof_values),
485 TimsIndexConverter::Calibrated(converter) => converter.tof_to_mz(frame_id, tof_values),
486 TimsIndexConverter::BrukerLib(converter) => converter.tof_to_mz(frame_id, tof_values),
487 TimsIndexConverter::Lookup(converter) => converter.tof_to_mz(frame_id, tof_values),
488 TimsIndexConverter::BrukerFormula(converter) => converter.tof_to_mz(frame_id, tof_values),
489 }
490 }
491
492 fn mz_to_tof(&self, frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
493 match self {
494 TimsIndexConverter::Simple(converter) => converter.mz_to_tof(frame_id, mz_values),
495 TimsIndexConverter::Calibrated(converter) => converter.mz_to_tof(frame_id, mz_values),
496 TimsIndexConverter::BrukerLib(converter) => converter.mz_to_tof(frame_id, mz_values),
497 TimsIndexConverter::Lookup(converter) => converter.mz_to_tof(frame_id, mz_values),
498 TimsIndexConverter::BrukerFormula(converter) => converter.mz_to_tof(frame_id, mz_values),
499 }
500 }
501
502 fn scan_to_inverse_mobility(&self, frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
503 match self {
504 TimsIndexConverter::Simple(converter) => {
505 converter.scan_to_inverse_mobility(frame_id, scan_values)
506 }
507 TimsIndexConverter::Calibrated(converter) => {
508 converter.scan_to_inverse_mobility(frame_id, scan_values)
509 }
510 TimsIndexConverter::BrukerLib(converter) => {
511 converter.scan_to_inverse_mobility(frame_id, scan_values)
512 }
513 TimsIndexConverter::Lookup(converter) => {
514 converter.scan_to_inverse_mobility(frame_id, scan_values)
515 }
516 TimsIndexConverter::BrukerFormula(converter) => {
517 converter.scan_to_inverse_mobility(frame_id, scan_values)
518 }
519 }
520 }
521
522 fn inverse_mobility_to_scan(
523 &self,
524 frame_id: u32,
525 inverse_mobility_values: &Vec<f64>,
526 ) -> Vec<u32> {
527 match self {
528 TimsIndexConverter::Simple(converter) => {
529 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
530 }
531 TimsIndexConverter::Calibrated(converter) => {
532 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
533 }
534 TimsIndexConverter::BrukerLib(converter) => {
535 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
536 }
537 TimsIndexConverter::Lookup(converter) => {
538 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
539 }
540 TimsIndexConverter::BrukerFormula(converter) => {
541 converter.inverse_mobility_to_scan(frame_id, inverse_mobility_values)
542 }
543 }
544 }
545}
546
547pub struct TimsLazyLoder {
548 pub raw_data_layout: TimsRawDataLayout,
549 pub index_converter: TimsIndexConverter,
550}
551
552impl TimsData for TimsLazyLoder {
553 fn get_frame(&self, frame_id: u32) -> TimsFrame {
554 let frame_index = (frame_id - 1) as usize;
555
556 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
558
559 if num_peaks == 0 {
560
561 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
562
563 let ms_type = match ms_type_raw {
564 0 => MsType::Precursor,
565 8 => MsType::FragmentDda,
566 9 => MsType::FragmentDia,
567 _ => MsType::Unknown,
568 };
569
570 return TimsFrame {
571 frame_id: frame_id as i32,
572 ms_type,
573 scan: Vec::new(),
574 tof: Vec::new(),
575 ims_frame: ImsFrame::new(
576 self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
577 Vec::new(),
578 Vec::new(),
579 Vec::new(),
580 ),
581 };
582 }
583
584 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
585
586 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
587 file_path.push("analysis.tdf_bin");
588 let mut infile = File::open(&file_path).unwrap();
589
590 infile.seek(SeekFrom::Start(offset)).unwrap();
591
592 let mut bin_buffer = [0u8; 4];
593 infile.read_exact(&mut bin_buffer).unwrap();
594 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
595
596 infile.read_exact(&mut bin_buffer).unwrap();
597
598 match self.raw_data_layout.global_meta_data.tims_compression_type {
599 1 => {
600 let scan_count =
601 self.raw_data_layout.frame_meta_data[frame_index].num_scans as usize;
602 let num_peaks = num_peaks as usize;
603 let compression_offset = 8 + (scan_count + 1) * 4;
604
605 let mut scan_offsets_buffer = vec![0u8; (scan_count + 1) * 4];
606 infile.read_exact(&mut scan_offsets_buffer).unwrap();
607
608 let mut scan_offsets = Vec::with_capacity(scan_count + 1);
609 {
610 let mut rdr = Cursor::new(&scan_offsets_buffer);
611 for _ in 0..(scan_count + 1) {
612 scan_offsets.push(rdr.read_i32::<LittleEndian>().unwrap());
613 }
614 }
615
616 for offs in &mut scan_offsets {
617 *offs -= compression_offset as i32;
618 }
619
620 let remaining_size = (bin_size as usize - compression_offset) as usize;
621 let mut compressed_data = vec![0u8; remaining_size];
622 infile.read_exact(&mut compressed_data).unwrap();
623
624 let mut scan_indices_ = vec![0i64; scan_count];
625 let mut tof_indices_ = vec![0u32; num_peaks];
626 let mut intensities_ = vec![0u16; num_peaks];
627
628 let mut scan_start = 0usize;
629
630 for scan_index in 0..scan_count {
631 let start = scan_offsets[scan_index] as usize;
632 let end = scan_offsets[scan_index + 1] as usize;
633
634 if start == end {
635 continue;
636 }
637
638 let max_output_size = num_peaks * 8;
639 let decompressed_bytes =
640 lzf_decompress(&compressed_data[start..end], max_output_size)
641 .expect("LZF decompression failed.");
642
643 scan_start += parse_decompressed_bruker_binary_type1(
644 &decompressed_bytes,
645 &mut scan_indices_,
646 &mut tof_indices_,
647 &mut intensities_,
648 scan_start,
649 scan_index,
650 );
651 }
652
653 let mut scan = Vec::with_capacity(num_peaks);
655 {
656 let mut current_scan_index = 0u32;
657 for &size in &scan_indices_ {
658 let sz = size as usize;
659 for _ in 0..sz {
660 scan.push(current_scan_index);
661 }
662 current_scan_index += 1;
663 }
664 }
665
666 let intensity_dbl = intensities_.iter().map(|&x| x as f64).collect::<Vec<f64>>();
667 let tof_i32 = tof_indices_.iter().map(|&x| x as i32).collect::<Vec<i32>>();
668
669 let mz = self.index_converter.tof_to_mz(frame_id, &tof_indices_);
670 let inv_mobility = self
671 .index_converter
672 .scan_to_inverse_mobility(frame_id, &scan);
673
674 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
675 let ms_type = match ms_type_raw {
676 0 => MsType::Precursor,
677 8 => MsType::FragmentDda,
678 9 => MsType::FragmentDia,
679 _ => MsType::Unknown,
680 };
681
682 TimsFrame {
683 frame_id: frame_id as i32,
684 ms_type,
685 scan: scan.iter().map(|&x| x as i32).collect(),
686 tof: tof_i32,
687 ims_frame: ImsFrame::new(
688 self.raw_data_layout.frame_meta_data[frame_index].time,
689 inv_mobility,
690 mz,
691 intensity_dbl,
692 ),
693 }
694 }
695
696 2 => {
698 let mut compressed_data = vec![0u8; bin_size as usize - 8];
699 infile.read_exact(&mut compressed_data).unwrap();
700
701 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
702
703 let (scan, tof, intensity) =
704 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
705 let intensity_dbl = intensity.iter().map(|&x| x as f64).collect();
706 let tof_i32 = tof.iter().map(|&x| x as i32).collect();
707 let scan = flatten_scan_values(&scan, true);
708
709 let mz = self.index_converter.tof_to_mz(frame_id, &tof);
710 let inv_mobility = self
711 .index_converter
712 .scan_to_inverse_mobility(frame_id, &scan);
713
714 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
715
716 let ms_type = match ms_type_raw {
717 0 => MsType::Precursor,
718 8 => MsType::FragmentDda,
719 9 => MsType::FragmentDia,
720 _ => MsType::Unknown,
721 };
722
723 TimsFrame {
724 frame_id: frame_id as i32,
725 ms_type,
726 scan: scan.iter().map(|&x| x as i32).collect(),
727 tof: tof_i32,
728 ims_frame: ImsFrame::new(
729 self.raw_data_layout.frame_meta_data[frame_index].time,
730 inv_mobility,
731 mz,
732 intensity_dbl,
733 ),
734 }
735 }
736
737 _ => {
738 panic!("TimsCompressionType is not 1 or 2.")
739 }
740 }
741 }
742
743 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
744 let frame_index = (frame_id - 1) as usize;
745 let offset = self.raw_data_layout.tims_offset_values[frame_index] as u64;
746
747 let num_peaks = self.raw_data_layout.frame_meta_data[frame_index].num_peaks;
749
750 if num_peaks == 0 {
751 return RawTimsFrame {
752 frame_id: frame_id as i32,
753 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
754 ms_type: MsType::Unknown,
755 scan: Vec::new(),
756 tof: Vec::new(),
757 intensity: Vec::new(),
758 };
759 }
760
761 let mut file_path = PathBuf::from(&self.raw_data_layout.raw_data_path);
762 file_path.push("analysis.tdf_bin");
763 let mut infile = File::open(&file_path).unwrap();
764
765 infile.seek(SeekFrom::Start(offset)).unwrap();
766
767 let mut bin_buffer = [0u8; 4];
768 infile.read_exact(&mut bin_buffer).unwrap();
769 let bin_size = Cursor::new(bin_buffer).read_i32::<LittleEndian>().unwrap();
770
771 infile.read_exact(&mut bin_buffer).unwrap();
772
773 match self.raw_data_layout.global_meta_data.tims_compression_type {
774 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 1 => {
775 panic!("Decompression Type1 not implemented.");
776 }
777
778 _ if self.raw_data_layout.global_meta_data.tims_compression_type == 2 => {
780 let mut compressed_data = vec![0u8; bin_size as usize - 8];
781 infile.read_exact(&mut compressed_data).unwrap();
782
783 let decompressed_bytes = zstd_decompress(&compressed_data).unwrap();
784
785 let (scan, tof, intensity) =
786 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
787
788 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
789
790 let ms_type = match ms_type_raw {
791 0 => MsType::Precursor,
792 8 => MsType::FragmentDda,
793 9 => MsType::FragmentDia,
794 _ => MsType::Unknown,
795 };
796
797 let frame = RawTimsFrame {
798 frame_id: frame_id as i32,
799 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize]
800 .time,
801 ms_type,
802 scan,
803 tof,
804 intensity: intensity.iter().map(|&x| x as f64).collect(),
805 };
806
807 return frame;
808 }
809
810 _ => {
812 panic!("TimsCompressionType is not 1 or 2.")
813 }
814 }
815 }
816
817 fn get_slice(&self, frame_ids: Vec<u32>, _num_threads: usize) -> TimsSlice {
818 let result: Vec<TimsFrame> = frame_ids.into_iter().map(|f| self.get_frame(f)).collect();
819
820 TimsSlice { frames: result }
821 }
822
823 fn get_acquisition_mode(&self) -> AcquisitionMode {
824 self.raw_data_layout.acquisition_mode.clone()
825 }
826
827 fn get_frame_count(&self) -> i32 {
828 self.raw_data_layout.frame_meta_data.len() as i32
829 }
830
831 fn get_data_path(&self) -> &str {
832 &self.raw_data_layout.raw_data_path
833 }
834}
835
836pub struct TimsInMemoryLoader {
837 pub raw_data_layout: TimsRawDataLayout,
838 pub index_converter: TimsIndexConverter,
839 compressed_data: Vec<u8>,
840}
841
842impl TimsData for TimsInMemoryLoader {
843 fn get_frame(&self, frame_id: u32) -> TimsFrame {
844 let raw_frame = self.get_raw_frame(frame_id);
845
846 let raw_frame = match raw_frame.ms_type {
847 MsType::FragmentDda => raw_frame.smooth(1).centroid(1),
848 _ => raw_frame,
849 };
850
851 if raw_frame.scan.is_empty() {
853 return TimsFrame::default();
854 }
855
856 let tof_i32 = raw_frame.tof.iter().map(|&x| x as i32).collect();
857 let scan = flatten_scan_values(&raw_frame.scan, true);
858
859 let mz = self.index_converter.tof_to_mz(frame_id, &raw_frame.tof);
860 let inverse_mobility = self
861 .index_converter
862 .scan_to_inverse_mobility(frame_id, &scan);
863
864 let ims_frame = ImsFrame::new(
865 raw_frame.retention_time,
866 inverse_mobility,
867 mz,
868 raw_frame.intensity,
869 );
870
871 TimsFrame {
872 frame_id: frame_id as i32,
873 ms_type: raw_frame.ms_type,
874 scan: scan.iter().map(|&x| x as i32).collect(),
875 tof: tof_i32,
876 ims_frame,
877 }
878 }
879
880 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
881 let frame_index = (frame_id - 1) as usize;
882 let offset = self.raw_data_layout.tims_offset_values[frame_index] as usize;
883
884 let bin_size_offset = offset + 4; let bin_size = Cursor::new(&self.compressed_data[offset..bin_size_offset])
886 .read_i32::<LittleEndian>()
887 .unwrap();
888
889 let data_offset = bin_size_offset + 4; let frame_data = &self.compressed_data[data_offset..data_offset + bin_size as usize - 8];
891
892 let decompressed_bytes = zstd_decompress(&frame_data).unwrap();
893
894 let (scan, tof, intensity) =
895 parse_decompressed_bruker_binary_data(&decompressed_bytes).unwrap();
896
897 let ms_type_raw = self.raw_data_layout.frame_meta_data[frame_index].ms_ms_type;
898
899 let ms_type = match ms_type_raw {
900 0 => MsType::Precursor,
901 8 => MsType::FragmentDda,
902 9 => MsType::FragmentDia,
903 _ => MsType::Unknown,
904 };
905
906 let raw_frame = RawTimsFrame {
907 frame_id: frame_id as i32,
908 retention_time: self.raw_data_layout.frame_meta_data[(frame_id - 1) as usize].time,
909 ms_type,
910 scan,
911 tof,
912 intensity: intensity.iter().map(|&x| x as f64).collect(),
913 };
914
915 raw_frame
916 }
917
918 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
919 let pool = ThreadPoolBuilder::new()
920 .num_threads(num_threads)
921 .build()
922 .unwrap();
923 let frames = pool.install(|| {
924 frame_ids
925 .par_iter()
926 .map(|&frame_id| self.get_frame(frame_id))
927 .collect()
928 });
929
930 TimsSlice { frames }
931 }
932
933 fn get_acquisition_mode(&self) -> AcquisitionMode {
934 self.raw_data_layout.acquisition_mode.clone()
935 }
936
937 fn get_frame_count(&self) -> i32 {
938 self.raw_data_layout.frame_meta_data.len() as i32
939 }
940
941 fn get_data_path(&self) -> &str {
942 &self.raw_data_layout.raw_data_path
943 }
944}
945
946pub enum TimsDataLoader {
947 InMemory(TimsInMemoryLoader),
948 Lazy(TimsLazyLoder),
949}
950
951fn build_index_converter(
959 bruker_lib_path: &str,
960 data_path: &str,
961 use_bruker_sdk: bool,
962 scan_max_index: u32,
963 im_lower: f64,
964 im_upper: f64,
965 tof_max_index: u32,
966 mz_lower: f64,
967 mz_upper: f64,
968) -> TimsIndexConverter {
969 if use_bruker_sdk {
970 match BrukerLibTimsDataConverter::try_new(bruker_lib_path, data_path) {
971 Ok(converter) => return TimsIndexConverter::BrukerLib(converter),
972 Err(e) => eprintln!(
973 "Warning: Bruker SDK requested but failed to load ({e}); \
974 falling back to derived/simple m/z calibration."
975 ),
976 }
977 }
978 match derive_mz_calibration(bruker_lib_path, data_path, tof_max_index) {
980 Some((intercept, slope)) => TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
981 intercept,
982 slope,
983 im_lower,
984 im_upper,
985 scan_max_index,
986 )),
987 None => {
988 eprintln!(
989 "Warning: Could not derive m/z calibration from SDK. \
990 Using simple boundary model which may have ~5 Da error on some datasets. \
991 This typically happens on macOS where Bruker SDK is not available."
992 );
993 TimsIndexConverter::Simple(SimpleIndexConverter::from_boundaries(
994 mz_lower,
995 mz_upper,
996 tof_max_index,
997 im_lower,
998 im_upper,
999 scan_max_index,
1000 ))
1001 }
1002 }
1003}
1004
1005impl TimsDataLoader {
1006 pub fn new_lazy(
1007 bruker_lib_path: &str,
1008 data_path: &str,
1009 use_bruker_sdk: bool,
1010 scan_max_index: u32,
1011 im_lower: f64,
1012 im_upper: f64,
1013 tof_max_index: u32,
1014 mz_lower: f64,
1015 mz_upper: f64,
1016 ) -> Self {
1017 let raw_data_layout = TimsRawDataLayout::new(data_path);
1018 let index_converter = build_index_converter(
1019 bruker_lib_path,
1020 data_path,
1021 use_bruker_sdk,
1022 scan_max_index,
1023 im_lower,
1024 im_upper,
1025 tof_max_index,
1026 mz_lower,
1027 mz_upper,
1028 );
1029
1030 TimsDataLoader::Lazy(TimsLazyLoder {
1031 raw_data_layout,
1032 index_converter,
1033 })
1034 }
1035
1036 pub fn new_in_memory(
1037 bruker_lib_path: &str,
1038 data_path: &str,
1039 use_bruker_sdk: bool,
1040 scan_max_index: u32,
1041 im_lower: f64,
1042 im_upper: f64,
1043 tof_max_index: u32,
1044 mz_lower: f64,
1045 mz_upper: f64,
1046 ) -> Self {
1047 let raw_data_layout = TimsRawDataLayout::new(data_path);
1048 let index_converter = build_index_converter(
1049 bruker_lib_path,
1050 data_path,
1051 use_bruker_sdk,
1052 scan_max_index,
1053 im_lower,
1054 im_upper,
1055 tof_max_index,
1056 mz_lower,
1057 mz_upper,
1058 );
1059
1060 let mut file_path = PathBuf::from(data_path);
1061 file_path.push("analysis.tdf_bin");
1062 let mut infile = File::open(file_path).unwrap();
1063 let mut data = Vec::new();
1064 infile.read_to_end(&mut data).unwrap();
1065
1066 TimsDataLoader::InMemory(TimsInMemoryLoader {
1067 raw_data_layout,
1068 index_converter,
1069 compressed_data: data,
1070 })
1071 }
1072
1073 pub fn new_lazy_with_calibration(
1091 data_path: &str,
1092 bruker_lib_path: &str,
1093 tof_max_index: u32,
1094 mz_lower: f64,
1095 mz_upper: f64,
1096 im_lookup: Vec<f64>,
1097 ) -> Self {
1098 let raw_data_layout = TimsRawDataLayout::new(data_path);
1099
1100 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1101 bruker_lib_path,
1102 data_path,
1103 tof_max_index,
1104 mz_lower,
1105 mz_upper,
1106 im_lookup,
1107 ));
1108
1109 TimsDataLoader::Lazy(TimsLazyLoder {
1110 raw_data_layout,
1111 index_converter,
1112 })
1113 }
1114
1115 pub fn new_in_memory_with_calibration(
1133 data_path: &str,
1134 bruker_lib_path: &str,
1135 tof_max_index: u32,
1136 mz_lower: f64,
1137 mz_upper: f64,
1138 im_lookup: Vec<f64>,
1139 ) -> Self {
1140 let raw_data_layout = TimsRawDataLayout::new(data_path);
1141
1142 let index_converter = TimsIndexConverter::Lookup(build_lookup_converter(
1143 bruker_lib_path,
1144 data_path,
1145 tof_max_index,
1146 mz_lower,
1147 mz_upper,
1148 im_lookup,
1149 ));
1150
1151 let mut file_path = PathBuf::from(data_path);
1152 file_path.push("analysis.tdf_bin");
1153 let mut infile = File::open(file_path).unwrap();
1154 let mut data = Vec::new();
1155 infile.read_to_end(&mut data).unwrap();
1156
1157 TimsDataLoader::InMemory(TimsInMemoryLoader {
1158 raw_data_layout,
1159 index_converter,
1160 compressed_data: data,
1161 })
1162 }
1163
1164 pub fn new_lazy_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1172 let raw_data_layout = TimsRawDataLayout::new(data_path);
1173 let index_converter = TimsIndexConverter::BrukerFormula(
1174 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1175 );
1176 TimsDataLoader::Lazy(TimsLazyLoder {
1177 raw_data_layout,
1178 index_converter,
1179 })
1180 }
1181
1182 pub fn new_in_memory_with_bruker_formula(data_path: &str, calibration_frame_id: u32) -> Self {
1184 let raw_data_layout = TimsRawDataLayout::new(data_path);
1185 let index_converter = TimsIndexConverter::BrukerFormula(
1186 BrukerFormulaConverter::from_d_folder(data_path, calibration_frame_id).unwrap(),
1187 );
1188
1189 let mut file_path = PathBuf::from(data_path);
1190 file_path.push("analysis.tdf_bin");
1191 let mut infile = File::open(file_path).unwrap();
1192 let mut data = Vec::new();
1193 infile.read_to_end(&mut data).unwrap();
1194
1195 TimsDataLoader::InMemory(TimsInMemoryLoader {
1196 raw_data_layout,
1197 index_converter,
1198 compressed_data: data,
1199 })
1200 }
1201
1202 pub fn new_lazy_with_mz_calibration(
1215 data_path: &str,
1216 tof_intercept: f64,
1217 tof_slope: f64,
1218 im_min: f64,
1219 im_max: f64,
1220 scan_max_index: u32,
1221 ) -> Self {
1222 let raw_data_layout = TimsRawDataLayout::new(data_path);
1223
1224 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1225 tof_intercept,
1226 tof_slope,
1227 im_min,
1228 im_max,
1229 scan_max_index,
1230 ));
1231
1232 TimsDataLoader::Lazy(TimsLazyLoder {
1233 raw_data_layout,
1234 index_converter,
1235 })
1236 }
1237
1238 pub fn new_in_memory_with_mz_calibration(
1243 data_path: &str,
1244 tof_intercept: f64,
1245 tof_slope: f64,
1246 im_min: f64,
1247 im_max: f64,
1248 scan_max_index: u32,
1249 ) -> Self {
1250 let raw_data_layout = TimsRawDataLayout::new(data_path);
1251
1252 let index_converter = TimsIndexConverter::Calibrated(CalibratedIndexConverter::new(
1253 tof_intercept,
1254 tof_slope,
1255 im_min,
1256 im_max,
1257 scan_max_index,
1258 ));
1259
1260 let mut file_path = PathBuf::from(data_path);
1261 file_path.push("analysis.tdf_bin");
1262 let mut infile = File::open(file_path).unwrap();
1263 let mut data = Vec::new();
1264 infile.read_to_end(&mut data).unwrap();
1265
1266 TimsDataLoader::InMemory(TimsInMemoryLoader {
1267 raw_data_layout,
1268 index_converter,
1269 compressed_data: data,
1270 })
1271 }
1272
1273 pub fn get_index_converter(&self) -> &dyn IndexConverter {
1274 match self {
1275 TimsDataLoader::InMemory(loader) => &loader.index_converter,
1276 TimsDataLoader::Lazy(loader) => &loader.index_converter,
1277 }
1278 }
1279
1280 pub fn uses_bruker_sdk(&self) -> bool {
1284 match self {
1285 TimsDataLoader::InMemory(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1286 TimsDataLoader::Lazy(loader) => matches!(&loader.index_converter, TimsIndexConverter::BrukerLib(_)),
1287 }
1288 }
1289}
1290
1291impl TimsData for TimsDataLoader {
1292 fn get_frame(&self, frame_id: u32) -> TimsFrame {
1293 match self {
1294 TimsDataLoader::InMemory(loader) => loader.get_frame(frame_id),
1295 TimsDataLoader::Lazy(loader) => loader.get_frame(frame_id),
1296 }
1297 }
1298 fn get_raw_frame(&self, frame_id: u32) -> RawTimsFrame {
1299 match self {
1300 TimsDataLoader::InMemory(loader) => loader.get_raw_frame(frame_id),
1301 TimsDataLoader::Lazy(loader) => loader.get_raw_frame(frame_id),
1302 }
1303 }
1304
1305 fn get_slice(&self, frame_ids: Vec<u32>, num_threads: usize) -> TimsSlice {
1306 match self {
1307 TimsDataLoader::InMemory(loader) => loader.get_slice(frame_ids, num_threads),
1308 TimsDataLoader::Lazy(loader) => loader.get_slice(frame_ids, num_threads),
1309 }
1310 }
1311
1312 fn get_acquisition_mode(&self) -> AcquisitionMode {
1313 match self {
1314 TimsDataLoader::InMemory(loader) => loader.get_acquisition_mode(),
1315 TimsDataLoader::Lazy(loader) => loader.get_acquisition_mode(),
1316 }
1317 }
1318
1319 fn get_frame_count(&self) -> i32 {
1320 match self {
1321 TimsDataLoader::InMemory(loader) => loader.get_frame_count(),
1322 TimsDataLoader::Lazy(loader) => loader.get_frame_count(),
1323 }
1324 }
1325
1326 fn get_data_path(&self) -> &str {
1327 match self {
1328 TimsDataLoader::InMemory(loader) => loader.get_data_path(),
1329 TimsDataLoader::Lazy(loader) => loader.get_data_path(),
1330 }
1331 }
1332}
1333
1334pub struct SimpleIndexConverter {
1335 pub tof_intercept: f64,
1336 pub tof_slope: f64,
1337 pub scan_intercept: f64,
1338 pub scan_slope: f64,
1339}
1340
1341impl SimpleIndexConverter {
1342 pub fn from_boundaries(
1343 mz_min: f64,
1344 mz_max: f64,
1345 tof_max_index: u32,
1346 im_min: f64,
1347 im_max: f64,
1348 scan_max_index: u32,
1349 ) -> Self {
1350 let tof_intercept: f64 = mz_min.sqrt();
1351 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1352
1353 let scan_intercept: f64 = im_max;
1354 let scan_slope: f64 = (im_min - scan_intercept) / scan_max_index as f64;
1355 Self {
1356 tof_intercept,
1357 tof_slope,
1358 scan_intercept,
1359 scan_slope,
1360 }
1361 }
1362}
1363
1364impl IndexConverter for SimpleIndexConverter {
1365 fn tof_to_mz(&self, _frame_id: u32, _tof_values: &Vec<u32>) -> Vec<f64> {
1366 let mut mz_values: Vec<f64> = Vec::new();
1367 mz_values.resize(_tof_values.len(), 0.0);
1368
1369 for (i, &val) in _tof_values.iter().enumerate() {
1370 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1371 }
1372
1373 mz_values
1374 }
1375
1376 fn mz_to_tof(&self, _frame_id: u32, _mz_values: &Vec<f64>) -> Vec<u32> {
1377 let mut tof_values: Vec<u32> = Vec::new();
1378 tof_values.resize(_mz_values.len(), 0);
1379
1380 for (i, &val) in _mz_values.iter().enumerate() {
1381 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1382 }
1383
1384 tof_values
1385 }
1386
1387 fn scan_to_inverse_mobility(&self, _frame_id: u32, _scan_values: &Vec<u32>) -> Vec<f64> {
1388 let mut inv_mobility_values: Vec<f64> = Vec::new();
1389 inv_mobility_values.resize(_scan_values.len(), 0.0);
1390
1391 for (i, &val) in _scan_values.iter().enumerate() {
1392 inv_mobility_values[i] = self.scan_intercept + self.scan_slope * val as f64;
1393 }
1394
1395 inv_mobility_values
1396 }
1397
1398 fn inverse_mobility_to_scan(
1399 &self,
1400 _frame_id: u32,
1401 _inverse_mobility_values: &Vec<f64>,
1402 ) -> Vec<u32> {
1403 let mut scan_values: Vec<u32> = Vec::new();
1404 scan_values.resize(_inverse_mobility_values.len(), 0);
1405
1406 for (i, &val) in _inverse_mobility_values.iter().enumerate() {
1407 scan_values[i] = ((val - self.scan_intercept) / self.scan_slope) as u32;
1408 }
1409
1410 scan_values
1411 }
1412}
1413
1414pub struct CalibratedIndexConverter {
1425 pub tof_intercept: f64,
1426 pub tof_slope: f64,
1427 pub scan_intercept: f64,
1428 pub scan_slope: f64,
1429}
1430
1431impl CalibratedIndexConverter {
1432 pub fn new(
1441 tof_intercept: f64,
1442 tof_slope: f64,
1443 im_min: f64,
1444 im_max: f64,
1445 scan_max_index: u32,
1446 ) -> Self {
1447 let scan_intercept = im_max;
1448 let scan_slope = (im_min - scan_intercept) / scan_max_index as f64;
1449 Self {
1450 tof_intercept,
1451 tof_slope,
1452 scan_intercept,
1453 scan_slope,
1454 }
1455 }
1456}
1457
1458impl IndexConverter for CalibratedIndexConverter {
1459 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1460 let mut mz_values: Vec<f64> = Vec::with_capacity(tof_values.len());
1461
1462 for &tof_index in tof_values.iter() {
1463 let sqrt_mz = self.tof_intercept + self.tof_slope * tof_index as f64;
1465 mz_values.push(sqrt_mz * sqrt_mz);
1466 }
1467
1468 mz_values
1469 }
1470
1471 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1472 let mut tof_values: Vec<u32> = Vec::with_capacity(mz_values.len());
1473
1474 for &mz in mz_values.iter() {
1475 let sqrt_mz = mz.sqrt();
1476 tof_values.push(((sqrt_mz - self.tof_intercept) / self.tof_slope) as u32);
1478 }
1479
1480 tof_values
1481 }
1482
1483 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1484 let mut inv_mobility_values: Vec<f64> = Vec::with_capacity(scan_values.len());
1485
1486 for &val in scan_values.iter() {
1487 inv_mobility_values.push(self.scan_intercept + self.scan_slope * val as f64);
1488 }
1489
1490 inv_mobility_values
1491 }
1492
1493 fn inverse_mobility_to_scan(
1494 &self,
1495 _frame_id: u32,
1496 inverse_mobility_values: &Vec<f64>,
1497 ) -> Vec<u32> {
1498 let mut scan_values: Vec<u32> = Vec::with_capacity(inverse_mobility_values.len());
1499
1500 for &val in inverse_mobility_values.iter() {
1501 scan_values.push(((val - self.scan_intercept) / self.scan_slope) as u32);
1502 }
1503
1504 scan_values
1505 }
1506}
1507
1508pub struct LookupIndexConverter {
1521 pub tof_intercept: f64,
1523 pub tof_slope: f64,
1524
1525 pub im_lookup: Vec<f64>,
1528
1529 pub im_min: f64,
1532 pub im_max: f64,
1533}
1534
1535impl LookupIndexConverter {
1536 pub fn new(
1547 mz_min: f64,
1548 mz_max: f64,
1549 tof_max_index: u32,
1550 im_lookup: Vec<f64>,
1551 ) -> Self {
1552 let tof_intercept: f64 = mz_min.sqrt();
1553 let tof_slope: f64 = (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
1554
1555 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1557 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1558
1559 Self {
1560 tof_intercept,
1561 tof_slope,
1562 im_lookup,
1563 im_min,
1564 im_max,
1565 }
1566 }
1567
1568 pub fn with_mz_fit(tof_intercept: f64, tof_slope: f64, im_lookup: Vec<f64>) -> Self {
1579 let im_min = im_lookup.iter().cloned().fold(f64::INFINITY, f64::min);
1581 let im_max = im_lookup.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1582
1583 Self {
1584 tof_intercept,
1585 tof_slope,
1586 im_lookup,
1587 im_min,
1588 im_max,
1589 }
1590 }
1591}
1592
1593impl IndexConverter for LookupIndexConverter {
1594 fn tof_to_mz(&self, _frame_id: u32, tof_values: &Vec<u32>) -> Vec<f64> {
1595 let mut mz_values: Vec<f64> = Vec::new();
1596 mz_values.resize(tof_values.len(), 0.0);
1597
1598 for (i, &val) in tof_values.iter().enumerate() {
1599 mz_values[i] = (self.tof_intercept + self.tof_slope * val as f64).powi(2);
1600 }
1601
1602 mz_values
1603 }
1604
1605 fn mz_to_tof(&self, _frame_id: u32, mz_values: &Vec<f64>) -> Vec<u32> {
1606 let mut tof_values: Vec<u32> = Vec::new();
1607 tof_values.resize(mz_values.len(), 0);
1608
1609 for (i, &val) in mz_values.iter().enumerate() {
1610 tof_values[i] = ((val.sqrt() - self.tof_intercept) / self.tof_slope) as u32;
1611 }
1612
1613 tof_values
1614 }
1615
1616 fn scan_to_inverse_mobility(&self, _frame_id: u32, scan_values: &Vec<u32>) -> Vec<f64> {
1617 scan_values
1619 .iter()
1620 .map(|&s| {
1621 self.im_lookup
1622 .get(s as usize)
1623 .copied()
1624 .unwrap_or(f64::NAN)
1625 })
1626 .collect()
1627 }
1628
1629 fn inverse_mobility_to_scan(
1630 &self,
1631 _frame_id: u32,
1632 inverse_mobility_values: &Vec<f64>,
1633 ) -> Vec<u32> {
1634 inverse_mobility_values
1637 .iter()
1638 .map(|&im| {
1639 if im.is_nan() || self.im_lookup.is_empty() {
1640 return 0;
1641 }
1642
1643 let mut best_scan = 0usize;
1646 let mut best_diff = f64::INFINITY;
1647
1648 for (scan, &lookup_im) in self.im_lookup.iter().enumerate() {
1649 let diff = (lookup_im - im).abs();
1650 if diff < best_diff {
1651 best_diff = diff;
1652 best_scan = scan;
1653 }
1654 }
1655
1656 best_scan as u32
1657 })
1658 .collect()
1659 }
1660}