diff --git a/Cargo.lock b/Cargo.lock index e2da423..6c93f1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.17" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93fe60e2fc87b6ba2c117f67ae14f66e3fc7d6a1e612a25adb238cc980eadb3" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "jobserver", "libc", @@ -1421,9 +1421,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" @@ -2638,11 +2638,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Justfile b/Justfile index 49d1786..a2f1713 100644 --- a/Justfile +++ b/Justfile @@ -22,8 +22,10 @@ update-cv: --location \ https://github.com/HUPO-PSI/psi-ms-CV/releases/latest/download/psi-ms.obo | gzip -c > cv/psi-ms.obo.gz + gzip -d -c cv/psi-ms.obo.gz | head -n 5 + update-cv-terms: - cog -c -r -U src/meta/software.rs src/meta/instrument.rs src/meta/file_description.rs src/io/mzml/writer.rs + cog -c -r -U src/meta/software.rs src/meta/instrument.rs src/meta/file_description.rs src/io/mzml/writer.rs src/meta/activation.rs changelog version: #!/usr/bin/env python diff --git a/cv/extract_activation.py b/cv/extract_activation.py new file mode 100644 index 0000000..afe37ae --- /dev/null +++ b/cv/extract_activation.py @@ -0,0 +1,116 @@ +import gzip +import json +import io +import itertools +import re + +from typing import Tuple, Dict, Set, List + +import fastobo +from fastobo.term import ( + TermFrame, + IsAClause, + NameClause, + DefClause, +) + +from fastobo.doc import OboDoc + +from fastobo.id import PrefixedIdent + +ROOT_TERM = PrefixedIdent("MS", "1000044") + +segment_pattern = re.compile(r"(_[a-zA-Z])") + + +def collect_components( + cv: OboDoc, base_term: PrefixedIdent +) -> Tuple[Set[PrefixedIdent], Dict[PrefixedIdent, TermFrame]]: + term: TermFrame + id_to_clause = {} + component_ids = {base_term} + # Make multiple passes + for term in itertools.chain(cv, cv): + id_to_clause[term.id] = term + for clause in term: + if isinstance(clause, IsAClause): + if clause.term in component_ids: + component_ids.add(term.id) + return component_ids, id_to_clause + + +def format_name(match: re.Match) -> str: + return match.group(1)[-1].upper() + + +def find_name(term: TermFrame): + for clause in term: + if isinstance(clause, NameClause): + name = str(clause.name) + return name + else: + raise LookupError(f"Term name not found for {term.id!s}") + + +def make_entry_for(term: TermFrame): + name = None + parents = [] + descr = "" + for clause in term: + if isinstance(clause, NameClause): + name = str(clause.name) + if isinstance(clause, IsAClause): + parents.append(str(clause.term)) + if isinstance(clause, DefClause): + descr = re.sub( + r"(\[|\])", + lambda m: "\\\\" + m.group(1), + str(clause.definition).replace('"', "'"), + ) + + vname = name + if "-" in vname: + vname = vname.replace("-", "_") + if ":" in vname: + vname = vname.replace(":", "_") + if "/" in vname: + vname = vname.replace("/", "_") + if "+" in vname: + vname = vname.replace("+", "plus") + if "!" in vname: + vname = vname.replace("!", "_") + + vname = segment_pattern.sub(format_name, vname.replace(" ", "_")) + vname = vname[0].upper() + vname[1:] + + if vname[0].isdigit(): + vname = "_" + vname + + return f""" + #[term(cv=MS, accession={term.id.local}, name="{name}", flags={{{0}}}, parents={{{json.dumps(parents)}}})] + #[doc = "{name} - {descr}"] + {vname},""" + + +def generate_term_enum(terms: List[TermFrame], type_name: str): + buffer = io.StringIO() + buffer.write("pub enum $Term {".replace("$", type_name)) + for term in terms: + buffer.write(make_entry_for(term)) + buffer.write("\n}") + return buffer.getvalue() + + +def main(): + cv: OboDoc = fastobo.load(gzip.open("./cv/psi-ms.obo.gz")) + term_ids, id_to_clause = collect_components(cv, ROOT_TERM) + t = find_name(id_to_clause[ROOT_TERM]) + type_name = t.title().replace(" ", "") + + term_specs = list(map(id_to_clause.get, sorted(term_ids))) + text = generate_term_enum(term_specs, type_name) + print(text) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cv/extract_component.py b/cv/extract_component.py index 8d69ce5..7a83376 100644 --- a/cv/extract_component.py +++ b/cv/extract_component.py @@ -49,6 +49,7 @@ class ValueType(IntFlag): "ionization-type": 'IonizationType', "inlet-type": "InletType", "detector-type": "DetectorType", + "collision-energy": "CollisionEnergy" } @@ -57,12 +58,23 @@ class ValueType(IntFlag): "ionization-type": PrefixedIdent("MS", "1000008"), "inlet-type": PrefixedIdent("MS", "1000007"), "detector-type": PrefixedIdent("MS", "1000026"), + "collision-energy": PrefixedIdent("MS", "1000045"), } def make_parser(): parser = argparse.ArgumentParser() - parser.add_argument("component", choices=["mass-analyzer", "ionization-type", "inlet-type", "detector-type", "-"]) + parser.add_argument( + "component", + choices=[ + "mass-analyzer", + "ionization-type", + "inlet-type", + "detector-type", + "collision-energy", + "-", + ], + ) parser.add_argument("-c", "--curie") parser.add_argument("-t", "--type-name") return parser diff --git a/cv/extract_energy.py b/cv/extract_energy.py new file mode 100644 index 0000000..d90b923 --- /dev/null +++ b/cv/extract_energy.py @@ -0,0 +1,130 @@ +import gzip +import json +import io +import itertools +import re + +from typing import Tuple, Dict, Set, List + +import fastobo +from fastobo.term import ( + TermFrame, + IsAClause, + NameClause, + DefClause, +) + +from fastobo.doc import OboDoc + +from fastobo.id import PrefixedIdent + +ROOT_TERM = PrefixedIdent("MS", "1000045") +EXTRA_ROOTS = [ + PrefixedIdent("MS", "1000138"), + PrefixedIdent("MS", "1002680"), + PrefixedIdent("MS", "1003410") +] + +segment_pattern = re.compile(r"(_[a-zA-Z])") + + +def collect_components( + cv: OboDoc, base_term: PrefixedIdent +) -> Tuple[Set[PrefixedIdent], Dict[PrefixedIdent, TermFrame]]: + term: TermFrame + id_to_clause = {} + component_ids = {base_term} + # Make multiple passes + for term in itertools.chain(cv, cv): + id_to_clause[term.id] = term + for clause in term: + if isinstance(clause, IsAClause): + if clause.term in component_ids: + component_ids.add(term.id) + return component_ids, id_to_clause + + +def format_name(match: re.Match) -> str: + return match.group(1)[-1].upper() + + +def find_name(term: TermFrame): + for clause in term: + if isinstance(clause, NameClause): + name = str(clause.name) + return name + else: + raise LookupError(f"Term name not found for {term.id!s}") + + +def make_entry_for(term: TermFrame): + name = None + parents = [] + descr = "" + for clause in term: + if isinstance(clause, NameClause): + name = str(clause.name) + if isinstance(clause, IsAClause): + parents.append(str(clause.term)) + if isinstance(clause, DefClause): + descr = re.sub( + r"(\[|\])", + lambda m: "\\\\" + m.group(1), + str(clause.definition).replace('"', "'"), + ) + + vname = name + if "-" in vname: + vname = vname.replace("-", "_") + if ":" in vname: + vname = vname.replace(":", "_") + if "/" in vname: + vname = vname.replace("/", "_") + if "+" in vname: + vname = vname.replace("+", "plus") + if "!" in vname: + vname = vname.replace("!", "_") + + vname = segment_pattern.sub(format_name, vname.replace(" ", "_")) + vname = vname[0].upper() + vname[1:] + + if vname[0].isdigit(): + vname = "_" + vname + + return f""" + #[term(cv=MS, accession={term.id.local}, name="{name}", flags={{{0}}}, parents={{{json.dumps(parents)}}})] + #[doc = "{name} - {descr}"] + {vname}(f32),""" + + +def generate_term_enum(terms: List[TermFrame], type_name: str): + buffer = io.StringIO() + buffer.write("pub enum $Term {".replace("$", type_name)) + for term in terms: + buffer.write(make_entry_for(term)) + buffer.write("\n}") + return buffer.getvalue() + + +def merge_term_sets(term_sets: List[Tuple[Set, Dict]]) -> Tuple[Set, Dict]: + base_term_ids, base_id_to_clause = map(lambda x: x.copy(), term_sets[0]) + for (term_ids, id_to_clause) in term_sets[1:]: + base_term_ids.update(term_ids) + base_id_to_clause.update(id_to_clause) + return (base_term_ids, base_id_to_clause) + + +def main(): + cv: OboDoc = fastobo.load(gzip.open("./cv/psi-ms.obo.gz")) + term_ids, id_to_clause = merge_term_sets([collect_components(cv, root) for root in [ROOT_TERM] + EXTRA_ROOTS]) + # t = find_name(id_to_clause[ROOT_TERM]) + # type_name = t.title().replace(" ", "") + type_name = "DissociationEnergy" + + term_specs = list(map(id_to_clause.get, sorted(term_ids))) + text = generate_term_enum(term_specs, type_name) + print(text) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cv/psi-ms.obo.gz b/cv/psi-ms.obo.gz index 7816a89..b66a7ee 100644 Binary files a/cv/psi-ms.obo.gz and b/cv/psi-ms.obo.gz differ diff --git a/src/io/mzml/reader.rs b/src/io/mzml/reader.rs index d89108f..76e8305 100644 --- a/src/io/mzml/reader.rs +++ b/src/io/mzml/reader.rs @@ -13,13 +13,14 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::Error as XMLError; use quick_xml::Reader; +use crate::meta::DissociationEnergyTerm; use crate::meta::Sample; use crate::prelude::*; use super::super::offset_index::OffsetIndex; use super::super::traits::{ - MZFileReader, RandomAccessSpectrumIterator, SpectrumSource, SeekRead, SpectrumAccessError, - ChromatogramSource, + ChromatogramSource, MZFileReader, RandomAccessSpectrumIterator, SeekRead, SpectrumAccessError, + SpectrumSource, }; use super::reading_shared::EntryType; @@ -134,12 +135,14 @@ pub trait SpectrumBuilding< self.current_array_mut().name = ArrayType::NonStandardDataArray { name: Box::new(param.value().to_string()), }; - }, + } 1000595 => { self.current_array_mut().name = ArrayType::TimeArray; let unit = param.unit(); match unit { - Unit::Minute | Unit::Second | Unit::Millisecond => self.current_array_mut().unit = unit, + Unit::Minute | Unit::Second | Unit::Millisecond => { + self.current_array_mut().unit = unit + } _ => { warn!("Invalid unit {} found for time array", unit) } @@ -396,7 +399,7 @@ impl< spectrum.arrays = Some(self.arrays); } - fn fill_spectrum + ParamValue>(&mut self, param: P) { + fn fill_spectrum + ParamValue>(&mut self, param: P) { match param.name() { "ms level" => { self.ms_level = param.to_i32().expect("Failed to parse ms level") as u8; @@ -487,6 +490,16 @@ impl< } } + fn warning_context(&self) -> String { + if self.is_spectrum_entry() { + format!("spectrum entry {} ({})", self.index, self.entry_id) + } else if self.is_chromatogram_entry() { + format!("chromatogram entry {} ({})", self.index, self.entry_id) + } else { + format!("unknown entry {} ({})", self.index, self.entry_id) + } + } + pub fn _reset(&mut self) { self.params.clear(); self.acquisition = Acquisition::default(); @@ -557,7 +570,8 @@ impl< b"ion injection time" => { event.injection_time = param .to_f64() - .expect("Expected floating point number for injection time") as f32; + .expect("Expected floating point number for injection time") + as f32; } _ => event.add_param(param), } @@ -578,7 +592,7 @@ impl< if Activation::is_param_activation(¶m) && self.precursor.activation.method().is_none() { - *self.precursor.activation.method_mut() = Some(param.into()); + self.precursor.activation.methods_mut().push(param.into()); } else { match param.name.as_ref() { "collision energy" | "activation energy" => { @@ -782,32 +796,34 @@ impl< self.acquisition.add_param(param.into()) } } - MzMLParserState::Scan => { - let event = self.acquisition.scans.last_mut().unwrap(); - match param.name.as_bytes() { - b"scan start time" => { - let value: f64 = param - .to_f64() - .expect("Expected floating point number for scan time"); - let value = match ¶m.unit { - Unit::Minute => value, - Unit::Second => value / 60.0, - Unit::Millisecond => value / 60000.0, - _ => { - warn!("Could not infer unit for {:?}", param); - value - } - }; - event.start_time = value; - } - b"ion injection time" => { - event.injection_time = param.to_f32().expect( - "Expected floating point number for injection time", - ); - } - _ => event.add_param(param.into()), + MzMLParserState::Scan => match param.name.as_bytes() { + b"scan start time" => { + let value: f64 = param + .to_f64() + .unwrap_or_else(|e| panic!("Expected floating point number for scan time: {e} for {}", self.warning_context())); + let value = match ¶m.unit { + Unit::Minute => value, + Unit::Second => value / 60.0, + Unit::Millisecond => value / 60000.0, + _ => { + warn!("Could not infer unit for {:?} for {}", param, self.warning_context()); + value + } + }; + self.acquisition.scans.last_mut().unwrap().start_time = value; } - } + b"ion injection time" => { + self.acquisition.scans.last_mut().unwrap().injection_time = param.to_f32().unwrap_or_else( + |e| panic!("Expected floating point number for injection time: {e} for {}", self.warning_context()) + ); + } + _ => self + .acquisition + .scans + .last_mut() + .unwrap() + .add_param(param.into()), + }, MzMLParserState::ScanWindowList => self .acquisition .scans @@ -824,18 +840,30 @@ impl< self.fill_selected_ion(param.into()); } MzMLParserState::Activation => { - if Activation::is_param_activation(¶m) - && self.precursor.activation.method().is_none() - { - *self.precursor.activation.method_mut() = Some(param.into()); + if Activation::is_param_activation(¶m) { + self.precursor.activation.methods_mut().push(param.into()); } else { - match param.name.as_ref() { - "collision energy" | "activation energy" => { - self.precursor.activation.energy = param - .to_f32() - .expect("Failed to parse collision energy"); + let dissociation_energy = param.curie().and_then(|c| { + DissociationEnergyTerm::from_curie(&c, param.value().to_f32().unwrap_or_else(|e| { + warn!("Failed to convert dissociation energy: {e} for {} for {}", param.name(), self.warning_context()); + 0.0 + })) + }); + match dissociation_energy { + Some(t) => { + if t.is_supplemental() { + self.precursor.activation.add_param(param.into()) + } else { + if self.precursor.activation.energy != 0.0 { + warn!( + "Multiple dissociation energies detected. Saw {t} after already setting dissociation energy for {}", + self.warning_context() + ); + } + self.precursor.activation.energy = t.energy(); + } } - &_ => { + None => { self.precursor.activation.add_param(param.into()); } } @@ -1513,11 +1541,11 @@ impl< } impl< - R: SeekRead, - C: CentroidPeakAdapting + BuildFromArrayMap, - D: DeconvolutedPeakAdapting + BuildFromArrayMap, - > ChromatogramSource for MzMLReaderType { - + R: SeekRead, + C: CentroidPeakAdapting + BuildFromArrayMap, + D: DeconvolutedPeakAdapting + BuildFromArrayMap, + > ChromatogramSource for MzMLReaderType +{ fn get_chromatogram_by_id(&mut self, id: &str) -> Option { self.get_chromatogram_by_id(id) } @@ -1724,7 +1752,7 @@ impl< if let Some(captures) = pattern.captures(&String::from_utf8_lossy(&buf)) { if let Some(hit) = captures.get(1) { self.handle.seek(SeekFrom::Start(current_position))?; - return Ok(Some(hit.as_str().to_string())) + return Ok(Some(hit.as_str().to_string())); } } diff --git a/src/io/mzml/writer.rs b/src/io/mzml/writer.rs index 9fc1173..75a9ac5 100644 --- a/src/io/mzml/writer.rs +++ b/src/io/mzml/writer.rs @@ -701,8 +701,8 @@ where buf = subprocess.check_output(['python', 'cv/extract_cv_metadata.py', 'data-version']).decode('utf8').strip() cog.outl(f'const PSIMS_VERSION: &\'static str = "{buf}";') ]]]*/ - const PSIMS_VERSION: &'static str = "4.1.162"; - //[[[end]]] (checksum: e0bccb0a2d32dac54c25684cd6c3b148) + const PSIMS_VERSION: &'static str = "4.1.174"; + //[[[end]]] (checksum: c20f130bf2cb029cf820fb56ecf3075c) const UNIT_VERSION: &'static str = "releases/2020-03-10"; pub const fn get_indent_size() -> u64 { diff --git a/src/io/thermo/reader.rs b/src/io/thermo/reader.rs index a24c4b6..6f340ba 100644 --- a/src/io/thermo/reader.rs +++ b/src/io/thermo/reader.rs @@ -6,12 +6,13 @@ use log::{debug, warn}; use crate::{ io::{traits::ChromatogramSource, utils::checksum_file, DetailLevel, OffsetIndex}, meta::{ - Component, ComponentType, DataProcessing, DetectorTypeTerm, FileDescription, InstrumentConfiguration, IonizationTypeTerm, MassAnalyzerTerm, MassSpectrometryRun, Sample, Software, SourceFile + Component, ComponentType, DataProcessing, DetectorTypeTerm, FileDescription, InstrumentConfiguration, IonizationTypeTerm, MassAnalyzerTerm, MassSpectrometryRun, Sample, Software, SourceFile, + DissociationMethodTerm, }, params::{ControlledVocabulary, Unit}, prelude::*, spectrum::{ - ActivationMethod, ArrayType, BinaryArrayMap, BinaryDataArrayType, CentroidPeakAdapting, Chromatogram, ChromatogramDescription, ChromatogramType, DataArray, DeconvolutedPeakAdapting, MultiLayerSpectrum, Precursor, ScanEvent, ScanPolarity, ScanWindow, SelectedIon, SignalContinuity + ArrayType, BinaryArrayMap, BinaryDataArrayType, CentroidPeakAdapting, Chromatogram, ChromatogramDescription, ChromatogramType, DataArray, DeconvolutedPeakAdapting, MultiLayerSpectrum, Precursor, ScanEvent, ScanPolarity, ScanWindow, SelectedIon, SignalContinuity }, Param, }; @@ -501,32 +502,29 @@ impl, D: DeconvolutedCentroidLike activation.energy = vact.collision_energy() as f32; match vact.dissociation_method() { DissociationMethod::CID => { - *activation.method_mut() = Some(ActivationMethod::CollisionInducedDissociation); + activation.methods_mut().push(DissociationMethodTerm::CollisionInducedDissociation); } DissociationMethod::HCD => { - *activation.method_mut() = - Some(ActivationMethod::BeamTypeCollisionInducedDissociation); + activation.methods_mut().push(DissociationMethodTerm::BeamTypeCollisionInducedDissociation); } DissociationMethod::ECD => { - *activation.method_mut() = Some(ActivationMethod::ElectronCaptureDissociation); + activation.methods_mut().push(DissociationMethodTerm::ElectronCaptureDissociation); } DissociationMethod::ETD => { - *activation.method_mut() = Some(ActivationMethod::ElectronTransferDissociation); + activation.methods_mut().push(DissociationMethodTerm::ElectronTransferDissociation); } DissociationMethod::ETHCD => { - *activation.method_mut() = Some(ActivationMethod::ElectronTransferDissociation); + activation.methods_mut().push(DissociationMethodTerm::ElectronTransferDissociation); activation.add_param( - ActivationMethod::SupplementalBeamTypeCollisionInducedDissociation.into(), + DissociationMethodTerm::SupplementalBeamTypeCollisionInducedDissociation.into(), ); } DissociationMethod::ETCID => { - *activation.method_mut() = Some(ActivationMethod::ElectronTransferDissociation); - activation - .add_param(ActivationMethod::SupplementalCollisionInducedDissociation.into()); + activation.methods_mut().push(DissociationMethodTerm::ElectronTransferDissociation); + activation.methods_mut().push(DissociationMethodTerm::SupplementalCollisionInducedDissociation.into()); } DissociationMethod::NETD => { - *activation.method_mut() = - Some(ActivationMethod::NegativeElectronTransferDissociation); + activation.methods_mut().push(DissociationMethodTerm::NegativeElectronTransferDissociation); } DissociationMethod::MPD => { todo!("Need to define MPD") @@ -535,18 +533,18 @@ impl, D: DeconvolutedCentroidLike todo!("Need to define PTD") } DissociationMethod::ECCID => { - *activation.method_mut() = Some(ActivationMethod::ElectronCaptureDissociation); + activation.methods_mut().push(DissociationMethodTerm::ElectronCaptureDissociation); activation - .add_param(ActivationMethod::SupplementalCollisionInducedDissociation.into()); + .add_param(DissociationMethodTerm::SupplementalCollisionInducedDissociation.into()); } DissociationMethod::ECHCD => { - *activation.method_mut() = Some(ActivationMethod::ElectronCaptureDissociation); + activation.methods_mut().push(DissociationMethodTerm::ElectronCaptureDissociation); activation.add_param( - ActivationMethod::SupplementalBeamTypeCollisionInducedDissociation.into(), + DissociationMethodTerm::SupplementalBeamTypeCollisionInducedDissociation.into(), ) } _ => { - *activation.method_mut() = Some(ActivationMethod::CollisionInducedDissociation); + activation.methods_mut().push(DissociationMethodTerm::CollisionInducedDissociation); } } diff --git a/src/io/traits/spectrum.rs b/src/io/traits/spectrum.rs index e2f89a1..82118ab 100644 --- a/src/io/traits/spectrum.rs +++ b/src/io/traits/spectrum.rs @@ -652,6 +652,14 @@ impl< } } + pub fn get_inner(&self) -> &I { + &self.source + } + + pub fn get_mut(&mut self) -> &mut I { + &mut self.source + } + fn push_front(&mut self, spectrum: S) { self.buffer.push_front(spectrum); } diff --git a/src/meta.rs b/src/meta.rs index 5f2b08d..610dde8 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -7,6 +7,7 @@ mod instrument; mod run; mod sample; mod software; +mod activation; #[macro_use] mod traits; @@ -25,6 +26,7 @@ pub use instrument::{Component, ComponentType, InstrumentConfiguration, Detector pub use run::MassSpectrometryRun; pub use traits::MSDataFileMetadata; pub use sample::Sample; +pub use activation::{DissociationMethodTerm, DissociationEnergyTerm, DissociationEnergy}; use crate::params::{ParamValueParseError, Value, ValueRef}; @@ -194,6 +196,173 @@ macro_rules! cvmap { } } }; + + ( + #[value_type=$value_type:ty] + #[flag_type=$flag_type:ty] + $(#[$enum_attrs:meta])* + $vis:vis enum $enum_name:ident { + $( + #[term(cv=$cv:ident, accession=$accession:literal, name=$term_name:literal, flags=$flags:tt, parents=$parents:tt)] + $(#[$variant_attrs:meta])* + $variant:ident($value_type2:ty) + ),* + $(,)? + } + ) => { + $(#[$enum_attrs])* + $vis enum $enum_name { + $( + $(#[$variant_attrs])* + $variant($value_type), + )* + } + + /// These methods are part of the controlled vocabulary mapping + impl $enum_name { + + /// Retrieve the accession number for this term, independent of its controlled vocabulary + pub const fn accession(&self) -> u32 { + match self { + $(Self::$variant(_) => $accession,)* + } + } + + /// Retrieve the controlled vocabulary this term belongs to + pub const fn controlled_vocabulary(&self) -> $crate::params::ControlledVocabulary { + match self { + $(Self::$variant(_) => $crate::params::ControlledVocabulary::$cv,)* + } + } + + /// Retrieve the plain text human readable name for this term + pub const fn name(&self) -> &'static str { + match self { + $(Self::$variant(_) => $term_name,)* + } + } + + /// Attempt to map a string by name to retrieve one of the terms from this + /// set. + /// + /// If no match is found, [`None`] is returned. + pub fn from_name(name: &str) -> Option { + match name { + $($term_name => Some(Self::$variant(Default::default())),)* + _ => None + } + } + + + /// Attempt to map the numeric accession number to retrieve one of the terms from this + /// set. + /// + /// If no match is found, [`None`] is returned. + pub const fn from_accession(accession: u32, value: $value_type) -> Option { + match accession { + $($accession => Some(Self::$variant(value)),)* + _ => None + } + } + + /// Convert this term into a [`ParamCow`](crate::params::ParamCow) without a value. + pub const fn to_param(self) -> $crate::params::ParamCow<'static> { + $crate::params::ParamCow::const_new( + self.name(), + $crate::params::ValueRef::Empty, + Some(self.accession()), + Some(self.controlled_vocabulary()), + $crate::params::Unit::Unknown + ) + } + + /// Convert a [`CURIE`]($crate::params::CURIE) by accession. + /// + /// If no match is found, [`None`] is returned. + pub fn from_curie(curie: &$crate::params::CURIE, value: f32) -> Option { + if matches!(curie.controlled_vocabulary, $crate::params::ControlledVocabulary::MS) { + Self::from_accession(curie.accession, value) + } else { + None + } + } + + /// Attempt to convert a [`ParamCow`](crate::params::ParamCow) to a term from this set. + /// + /// If no match is found, [`None`] is returned. + /// + /// # Note + /// This method can be called in `const` contexts, requiring the type be [`ParamCow`](crate::params::ParamCow) with a `'static` + /// lifetime parameter, but the regular [`From`] trait is implemented for all [`ParamLike`](crate::params::ParamLike) types. + pub fn from_param(p: &$crate::params::ParamCow<'static>) -> Option { + if let Some(acc) = p.accession { + Self::from_accession(acc, p.value.clone().into()) + } else { + None + } + } + + /// Retrieve a term set specific set of flags + pub fn flags(&self) -> $flag_type { + match self { + $(Self::$variant(_) => $flags.into(),)* + } + } + + /// Retrieve the list of zero or more terms in the set which are + /// parents of this term. + pub fn parents(&self) -> Vec { + match self { + $(Self::$variant(_) => $parents.iter().map(|s: &&str| { + let curie = s.parse::<$crate::params::CURIE>().unwrap(); + Self::from_accession(curie.accession, Default::default()).unwrap() + }).collect(),)* + } + } + + } + + impl

