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#[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}