Skip to main content

timsrust_tdf/
calibration.rs

1use std::{collections::HashMap, str::FromStr};
2
3use timsrust_core::{Converter, FrameIndex, Im, Mz, Rt, ScanIndex, TofIndex};
4
5use crate::{MetadataReaderError, TdfFrameReader};
6
7#[derive(Clone, Debug, PartialEq)]
8pub struct UncalibratedTof2MzConverter {
9    tof_intercept: f64,
10    tof_slope: f64,
11}
12
13impl UncalibratedTof2MzConverter {
14    fn from_boundaries(mz_min: f64, mz_max: f64, tof_max_index: u32) -> Self {
15        let tof_intercept: f64 = mz_min.sqrt();
16        let tof_slope: f64 =
17            (mz_max.sqrt() - tof_intercept) / tof_max_index as f64;
18        Self {
19            tof_intercept,
20            tof_slope,
21        }
22    }
23
24    fn new(path: &str) -> Self {
25        use crate::file_readers::sql_reader::{
26            ReadableSqlHashMap, SqlReader, metadata::SqlMetadata,
27        };
28
29        let tdf_sql_reader = SqlReader::open(path).unwrap();
30        let sql_metadata: HashMap<String, String> =
31            SqlMetadata::from_sql_reader(&tdf_sql_reader).unwrap();
32        let (mz_min, mz_max) = get_mz_bounds(&sql_metadata).unwrap();
33        let tof_max_index: u32 =
34            parse_value(&sql_metadata, "DigitizerNumSamples").unwrap();
35        UncalibratedTof2MzConverter::from_boundaries(
36            mz_min,
37            mz_max,
38            tof_max_index,
39        )
40    }
41}
42
43impl Converter<TofIndex, Mz> for UncalibratedTof2MzConverter {
44    fn convert(&self, value: TofIndex) -> Mz {
45        let value = u32::from(value) as f64;
46        let mz = self.tof_intercept + self.tof_slope * value;
47        let result = mz * mz;
48        Mz::from(result)
49    }
50}
51
52impl Converter<Mz, TofIndex> for UncalibratedTof2MzConverter {
53    fn convert(&self, value: Mz) -> TofIndex {
54        let value = f64::from(value);
55        let result = (value.sqrt() - self.tof_intercept) / self.tof_slope;
56        TofIndex::try_from(result as u32)
57            .expect("TofIndex conversion out of bounds")
58    }
59}
60
61#[derive(Clone, Debug)]
62pub enum Tof2MzConverter {
63    Uncalibrated(UncalibratedTof2MzConverter),
64}
65
66const OTOF_CONTROL: &str = "Bruker otofControl";
67
68fn get_mz_bounds(
69    sql_metadata: &HashMap<String, String>,
70) -> Result<(f64, f64), MetadataReaderError> {
71    let software = sql_metadata.get("AcquisitionSoftware").ok_or(
72        MetadataReaderError::KeyNotFound("AcquisitionSoftware".to_string()),
73    )?;
74    let mut mz_min: f64 = parse_value(sql_metadata, "MzAcqRangeLower")?;
75    let mut mz_max: f64 = parse_value(sql_metadata, "MzAcqRangeUpper")?;
76    if software == OTOF_CONTROL {
77        mz_min -= 5.0;
78        mz_max += 5.0;
79    }
80    Ok((mz_min, mz_max))
81}
82
83impl Tof2MzConverter {
84    pub fn new(path: &str) -> Self {
85        Self::Uncalibrated(UncalibratedTof2MzConverter::new(path))
86    }
87}
88
89impl Converter<TofIndex, Mz> for Tof2MzConverter {
90    fn convert(&self, value: TofIndex) -> Mz {
91        match self {
92            Tof2MzConverter::Uncalibrated(converter) => {
93                converter.convert(value)
94            },
95        }
96    }
97}
98
99impl Converter<Mz, TofIndex> for Tof2MzConverter {
100    fn convert(&self, value: Mz) -> TofIndex {
101        match self {
102            Tof2MzConverter::Uncalibrated(converter) => {
103                converter.convert(value)
104            },
105        }
106    }
107}
108
109#[derive(Clone, Debug, PartialEq, Default)]
110pub struct UncalibratedScan2ImConverter {
111    scan_intercept: f64,
112    scan_slope: f64,
113}
114
115impl UncalibratedScan2ImConverter {
116    fn from_boundaries(im_min: f64, im_max: f64, scan_max_index: u32) -> Self {
117        let scan_intercept: f64 = im_max.sqrt();
118        let scan_slope: f64 =
119            (im_min.sqrt() - scan_intercept) / scan_max_index as f64;
120        Self {
121            scan_intercept,
122            scan_slope,
123        }
124    }
125
126    fn new(path: &str) -> Self {
127        use crate::file_readers::sql_reader::{
128            ReadableSqlHashMap, ReadableSqlTable, SqlReader, frames::SqlFrame,
129            metadata::SqlMetadata,
130        };
131        let tdf_sql_reader = SqlReader::open(path).unwrap();
132        let sql_metadata: HashMap<String, String> =
133            SqlMetadata::from_sql_reader(&tdf_sql_reader).unwrap();
134        let sql_frames = SqlFrame::from_sql_reader(&tdf_sql_reader).unwrap();
135        let scan_max_index = sql_frames
136            .iter()
137            .map(|f| f.scan_count as u32)
138            .max()
139            .expect("SqlReader cannot return empty vecs, so there is always a max scan index");
140        let (im_min, im_max) = get_im_bounds(&sql_metadata).unwrap();
141        Self::from_boundaries(im_min, im_max, scan_max_index)
142    }
143}
144
145impl Converter<ScanIndex, Im> for UncalibratedScan2ImConverter {
146    fn convert(&self, value: ScanIndex) -> Im {
147        let value = f64::from(value);
148        let im = self.scan_intercept + self.scan_slope * value;
149        let result = im * im;
150        Im::from(result)
151    }
152}
153
154impl Converter<Im, ScanIndex> for UncalibratedScan2ImConverter {
155    fn convert(&self, value: Im) -> ScanIndex {
156        let value = f64::from(value);
157        let result = (value.sqrt() - self.scan_intercept) / self.scan_slope;
158        ScanIndex::try_from(result as u32)
159            .expect("ScanIndex conversion out of bounds")
160    }
161}
162
163#[derive(Clone, Debug)]
164pub enum Scan2ImConverter {
165    Uncalibrated(UncalibratedScan2ImConverter),
166}
167
168fn parse_value<T: FromStr>(
169    hash_map: &HashMap<String, String>,
170    key: &str,
171) -> Result<T, MetadataReaderError> {
172    let value: T = hash_map
173        .get(key)
174        .ok_or(MetadataReaderError::KeyNotFound(key.to_string()))?
175        .parse()
176        .map_err(|_| MetadataReaderError::ParseError(key.to_string()))?;
177    Ok(value)
178}
179
180fn get_im_bounds(
181    sql_metadata: &HashMap<String, String>,
182) -> Result<(f64, f64), MetadataReaderError> {
183    let im_min: f64 = parse_value(sql_metadata, "OneOverK0AcqRangeLower")?;
184    let im_max: f64 = parse_value(sql_metadata, "OneOverK0AcqRangeUpper")?;
185    Ok((im_min, im_max))
186}
187
188impl Scan2ImConverter {
189    pub fn new(path: &str) -> Self {
190        Self::Uncalibrated(UncalibratedScan2ImConverter::new(path))
191    }
192}
193
194impl Converter<ScanIndex, Im> for Scan2ImConverter {
195    fn convert(&self, value: ScanIndex) -> Im {
196        match self {
197            Scan2ImConverter::Uncalibrated(converter) => {
198                converter.convert(value)
199            },
200        }
201    }
202}
203
204impl Converter<Im, ScanIndex> for Scan2ImConverter {
205    fn convert(&self, value: Im) -> ScanIndex {
206        match self {
207            Scan2ImConverter::Uncalibrated(converter) => {
208                converter.convert(value)
209            },
210        }
211    }
212}
213
214/// A converter from Frame -> retention time.
215#[derive(Debug, Default, Clone, PartialEq)]
216pub struct Frame2RtConverter {
217    forward: HashMap<FrameIndex, Rt>,
218    reverse: HashMap<Rt, FrameIndex>,
219}
220
221impl Frame2RtConverter {
222    pub fn new(path: &str) -> Self {
223        let frame_reader = TdfFrameReader::new(path).unwrap();
224        let rt_values = frame_reader
225            .iter_indices()
226            .map(|index| {
227                let frame =
228                    frame_reader.get_partial_frame_without_ions(index).unwrap();
229                (
230                    FrameIndex::try_from(frame.info().index() as u32)
231                        .expect("FrameIndex conversion out of bounds"),
232                    Rt::from(frame.info().rt_in_seconds()),
233                )
234            })
235            .collect::<HashMap<FrameIndex, Rt>>();
236        Self::from_values(rt_values)
237    }
238
239    pub fn from_values(forward: HashMap<FrameIndex, Rt>) -> Self {
240        let reverse = forward.iter().map(|(k, v)| (*v, *k)).collect();
241        Self { forward, reverse }
242    }
243}
244
245impl Converter<FrameIndex, Rt> for Frame2RtConverter {
246    fn convert(&self, value: FrameIndex) -> Rt {
247        *self
248            .forward
249            .get(&value)
250            .expect("FrameIndex not found in converter")
251    }
252}
253
254impl Converter<Rt, FrameIndex> for Frame2RtConverter {
255    fn convert(&self, value: Rt) -> FrameIndex {
256        *self.reverse.get(&value).expect("Rt not found in converter")
257    }
258}