From

for $enum_name where P: $crate::params::ParamLike { + fn from(value: P) -> Self { + Self::from_accession( + value.accession().expect( + concat!("Cannot convert an uncontrolled parameter to ", stringify!($enum_name))), + value.value().into() + ).unwrap_or_else( + || panic!( + "Could not map {:?}:{} to {}", + value.controlled_vocabulary().unwrap(), + value.accession().unwrap(), + stringify!($enum_name) + ) + ) + } + } + + impl From<$enum_name> for $crate::params::ParamCow<'static> { + fn from(value: $enum_name) -> Self { + value.to_param() + } + } + + impl From<$enum_name> for $crate::params::Param { + fn from(value: $enum_name) -> Self { + value.to_param().into() + } + } + + impl From<&$enum_name> for $crate::params::ParamCow<'static> { + fn from(value: &$enum_name) -> Self { + value.to_param() + } + } + + impl From<&$enum_name> for $crate::params::Param { + fn from(value: &$enum_name) -> Self { + value.to_param().into() + } + } + }; } bitflags::bitflags! { diff --git a/src/meta/activation.rs b/src/meta/activation.rs new file mode 100644 index 0000000..aa94a24 --- /dev/null +++ b/src/meta/activation.rs @@ -0,0 +1,227 @@ +use std::fmt::Display; + + + +crate::cvmap! { + #[flag_type=i32] + #[allow(unused)] + #[doc = "A method used for dissociation or fragmentation."] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + /*[[[cog + import cog + import subprocess + buf = subprocess.check_output(['python', 'cv/extract_activation.py']).decode('utf8') + for line in buf.splitlines(): + cog.outl(line) + ]]]*/ + pub enum DissociationMethodTerm { + #[term(cv=MS, accession=1000044, name="dissociation method", flags={0}, parents={[]})] + #[doc = "dissociation method - Fragmentation method used for dissociation or fragmentation."] + DissociationMethod, + #[term(cv=MS, accession=1000133, name="collision-induced dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "collision-induced dissociation - The dissociation of an ion after collisional excitation. The term collisional-activated dissociation is not recommended."] + CollisionInducedDissociation, + #[term(cv=MS, accession=1000134, name="plasma desorption", flags={0}, parents={["MS:1000044"]})] + #[doc = "plasma desorption - The ionization of material in a solid sample by bombarding it with ionic or neutral atoms formed as a result of the fission of a suitable nuclide, typically 252Cf. Synonymous with fission fragment ionization."] + PlasmaDesorption, + #[term(cv=MS, accession=1000135, name="post-source decay", flags={0}, parents={["MS:1000044"]})] + #[doc = "post-source decay - A technique specific to reflectron time-of-flight mass spectrometers where product ions of metastable transitions or collision-induced dissociations generated in the drift tube prior to entering the reflectron are m/z separated to yield product ion spectra."] + PostSourceDecay, + #[term(cv=MS, accession=1000136, name="surface-induced dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "surface-induced dissociation - Fragmentation that results from the collision of an ion with a surface."] + SurfaceInducedDissociation, + #[term(cv=MS, accession=1000242, name="blackbody infrared radiative dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "blackbody infrared radiative dissociation - A special case of infrared multiphoton dissociation wherein excitation of the reactant ion is caused by absorption of infrared photons radiating from heated blackbody surroundings, which are usually the walls of a vacuum chamber. See also infrared multiphoton dissociation."] + BlackbodyInfraredRadiativeDissociation, + #[term(cv=MS, accession=1000250, name="electron capture dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "electron capture dissociation - A process in which a multiply protonated molecules interacts with a low energy electrons. Capture of the electron leads the liberation of energy and a reduction in charge state of the ion with the production of the (M + nH) (n-1)+ odd electron ion, which readily fragments."] + ElectronCaptureDissociation, + #[term(cv=MS, accession=1000262, name="infrared multiphoton dissociation", flags={0}, parents={["MS:1000435"]})] + #[doc = "infrared multiphoton dissociation - Multiphoton ionization where the reactant ion dissociates as a result of the absorption of multiple infrared photons."] + InfraredMultiphotonDissociation, + #[term(cv=MS, accession=1000282, name="sustained off-resonance irradiation", flags={0}, parents={["MS:1000044"]})] + #[doc = "sustained off-resonance irradiation - A technique associated with Fourier transform ion cyclotron resonance (FT-ICR) mass spectrometry to carry out ion/neutral reactions such as low-energy collision-induced dissociation. A radio-frequency electric field of slightly off-resonance to the cyclotron frequency of the reactant ion cyclically accelerates and decelerates the reactant ion that is confined in the Penning ion trap. The ion's orbit does not exceed the dimensions of ion trap while the ion undergoes an ion/neutral species process that produces a high average translational energy for an extended time."] + SustainedOffResonanceIrradiation, + #[term(cv=MS, accession=1000422, name="beam-type collision-induced dissociation", flags={0}, parents={["MS:1000133"]})] + #[doc = "beam-type collision-induced dissociation - A collision-induced dissociation process that occurs in a beam-type collision cell."] + BeamTypeCollisionInducedDissociation, + #[term(cv=MS, accession=1000433, name="low-energy collision-induced dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "low-energy collision-induced dissociation - A collision-induced dissociation process wherein the precursor ion has the translational energy lower than approximately 1000 eV. This process typically requires multiple collisions and the collisional excitation is cumulative."] + LowEnergyCollisionInducedDissociation, + #[term(cv=MS, accession=1000435, name="photodissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "photodissociation - A process wherein the reactant ion is dissociated as a result of absorption of one or more photons."] + Photodissociation, + #[term(cv=MS, accession=1000598, name="electron transfer dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "electron transfer dissociation - A process to fragment ions in a mass spectrometer by inducing fragmentation of cations (e.g. peptides or proteins) by transferring electrons from radical-anions."] + ElectronTransferDissociation, + #[term(cv=MS, accession=1000599, name="pulsed q dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "pulsed q dissociation - A process that involves precursor ion activation at high Q, a time delay to allow the precursor to fragment, then a rapid pulse to low Q where all fragment ions are trapped. The product ions can then be scanned out of the ion trap and detected."] + PulsedQDissociation, + #[term(cv=MS, accession=1001880, name="in-source collision-induced dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "in-source collision-induced dissociation - The dissociation of an ion as a result of collisional excitation during ion transfer from an atmospheric pressure ion source and the mass spectrometer vacuum."] + InSourceCollisionInducedDissociation, + #[term(cv=MS, accession=1002000, name="LIFT", flags={0}, parents={["MS:1000044"]})] + #[doc = "LIFT - A Bruker's proprietary technique where molecular ions are initially accelerated at lower energy, then collide with inert gas in a collision cell that is then 'lifted' to high potential. The use of inert gas is optional, as it could lift also fragments provided by LID."] + LIFT, + #[term(cv=MS, accession=1002472, name="trap-type collision-induced dissociation", flags={0}, parents={["MS:1000133"]})] + #[doc = "trap-type collision-induced dissociation - A collision-induced dissociation process that occurs in a trap-type collision cell."] + TrapTypeCollisionInducedDissociation, + #[term(cv=MS, accession=1002481, name="higher energy beam-type collision-induced dissociation", flags={0}, parents={["MS:1000422"]})] + #[doc = "higher energy beam-type collision-induced dissociation - A collision-induced dissociation process wherein the projectile ion has the translational energy higher than approximately 1000 eV."] + HigherEnergyBeamTypeCollisionInducedDissociation, + #[term(cv=MS, accession=1002678, name="supplemental beam-type collision-induced dissociation", flags={0}, parents={["MS:1000422"]})] + #[doc = "supplemental beam-type collision-induced dissociation - A supplemental collision-induced dissociation process that occurs in a beam-type collision cell in addition to another primary type of dissociation."] + SupplementalBeamTypeCollisionInducedDissociation, + #[term(cv=MS, accession=1002679, name="supplemental collision-induced dissociation", flags={0}, parents={["MS:1000133"]})] + #[doc = "supplemental collision-induced dissociation - The dissociation of an ion after supplemental collisional excitation."] + SupplementalCollisionInducedDissociation, + #[term(cv=MS, accession=1003246, name="ultraviolet photodissociation", flags={0}, parents={["MS:1000435"]})] + #[doc = "ultraviolet photodissociation - Multiphoton ionization where the reactant ion dissociates as a result of the absorption of multiple UV photons."] + UltravioletPhotodissociation, + #[term(cv=MS, accession=1003247, name="negative electron transfer dissociation", flags={0}, parents={["MS:1000044"]})] + #[doc = "negative electron transfer dissociation - A process to fragment ions in a mass spectrometer by inducing fragmentation of anions (e.g. peptides or proteins) by transferring electrons to a radical-cation."] + NegativeElectronTransferDissociation, + #[term(cv=MS, accession=1003294, name="electron activated dissociation", flags={0}, parents={["MS:1000250"]})] + #[doc = "electron activated dissociation - A process to fragment ions in a high intensity electron beam which results in a dissociation of various analytes ranging from singly charged small molecules to multiply protonated proteins."] + ElectronActivatedDissociation, + } + //[[[end]]] (checksum: c0f6c7cfcb5d43054288f14702549985) +} + +impl DissociationMethodTerm { + + pub fn is_electronic(&self) -> bool { + match self { + Self::ElectronActivatedDissociation + | Self::ElectronCaptureDissociation + | Self::ElectronTransferDissociation + | Self::NegativeElectronTransferDissociation => true, + _ => false + } + } + + pub fn is_collisional(&self) -> bool { + match self { + Self::CollisionInducedDissociation + | Self::LowEnergyCollisionInducedDissociation + | Self::BeamTypeCollisionInducedDissociation + | Self::TrapTypeCollisionInducedDissociation + | Self::InSourceCollisionInducedDissociation + | Self::SupplementalBeamTypeCollisionInducedDissociation + | Self::SupplementalCollisionInducedDissociation + | Self::HigherEnergyBeamTypeCollisionInducedDissociation => true, + _ => false, + } + } +} + +crate::cvmap! { + #[value_type=f32] + #[flag_type=i32] + #[doc = "Energy for an ion experiencing some form of dissociation"] + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] + /*[[[cog + import cog + import subprocess + buf = subprocess.check_output(['python', 'cv/extract_energy.py']).decode('utf8') + for line in buf.splitlines(): + cog.outl(line) + ]]]*/ + pub enum DissociationEnergyTerm { + #[term(cv=MS, accession=1000045, name="collision energy", flags={0}, parents={["MS:1000510"]})] + #[doc = "collision energy - Energy for an ion experiencing collision with a stationary gas particle resulting in dissociation of the ion."] + CollisionEnergy(f32), + #[term(cv=MS, accession=1000138, name="normalized collision energy", flags={0}, parents={["MS:1000510"]})] + #[doc = "normalized collision energy - Instrument setting, expressed in percent, for adjusting collisional energies of ions in an effort to provide equivalent excitation of all ions."] + NormalizedCollisionEnergy(f32), + #[term(cv=MS, accession=1002013, name="collision energy ramp start", flags={0}, parents={["MS:1000045"]})] + #[doc = "collision energy ramp start - Collision energy at the start of the collision energy ramp."] + CollisionEnergyRampStart(f32), + #[term(cv=MS, accession=1002014, name="collision energy ramp end", flags={0}, parents={["MS:1000045"]})] + #[doc = "collision energy ramp end - Collision energy at the end of the collision energy ramp."] + CollisionEnergyRampEnd(f32), + #[term(cv=MS, accession=1002218, name="percent collision energy ramp start", flags={0}, parents={["MS:1000138"]})] + #[doc = "percent collision energy ramp start - Collision energy at the start of the collision energy ramp in percent, normalized to the mass of the ion."] + PercentCollisionEnergyRampStart(f32), + #[term(cv=MS, accession=1002219, name="percent collision energy ramp end", flags={0}, parents={["MS:1000138"]})] + #[doc = "percent collision energy ramp end - Collision energy at the end of the collision energy ramp in percent, normalized to the mass of the ion."] + PercentCollisionEnergyRampEnd(f32), + #[term(cv=MS, accession=1002680, name="supplemental collision energy", flags={0}, parents={["MS:1000510"]})] + #[doc = "supplemental collision energy - Energy for an ion experiencing supplemental collision with a stationary gas particle resulting in dissociation of the ion."] + SupplementalCollisionEnergy(f32), + #[term(cv=MS, accession=1003410, name="electron beam energy", flags={0}, parents={["MS:1000510"]})] + #[doc = "electron beam energy - The kinetic energy of the electron beam used in dissociation methods induced by a free electron beam, such as electron-capture dissociation (ECD), electron-detachment dissociation (EDD), and electron-activated dissociation (EAD)."] + ElectronBeamEnergy(f32), + } + //[[[end]]] (checksum: 252597ff6067b90ae0bc37f63da81021) +} + +impl Display for DissociationEnergyTerm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl DissociationEnergyTerm { + pub const fn is_supplemental(&self) -> bool { + matches!(self, Self::SupplementalCollisionEnergy(_)) + } + + pub const fn is_ramp_start(&self) -> bool { + matches!(self, Self::CollisionEnergyRampStart(_) | Self::PercentCollisionEnergyRampStart(_)) + } + + pub const fn is_ramp_end(&self) -> bool { + matches!(self, Self::CollisionEnergyRampEnd(_) | Self::PercentCollisionEnergyRampEnd(_)) + } + + pub const fn energy(&self) -> f32 { + *match self { + DissociationEnergyTerm::CollisionEnergy(x) => x, + DissociationEnergyTerm::NormalizedCollisionEnergy(x) => x, + DissociationEnergyTerm::CollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::CollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::SupplementalCollisionEnergy(x) => x, + DissociationEnergyTerm::ElectronBeamEnergy(x) => x, + } + } + + pub fn energy_mut(&mut self) -> &mut f32 { + match self { + DissociationEnergyTerm::CollisionEnergy(x) => x, + DissociationEnergyTerm::NormalizedCollisionEnergy(x) => x, + DissociationEnergyTerm::CollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::CollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::SupplementalCollisionEnergy(x) => x, + DissociationEnergyTerm::ElectronBeamEnergy(x) => x, + } + } +} + +impl AsRef for DissociationEnergyTerm { + fn as_ref(&self) -> &f32 { + match self { + DissociationEnergyTerm::CollisionEnergy(x) => x, + DissociationEnergyTerm::NormalizedCollisionEnergy(x) => x, + DissociationEnergyTerm::CollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::CollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampStart(x) => x, + DissociationEnergyTerm::PercentCollisionEnergyRampEnd(x) => x, + DissociationEnergyTerm::SupplementalCollisionEnergy(x) => x, + DissociationEnergyTerm::ElectronBeamEnergy(x) => x, + } + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub enum DissociationEnergy { + Energy(DissociationEnergyTerm), + Ramp { start: DissociationEnergyTerm, end: DissociationEnergyTerm }, + Combined { primary: DissociationEnergyTerm, supplementary: DissociationEnergyTerm} +} + diff --git a/src/meta/file_description.rs b/src/meta/file_description.rs index 3143ec4..0668d4c 100644 --- a/src/meta/file_description.rs +++ b/src/meta/file_description.rs @@ -378,7 +378,7 @@ crate::cvmap! { #[doc = "Open Chromatography Binary OCB format - ChemClipse/OpenChrom file format."] OpenChromatographyBinaryOCB, } - //[[[end]]] (checksum: 05a2b3a28a60b4f6990463fb98e5afe4) + //[[[end]]] (checksum: 144dff32032929c664857bb8d843810a) } diff --git a/src/meta/software.rs b/src/meta/software.rs index a044080..a702d94 100644 --- a/src/meta/software.rs +++ b/src/meta/software.rs @@ -806,7 +806,7 @@ crate::cvmap! { LibraryCreationSoftware, #[term(cv=MS, accession=1003232, name="PeakForest", flags={1}, parents={["MS:1001456", "MS:1003207", "MS:1002878"]})] PeakForest, - #[term(cv=MS, accession=1003253, name="DIA-NN", flags={1}, parents={["MS:1001139", "MS:1001456"]})] + #[term(cv=MS, accession=1003253, name="DIA-NN", flags={1}, parents={["MS:1001139", "MS:1001456", "MS:1003207"]})] DIANN, #[term(cv=MS, accession=1003281, name="Casanovo", flags={1}, parents={["MS:1001456"]})] Casanovo, @@ -843,7 +843,7 @@ crate::cvmap! { #[term(cv=MS, accession=4000151, name="MsQuality", flags={1}, parents={["MS:1001456"]})] MsQuality, } - //[[[end]]] (checksum: d489393ae10116422bbe72ad6eebfee7) + //[[[end]]] (checksum: d5462cd300dc95d12f301bba900962a3) } #[cfg(test)] diff --git a/src/params.rs b/src/params.rs index c71bb64..239fad2 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1027,6 +1027,30 @@ macro_rules! param_value_ref_float { }; } +impl From> for f32 { + fn from(value: ValueRef<'_>) -> Self { + value.to_f32().unwrap() + } +} + +impl From> for f64 { + fn from(value: ValueRef<'_>) -> Self { + value.to_f64().unwrap() + } +} + +impl From> for i32 { + fn from(value: ValueRef<'_>) -> Self { + value.to_i32().unwrap() + } +} + +impl From> for i64 { + fn from(value: ValueRef<'_>) -> Self { + value.to_i64().unwrap() + } +} + impl From for Value { fn from(value: bool) -> Self { Self::Boolean(value) diff --git a/src/spectrum/scan_properties.rs b/src/spectrum/scan_properties.rs index e42e6ed..93666b9 100644 --- a/src/spectrum/scan_properties.rs +++ b/src/spectrum/scan_properties.rs @@ -9,6 +9,7 @@ use crate::io::traits::SpectrumSource; use crate::params::{ ControlledVocabulary, Param, ParamDescribed, ParamLike, ParamValue, Unit, ValueRef, CURIE, }; +use crate::meta::DissociationMethodTerm; use crate::{curie, impl_param_described, ParamList}; /** @@ -309,11 +310,19 @@ impl Acquisition { .collect() } - pub fn iter(&self) -> impl Iterator { + pub fn len(&self) -> usize { + self.scans.len() + } + + pub fn is_empty(&self) -> bool { + self.scans.is_empty() + } + + pub fn iter(&self) -> std::slice::Iter<'_, ScanEvent> { self.scans.iter() } - pub fn iter_mut(&mut self) -> impl Iterator { + pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, ScanEvent> { self.scans.iter_mut() } } @@ -362,205 +371,43 @@ impl IonProperties for SelectedIon { impl IonMobilityMeasure for SelectedIon {} -#[derive(Debug, Clone, PartialEq)] -pub enum ActivationMethod { - CollisionInducedDissociation, - HighEnergyCollisionInducedDissociation, - LowEnergyCollisionInducedDissociation, - InSourceCollisionInducedDissociation, - BeamTypeCollisionInducedDissociation, - TrapTypeCollisionInducedDissociation, - - SupplementalCollisionInducedDissociation, - SupplementalBeamTypeCollisionInducedDissociation, - - ElectronTransferDissociation, - ElectronCaptureDissociation, - ElectronActivationDissociation, - NegativeElectronTransferDissociation, - - Photodissociation, - UltravioletPhotodissociation, - Other(Box), +#[derive(Debug, Default, Clone, PartialEq)] +/// Describes the activation method used to dissociate the precursor ion +pub struct Activation { + _methods: Vec, + pub energy: f32, + pub params: ParamList, } -impl ActivationMethod { - pub fn accession(&self) -> Option { - match self { - ActivationMethod::CollisionInducedDissociation => Some(1000133), - ActivationMethod::LowEnergyCollisionInducedDissociation => Some(1000433), - ActivationMethod::BeamTypeCollisionInducedDissociation => Some(1000422), - ActivationMethod::TrapTypeCollisionInducedDissociation => Some(1002472), - ActivationMethod::SupplementalCollisionInducedDissociation => Some(1002678), - ActivationMethod::SupplementalBeamTypeCollisionInducedDissociation => Some(1002679), - ActivationMethod::HighEnergyCollisionInducedDissociation => Some(1002481), - ActivationMethod::InSourceCollisionInducedDissociation => Some(1001880), - - ActivationMethod::ElectronCaptureDissociation => Some(1000250), - ActivationMethod::ElectronTransferDissociation => Some(1000598), - ActivationMethod::NegativeElectronTransferDissociation => Some(1003247), - ActivationMethod::ElectronActivationDissociation => Some(1003294), - - ActivationMethod::Photodissociation => Some(1000435), - ActivationMethod::UltravioletPhotodissociation => Some(1003246), - - ActivationMethod::Other(param) => param.accession(), - } - } +impl Activation { - pub fn is_collisional(&self) -> Option { - match self { - ActivationMethod::CollisionInducedDissociation - | ActivationMethod::LowEnergyCollisionInducedDissociation - | ActivationMethod::BeamTypeCollisionInducedDissociation - | Self::TrapTypeCollisionInducedDissociation - | Self::InSourceCollisionInducedDissociation - | Self::SupplementalBeamTypeCollisionInducedDissociation - | Self::SupplementalCollisionInducedDissociation - | Self::HighEnergyCollisionInducedDissociation => Some(true), - Self::Other(_) => None, - _ => Some(false), - } + /// Get a reference to the first activation method, if it exists + pub fn method(&self) -> Option<&DissociationMethodTerm> { + self._methods.first() } - pub fn controlled_vocabulary(&self) -> Option { - match self { - ActivationMethod::Other(param) => param.controlled_vocabulary(), - _ => Some(ControlledVocabulary::MS), - } + /// Get a mutable reference to the first activation method, if it exists + pub fn method_mut(&mut self) -> Option<&mut DissociationMethodTerm> { + self._methods.first_mut() } -} -impl From for Param { - fn from(value: ActivationMethod) -> Self { - match value { - ActivationMethod::Other(val) => *val, - ActivationMethod::CollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident("collision-induced dissociation", value.accession().unwrap()) - .into(), - ActivationMethod::LowEnergyCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "low-energy collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::BeamTypeCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "beam-type collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::TrapTypeCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "trap-type collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::HighEnergyCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "higher energy collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::InSourceCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "in-source collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::SupplementalCollisionInducedDissociation => ControlledVocabulary::MS - .const_param_ident( - "supplemental collision-induced dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::SupplementalBeamTypeCollisionInducedDissociation => { - ControlledVocabulary::MS - .const_param_ident( - "supplemental beam-type collision-induced dissociation", - value.accession().unwrap(), - ) - .into() - } - ActivationMethod::ElectronTransferDissociation => ControlledVocabulary::MS - .const_param_ident("electron transfer dissociation", value.accession().unwrap()) - .into(), - ActivationMethod::ElectronCaptureDissociation => ControlledVocabulary::MS - .const_param_ident("electron capture dissociation", value.accession().unwrap()) - .into(), - ActivationMethod::ElectronActivationDissociation => ControlledVocabulary::MS - .const_param_ident( - "electron activation dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::NegativeElectronTransferDissociation => ControlledVocabulary::MS - .const_param_ident( - "negative electron transfer dissociation", - value.accession().unwrap(), - ) - .into(), - ActivationMethod::Photodissociation => ControlledVocabulary::MS - .const_param_ident("photodissociation", value.accession().unwrap()) - .into(), - ActivationMethod::UltravioletPhotodissociation => ControlledVocabulary::MS - .const_param_ident("ultraviolet photodissociation", value.accession().unwrap()) - .into(), - } + /// Get a slice over all of the activation methods used. + pub fn methods(&self) -> &[DissociationMethodTerm] { + &self._methods } -} -impl> From

for ActivationMethod { - fn from(value: P) -> Self { - if value.is_ms() { - match value.accession().unwrap() { - 1000133 => Self::CollisionInducedDissociation, - 1000433 => Self::LowEnergyCollisionInducedDissociation, - 1002481 => Self::HighEnergyCollisionInducedDissociation, - 1000422 => Self::BeamTypeCollisionInducedDissociation, - 1002472 => Self::TrapTypeCollisionInducedDissociation, - 1002678 => Self::SupplementalCollisionInducedDissociation, - 1002679 => Self::SupplementalBeamTypeCollisionInducedDissociation, - - 1001880 => Self::InSourceCollisionInducedDissociation, - - 1000250 => Self::ElectronCaptureDissociation, - 1000598 => Self::ElectronTransferDissociation, - 1003247 => Self::NegativeElectronTransferDissociation, - 1003294 => Self::ElectronActivationDissociation, - - 1000435 => Self::Photodissociation, - 1003246 => Self::UltravioletPhotodissociation, - - _ => Self::Other(Box::new(value.into())), - } - } else { - Self::Other(Box::new(value.into())) - } - } -} - -#[derive(Debug, Default, Clone, PartialEq)] -/// Describes the activation method used to dissociate the precursor ion -pub struct Activation { - _method: Option, - pub energy: f32, - pub params: ParamList, -} - -impl Activation { - pub fn method(&self) -> Option<&ActivationMethod> { - if self._method.is_some() { - self._method.as_ref() - } else { - None - } + /// Get a mutable reference to a [`Vec`] of activation methods, which may be useful + /// for adding or removing methods. + pub fn methods_mut(&mut self) -> &mut Vec { + &mut self._methods } - pub fn method_mut(&mut self) -> &mut Option { - &mut self._method + /// Check if multiple dissociation methods were used + pub fn is_combined(&self) -> bool { + self._methods.len() > 1 } + /// Check if a [`ParamLike`] type references an activation method pub fn is_param_activation(p: &P) -> bool { if p.is_controlled() && p.controlled_vocabulary().unwrap() == ControlledVocabulary::MS { Self::accession_to_activation(p.accession().unwrap()) @@ -570,54 +417,21 @@ impl Activation { } pub fn accession_to_activation(accession: u32) -> bool { - matches!( - accession, - 1000133 - | 1000134 - | 1000135 - | 1000136 - | 1000242 - | 1000250 - | 1000282 - | 1000433 - | 1000435 - | 1000598 - | 1000599 - | 1001880 - | 1002000 - | 1003181 - | 1003247 - | 1000422 - | 1002472 - | 1002679 - | 1003294 - | 1000262 - | 1003246 - | 1002631 - | 1003182 - | 1002481 - | 1002678 - ) + DissociationMethodTerm::from_accession(accession).is_some() } - pub fn _set_method(&mut self) { - let found = self - .params - .iter() - .enumerate() - .filter(|(_, p)| { - if p.is_controlled() && p.controlled_vocabulary.unwrap() == ControlledVocabulary::MS - { - Self::accession_to_activation(p.accession.unwrap()) - } else { - false - } - }) - .map(|(i, _)| i) - .next(); - if let Some(hit) = found { - self._method = Some(self.params.remove(hit).into()); + pub fn _extract_methods_from_params(&mut self) { + let mut methods = Vec::with_capacity(1); + let mut rest = Vec::with_capacity(self.params.len()); + for p in self.params.drain(..) { + if Self::is_param_activation(&p) { + methods.push(p.into()) + } else { + rest.push(p) + } } + self.params = rest; + self._methods = methods; } }