use crate::{Error, Result, SqlValue, field, invalid};
#[derive(Clone, Debug, PartialEq)]
pub struct FrameInfo {
pub id: u64,
pub retention_time_seconds: f64,
pub polarity: Option<String>,
pub scan_mode: Option<u32>,
pub msms_type: u32,
pub binary_offset: u64,
pub max_intensity: Option<u64>,
pub summed_intensities: Option<u64>,
pub scan_count: usize,
pub peak_count: usize,
pub mz_calibration_id: Option<u64>,
pub t1: Option<f64>,
pub t2: Option<f64>,
pub mobility_calibration_id: Option<u64>,
pub property_group: Option<u64>,
pub accumulation_time_ms: Option<f64>,
pub ramp_time_ms: Option<f64>,
}
impl FrameInfo {
pub fn ms_level(&self) -> Option<u8> {
match self.msms_type {
0 => Some(1),
8 | 9 => Some(2),
_ => None,
}
}
pub fn intensity_correction_factor(&self) -> Option<f64> {
self.accumulation_time_ms
.filter(|t| t.is_finite() && *t > 0.0)
.map(|t| 1000.0 / t)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AcquisitionType {
DdaPasef,
DiaPasef,
Unknown,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PasefFrameMsMsInfo {
pub frame: u64,
pub scan_num_begin: u32,
pub scan_num_end: u32,
pub isolation_mz: f64,
pub isolation_width: f64,
pub collision_energy: f64,
pub precursor: Option<u64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Precursor {
pub id: u64,
pub largest_peak_mz: f64,
pub average_mz: f64,
pub monoisotopic_mz: Option<f64>,
pub charge: Option<i64>,
pub scan_number: Option<f64>,
pub intensity: f64,
pub parent: Option<u64>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiaFrameMsMsInfo {
pub frame: u64,
pub window_group: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DiaFrameMsMsWindow {
pub window_group: u32,
pub scan_num_begin: u32,
pub scan_num_end: u32,
pub isolation_mz: f64,
pub isolation_width: f64,
pub collision_energy: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiaFrameMsMsWindowGroup {
pub id: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TsfFrameInfo {
pub id: u64,
pub retention_time_seconds: f64,
pub polarity: Option<String>,
pub scan_mode: Option<u32>,
pub msms_type: u32,
pub binary_offset: Option<u64>,
pub max_intensity: Option<f64>,
pub summed_intensities: Option<f64>,
pub peak_count: Option<usize>,
pub mz_calibration_id: Option<u64>,
pub t1: Option<f64>,
pub t2: Option<f64>,
pub property_group: Option<u64>,
}
impl TsfFrameInfo {
pub fn ms_level(&self) -> u8 {
if self.msms_type == 0 { 1 } else { 2 }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TsfFrameMsMsInfo {
pub frame: u64,
pub parent: Option<u64>,
pub trigger_mass: f64,
pub isolation_width: f64,
pub precursor_charge: Option<i64>,
pub collision_energy: Option<f64>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LineSpectrum {
pub frame_id: u64,
pub indices: Vec<f64>,
pub intensities: Vec<f32>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TimsCalibration {
pub id: u64,
pub model_type: i64,
pub coefficients: [f64; 10],
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GlobalMetadata {
entries: Vec<(String, SqlValue)>,
}
impl GlobalMetadata {
pub fn new(entries: Vec<(String, SqlValue)>) -> Result<Self> {
for (i, (key, _)) in entries.iter().enumerate() {
if entries[..i].iter().any(|(k, _)| k == key) {
return Err(field(
invalid(format!("duplicate key {key}")),
"GlobalMetadata",
"Key",
));
}
}
Ok(Self { entries })
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &SqlValue)> {
self.entries.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn get(&self, key: &str) -> Option<&SqlValue> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn text(&self, key: &str) -> Option<&str> {
self.get(key).and_then(SqlValue::as_str)
}
pub fn number(&self, key: &str) -> Result<f64> {
let value = match self.get(key) {
Some(SqlValue::Text(text)) => text.trim().parse::<f64>().ok(),
Some(value) => value.as_f64(),
None => return Err(missing(key)),
};
value.filter(|v| v.is_finite()).ok_or_else(|| {
field(
invalid("value is not a finite number"),
"GlobalMetadata",
key,
)
})
}
pub fn integer(&self, key: &str) -> Result<u64> {
let value = match self.get(key) {
Some(SqlValue::Text(text)) => text
.trim()
.parse::<u64>()
.map_err(|source| field(Error::ParseInteger(source), "GlobalMetadata", key))?,
Some(SqlValue::Integer(n)) => u64::try_from(*n)
.map_err(|_| field(invalid("negative integer"), "GlobalMetadata", key))?,
Some(_) => {
return Err(field(
invalid("value is not an integer"),
"GlobalMetadata",
key,
));
}
None => return Err(missing(key)),
};
Ok(value)
}
pub fn acquisition_software(&self) -> Option<&str> {
self.text("AcquisitionSoftware")
}
pub fn compression_type(&self) -> Result<u32> {
let value = self.integer("TimsCompressionType")?;
u32::try_from(value).map_err(|_| {
field(
invalid("out of range"),
"GlobalMetadata",
"TimsCompressionType",
)
})
}
pub fn mz_acq_range(&self) -> Result<(f64, f64)> {
Ok((
self.number("MzAcqRangeLower")?,
self.number("MzAcqRangeUpper")?,
))
}
pub fn one_over_k0_acq_range(&self) -> Result<(f64, f64)> {
Ok((
self.number("OneOverK0AcqRangeLower")?,
self.number("OneOverK0AcqRangeUpper")?,
))
}
pub fn digitizer_num_samples(&self) -> Result<u32> {
let value = self.integer("DigitizerNumSamples")?;
u32::try_from(value).map_err(|_| {
field(
invalid("out of range"),
"GlobalMetadata",
"DigitizerNumSamples",
)
})
}
}
fn missing(key: &str) -> Error {
field(invalid(format!("missing key {key}")), "GlobalMetadata", key)
}