Skip to main content

timsrust_tdf/
metadata.rs

1use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc};
2
3use timsrust_core::{AcquisitionType, FrameIndex, Im, MSLevel, Mz, Rt};
4
5use crate::{Frame2RtConverter, TdfFrameReader};
6
7use super::{
8    TDFPathLike,
9    file_readers::sql_reader::{
10        ReadableSqlHashMap, SqlReader, SqlReaderError, metadata::SqlMetadata,
11    },
12};
13
14/// Metadata from a single run.
15#[derive(Clone, Debug)]
16pub struct Metadata {
17    rt_converter: Arc<Frame2RtConverter>,
18    compression_type: u8,
19    acquisition_type: AcquisitionType,
20    lower_rt: Rt,
21    upper_rt: Rt,
22    lower_im: Im,
23    upper_im: Im,
24    lower_mz: Mz,
25    upper_mz: Mz,
26    path: String,
27    max_peaks_per_scan: usize,
28}
29
30const OTOF_CONTROL: &str = "Bruker otofControl";
31
32impl Metadata {
33    pub fn new(path: impl TDFPathLike) -> Result<Self, MetadataReaderError> {
34        let tdf_sql_reader = SqlReader::open(&path)?;
35        let sql_metadata: HashMap<String, String> =
36            SqlMetadata::from_sql_reader(&tdf_sql_reader)?;
37        let compression_type =
38            parse_value(&sql_metadata, "TimsCompressionType")?;
39        let max_peaks_per_scan =
40            parse_value(&sql_metadata, "MaxNumPeaksPerScan")?;
41        let frame_reader = TdfFrameReader::without_metadata(
42            path.as_ref(),
43            compression_type,
44            max_peaks_per_scan,
45        )
46        .unwrap();
47        let (mz_min, mz_max) = get_mz_bounds(&sql_metadata)?;
48        let (im_min, im_max) = get_im_bounds(&sql_metadata)?;
49        // let rt_values: Vec<f64> = tdf_sql_reader
50        //     .sql_file()
51        //     .read_column_from_table("Time", "Frames")?;
52        let rt_values = frame_reader
53            .iter_indices()
54            .map(|index| {
55                let frame =
56                    frame_reader.get_partial_frame_without_ions(index).unwrap();
57                (
58                    FrameIndex::try_from(frame.info().index() as u32)
59                        .expect("FrameIndex conversion out of bounds"),
60                    Rt::from(frame.info().rt_in_seconds()),
61                )
62            })
63            .collect::<HashMap<FrameIndex, Rt>>();
64        let rt_min = rt_values
65            .values()
66            .cloned()
67            .min_by(|a, b| a.partial_cmp(b).unwrap())
68            .unwrap();
69        let rt_max = rt_values
70            .values()
71            .cloned()
72            .max_by(|a, b| a.partial_cmp(b).unwrap())
73            .unwrap();
74        let mut acquisition_type = AcquisitionType::Unknown;
75        for index in frame_reader.iter_indices() {
76            let frame =
77                frame_reader.get_partial_frame_without_ions(index).unwrap();
78            let frame_info = frame.info();
79            if frame_info.ms_level() != MSLevel::MS1 {
80                acquisition_type = frame_info.acquisition_type();
81                break;
82            }
83        }
84        let metadata = Metadata {
85            rt_converter: Arc::new(Frame2RtConverter::from_values(rt_values)),
86            lower_rt: rt_min,
87            upper_rt: rt_max,
88            lower_im: im_min.into(),
89            upper_im: im_max.into(),
90            lower_mz: mz_min.into(),
91            upper_mz: mz_max.into(),
92            compression_type,
93            path: path.as_ref().to_string(),
94            max_peaks_per_scan,
95            acquisition_type,
96        };
97        Ok(metadata)
98    }
99
100    pub fn rt_converter(&self) -> &Arc<Frame2RtConverter> {
101        &self.rt_converter
102    }
103
104    pub fn max_peaks_per_scan(&self) -> usize {
105        self.max_peaks_per_scan
106    }
107
108    pub fn compression_type(&self) -> u8 {
109        self.compression_type
110    }
111
112    pub fn acquisition_type(&self) -> AcquisitionType {
113        self.acquisition_type
114    }
115
116    pub fn lower_rt(&self) -> Rt {
117        self.lower_rt
118    }
119
120    pub fn upper_rt(&self) -> Rt {
121        self.upper_rt
122    }
123
124    pub fn lower_im(&self) -> Im {
125        self.lower_im
126    }
127
128    pub fn upper_im(&self) -> Im {
129        self.upper_im
130    }
131
132    pub fn lower_mz(&self) -> Mz {
133        self.lower_mz
134    }
135
136    pub fn upper_mz(&self) -> Mz {
137        self.upper_mz
138    }
139
140    pub fn path(&self) -> &str {
141        &self.path
142    }
143}
144
145fn get_mz_bounds(
146    sql_metadata: &HashMap<String, String>,
147) -> Result<(f64, f64), MetadataReaderError> {
148    let software = sql_metadata.get("AcquisitionSoftware").ok_or(
149        MetadataReaderError::KeyNotFound("AcquisitionSoftware".to_string()),
150    )?;
151    let mut mz_min: f64 = parse_value(sql_metadata, "MzAcqRangeLower")?;
152    let mut mz_max: f64 = parse_value(sql_metadata, "MzAcqRangeUpper")?;
153    if software == OTOF_CONTROL {
154        mz_min -= 5.0;
155        mz_max += 5.0;
156    }
157    Ok((mz_min, mz_max))
158}
159
160fn get_im_bounds(
161    sql_metadata: &HashMap<String, String>,
162) -> Result<(f64, f64), MetadataReaderError> {
163    let im_min: f64 = parse_value(sql_metadata, "OneOverK0AcqRangeLower")?;
164    let im_max: f64 = parse_value(sql_metadata, "OneOverK0AcqRangeUpper")?;
165    Ok((im_min, im_max))
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
180#[allow(private_interfaces)]
181#[derive(Debug, thiserror::Error)]
182pub enum MetadataReaderError {
183    #[error("{0}")]
184    SqlReaderError(#[from] SqlReaderError),
185    #[error("Key not found: {0}")]
186    KeyNotFound(String),
187    #[error("Key not parsable: {0}")]
188    ParseError(String),
189}