use crate::scan_event::ScanEvent;
use crate::scan_index::ScanIndexEntry;
use crate::types::{Activation, Analyzer, MsPower, ScanType};
pub fn activation_str(analyzer: Option<Analyzer>, act: Activation) -> &'static str {
match act {
Activation::CID => match analyzer {
Some(Analyzer::FTMS) => "hcd",
_ => "cid",
},
other => other.as_str(),
}
}
pub fn build_filter(
event: &ScanEvent,
index_entry: &ScanIndexEntry,
precursor_mz: Option<f64>,
activation_energy: Option<f64>,
supplemental_energy: Option<f64>,
) -> String {
let mut out = String::with_capacity(96);
let p = &event.preamble;
let analyzer = p.analyzer();
if let Some(a) = analyzer {
out.push_str(a.as_str());
} else {
out.push_str("MS");
}
out.push(' ');
if let Some(pol) = p.polarity() {
out.push(pol.symbol());
} else {
out.push('+');
}
out.push(' ');
if let Some(m) = p.scan_mode() {
out.push(m.symbol());
out.push(' ');
}
if let Some(ion) = p.ionization() {
out.push_str(ion.as_str());
out.push(' ');
}
if p.is_dependent() {
out.push_str("d ");
}
match p.scan_type() {
Some(ScanType::Full) => out.push_str("Full"),
Some(ScanType::Zoom) => out.push('Z'),
Some(ScanType::Sim) => out.push_str("SIM"),
Some(ScanType::Srm) => out.push_str("SRM"),
Some(ScanType::Crm) => out.push_str("CRM"),
Some(ScanType::Q1) => out.push_str("Q1MS"),
Some(ScanType::Q3) => out.push_str("Q3MS"),
_ => out.push_str("Full"),
}
out.push(' ');
let n = match p.ms_power() {
Some(MsPower::Ms1) | Some(MsPower::Undefined) | None => 1,
Some(MsPower::Ms2) => 2,
Some(MsPower::Ms3) => 3,
Some(MsPower::Ms4) => 4,
Some(MsPower::Ms5) => 5,
Some(MsPower::Ms6) => 6,
Some(MsPower::Ms7) => 7,
Some(MsPower::Ms8) => 8,
};
if n == 1 {
out.push_str("ms");
} else {
out.push_str(&format!("ms{n}"));
}
if n >= 2 {
let act = p.activation();
let reactions = &event.reactions;
let n_valid_precursors = reactions.iter().filter(|r| r.precursor_mz > 0.0).count();
let is_tribrid_ethcd = n == 2
&& reactions.len() >= 2
&& n_valid_precursors == 1
&& matches!(analyzer, Some(Analyzer::FTMS))
&& matches!(act, Some(Activation::CID) | Some(Activation::HCD));
if is_tribrid_ethcd {
if let Some(rx) = reactions.iter().find(|r| r.precursor_mz > 0.0) {
out.push(' ');
out.push_str(&format!("{:.4}", rx.precursor_mz));
out.push('@');
out.push_str("etd");
out.push('@');
out.push_str("hcd");
if let Some(e) = activation_energy {
out.push_str(&format!("{e:.2}"));
}
}
} else if !reactions.is_empty() {
let last = reactions.len() - 1;
for (i, rx) in reactions.iter().enumerate() {
let mz = rx.precursor_mz;
if mz <= 0.0 {
continue;
}
out.push(' ');
out.push_str(&format!("{mz:.4}"));
if let Some(a) = act {
out.push('@');
let is_last = i == last;
let is_ethcd = a == Activation::EThcD;
if is_last && is_ethcd {
out.push_str("etd");
if let Some(e) = activation_energy {
out.push_str(&format!("{e:.2}"));
}
out.push('@');
out.push_str("hcd");
if let Some(se) = supplemental_energy {
out.push_str(&format!("{se:.2}"));
}
} else {
let astr = activation_str(analyzer, a);
out.push_str(astr);
let energy = if is_last {
activation_energy.or({
if rx.energy > 0.0 {
Some(rx.energy)
} else {
None
}
})
} else {
if rx.energy > 0.0 {
Some(rx.energy)
} else {
activation_energy
}
};
if let Some(e) = energy {
out.push_str(&format!("{e:.2}"));
}
}
}
}
} else if let Some(mz) = precursor_mz {
out.push(' ');
out.push_str(&format!("{mz:.4}"));
if let Some(a) = act {
out.push('@');
if a == Activation::EThcD {
out.push_str("etd");
if let Some(e) = activation_energy {
out.push_str(&format!("{e:.2}"));
}
out.push('@');
out.push_str("hcd");
if let Some(se) = supplemental_energy {
out.push_str(&format!("{se:.2}"));
}
} else {
out.push_str(activation_str(analyzer, a));
if let Some(e) = activation_energy {
out.push_str(&format!("{e:.2}"));
}
}
}
}
}
if index_entry.low_mz.is_finite()
&& index_entry.high_mz.is_finite()
&& index_entry.low_mz > 0.0
&& index_entry.high_mz > index_entry.low_mz
{
out.push(' ');
out.push_str(&format!(
"[{:.4}-{:.4}]",
index_entry.low_mz, index_entry.high_mz
));
} else if let Some(fc) = event.fraction_collectors.first() {
if fc.low_mz > 0.0 && fc.high_mz > fc.low_mz {
out.push(' ');
out.push_str(&format!("[{:.4}-{:.4}]", fc.low_mz, fc.high_mz));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scan_event::{FractionCollector, Reaction, ScanEvent, ScanEventPreamble};
use crate::scan_index::ScanIndexEntry;
fn make_preamble(
polarity: u8,
scan_mode: u8,
ms_power: u8,
scan_type: u8,
) -> ScanEventPreamble {
let mut bytes = vec![0u8; 136];
bytes[4] = polarity;
bytes[5] = scan_mode;
bytes[6] = ms_power;
bytes[7] = scan_type;
bytes[11] = 5; bytes[40] = 4; ScanEventPreamble { bytes }
}
fn make_index(low: f64, high: f64) -> ScanIndexEntry {
ScanIndexEntry {
index: 0,
scan_event: 0,
scan_segment: 0,
data_size: 0,
start_time: 0.0,
total_current: 0.0,
base_intensity: 0.0,
base_mz: 0.0,
low_mz: low,
high_mz: high,
offset: 0,
}
}
#[test]
fn ms1_full_profile() {
let ev = ScanEvent {
preamble: make_preamble(1, 1, 1, 0),
reactions: vec![],
fraction_collectors: vec![FractionCollector {
low_mz: 350.0,
high_mz: 1500.0,
}],
coefficients: vec![],
};
let idx = make_index(350.0, 1500.0);
let s = build_filter(&ev, &idx, None, None, None);
assert_eq!(s, "FTMS + p NSI Full ms [350.0000-1500.0000]");
}
#[test]
fn ms2_centroid_hcd_scan_params() {
let mut pre = make_preamble(1, 0, 2, 0);
pre.bytes[10] = 1; pre.bytes[24] = 1; let ev = ScanEvent {
preamble: pre,
reactions: vec![],
fraction_collectors: vec![FractionCollector {
low_mz: 150.0,
high_mz: 2000.0,
}],
coefficients: vec![],
};
let idx = make_index(150.0, 2000.0);
let s = build_filter(&ev, &idx, Some(645.8311), Some(28.0), None);
assert_eq!(
s,
"FTMS + c NSI d Full ms2 645.8311@hcd28.00 [150.0000-2000.0000]"
);
}
#[test]
fn ms2_reactions_chain() {
let mut pre = make_preamble(1, 0, 2, 0);
pre.bytes[10] = 1; pre.bytes[40] = 0; pre.bytes[24] = 4; let rxn = Reaction {
precursor_mz: 440.254,
unknown_double: 1.0,
energy: 35.0,
unknown_long1: 0,
unknown_long2: 0,
};
let ev = ScanEvent {
preamble: pre,
reactions: vec![rxn],
fraction_collectors: vec![FractionCollector {
low_mz: 116.0,
high_mz: 892.0,
}],
coefficients: vec![],
};
let idx = make_index(116.0, 892.0);
let s = build_filter(&ev, &idx, Some(440.254), Some(35.0), None);
assert_eq!(
s,
"ITMS + c NSI d Full ms2 440.2540@cid35.00 [116.0000-892.0000]"
);
}
#[test]
fn ms3_chain() {
let mut pre = make_preamble(1, 0, 3, 0);
pre.bytes[10] = 1; pre.bytes[40] = 0; pre.bytes[24] = 4; let ev = ScanEvent {
preamble: pre,
reactions: vec![
Reaction {
precursor_mz: 810.50,
unknown_double: 1.0,
energy: 35.0,
unknown_long1: 0,
unknown_long2: 0,
},
Reaction {
precursor_mz: 265.27,
unknown_double: 1.0,
energy: 35.0,
unknown_long1: 0,
unknown_long2: 0,
},
],
fraction_collectors: vec![FractionCollector {
low_mz: 100.0,
high_mz: 1000.0,
}],
coefficients: vec![],
};
let idx = make_index(100.0, 1000.0);
let s = build_filter(&ev, &idx, Some(265.27), Some(35.0), None);
assert_eq!(
s,
"ITMS + c NSI d Full ms3 810.5000@cid35.00 265.2700@cid35.00 [100.0000-1000.0000]"
);
}
#[test]
fn ethcd_filter() {
let mut pre = make_preamble(1, 0, 2, 0);
pre.bytes[10] = 1; pre.bytes[40] = 4; pre.bytes[24] = 12; let ev = ScanEvent {
preamble: pre,
reactions: vec![],
fraction_collectors: vec![FractionCollector {
low_mz: 150.0,
high_mz: 2000.0,
}],
coefficients: vec![],
};
let idx = make_index(150.0, 2000.0);
let s = build_filter(&ev, &idx, Some(649.12), Some(35.0), Some(28.0));
assert_eq!(
s,
"FTMS + c NSI d Full ms2 649.1200@etd35.00@hcd28.00 [150.0000-2000.0000]"
);
}
}