diff --git a/src/io/readers/precursor_reader.rs b/src/io/readers/precursor_reader.rs index e4750c5..8f477d0 100644 --- a/src/io/readers/precursor_reader.rs +++ b/src/io/readers/precursor_reader.rs @@ -9,7 +9,7 @@ use tdf::{TDFPrecursorReader, TDFPrecursorReaderError}; use crate::ms_data::Precursor; -use super::quad_settings_reader::FrameWindowSplittingStrategy; +use super::FrameWindowSplittingConfiguration; pub struct PrecursorReader { precursor_reader: Box, @@ -42,7 +42,7 @@ impl PrecursorReader { #[derive(Debug, Default, Clone)] pub struct PrecursorReaderBuilder { path: PathBuf, - config: FrameWindowSplittingStrategy, + config: FrameWindowSplittingConfiguration, } impl PrecursorReaderBuilder { @@ -53,7 +53,10 @@ impl PrecursorReaderBuilder { } } - pub fn with_config(&self, config: FrameWindowSplittingStrategy) -> Self { + pub fn with_config( + &self, + config: FrameWindowSplittingConfiguration, + ) -> Self { Self { config: config, ..self.clone() diff --git a/src/io/readers/precursor_reader/tdf.rs b/src/io/readers/precursor_reader/tdf.rs index 34efef1..60d179d 100644 --- a/src/io/readers/precursor_reader/tdf.rs +++ b/src/io/readers/precursor_reader/tdf.rs @@ -9,7 +9,7 @@ use dia::{DIATDFPrecursorReader, DIATDFPrecursorReaderError}; use crate::{ io::readers::{ file_readers::sql_reader::{SqlError, SqlReader}, - quad_settings_reader::FrameWindowSplittingStrategy, + FrameWindowSplittingConfiguration, }, ms_data::{AcquisitionType, Precursor}, }; @@ -23,7 +23,7 @@ pub struct TDFPrecursorReader { impl TDFPrecursorReader { pub fn new( path: impl AsRef, - splitting_strategy: FrameWindowSplittingStrategy, + splitting_strategy: FrameWindowSplittingConfiguration, ) -> Result { let sql_path = path.as_ref(); let tdf_sql_reader = SqlReader::open(sql_path)?; diff --git a/src/io/readers/precursor_reader/tdf/dia.rs b/src/io/readers/precursor_reader/tdf/dia.rs index 9eacaa7..435636d 100644 --- a/src/io/readers/precursor_reader/tdf/dia.rs +++ b/src/io/readers/precursor_reader/tdf/dia.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::io::readers::quad_settings_reader::FrameWindowSplittingStrategy; +use crate::io::readers::FrameWindowSplittingConfiguration; use crate::{ domain_converters::{ ConvertableDomain, Frame2RtConverter, Scan2ImConverter, @@ -25,16 +25,17 @@ pub struct DIATDFPrecursorReader { impl DIATDFPrecursorReader { pub fn new( path: impl AsRef, - splitting_strategy: FrameWindowSplittingStrategy, + splitting_config: FrameWindowSplittingConfiguration, ) -> Result { let sql_path = path.as_ref(); let tdf_sql_reader = SqlReader::open(sql_path)?; let metadata = MetadataReader::new(&path)?; let rt_converter: Frame2RtConverter = metadata.rt_converter; let im_converter: Scan2ImConverter = metadata.im_converter; + let splitting_strategy = splitting_config.finalize(im_converter); let expanded_quadrupole_settings = QuadrupoleSettingsReader::from_splitting( - tdf_sql_reader.get_path(), + &tdf_sql_reader, splitting_strategy, )?; let reader = Self { diff --git a/src/io/readers/quad_settings_reader.rs b/src/io/readers/quad_settings_reader.rs index 6f5f398..8559122 100644 --- a/src/io/readers/quad_settings_reader.rs +++ b/src/io/readers/quad_settings_reader.rs @@ -1,6 +1,10 @@ use std::path::Path; -use crate::{ms_data::QuadrupoleSettings, utils::vec_utils::argsort}; +use crate::{ + domain_converters::{ConvertableDomain, Scan2ImConverter}, + ms_data::QuadrupoleSettings, + utils::vec_utils::argsort, +}; use super::file_readers::sql_reader::{ frame_groups::SqlWindowGroup, quad_settings::SqlQuadSettings, @@ -48,11 +52,9 @@ impl QuadrupoleSettingsReader { } pub fn from_splitting( - path: impl AsRef, + tdf_sql_reader: &SqlReader, splitting_strat: FrameWindowSplittingStrategy, ) -> Result, QuadrupoleSettingsReaderError> { - let sql_path = path.as_ref(); - let tdf_sql_reader = SqlReader::open(&sql_path)?; let quadrupole_settings = Self::from_sql_settings(&tdf_sql_reader)?; let window_groups = SqlWindowGroup::from_sql_reader(&tdf_sql_reader)?; let expanded_quadrupole_settings = match splitting_strat { @@ -120,7 +122,8 @@ pub enum QuadrupoleSettingsReaderError { SqlError(#[from] SqlError), } -type SpanStep = (usize, usize); +type MobilitySpanStep = (f64, f64); +type ScanSpanStep = (usize, usize); /// Strategy for expanding quadrupole settings /// @@ -135,16 +138,62 @@ type SpanStep = (usize, usize); /// * `Even(usize)` - Split the quadrupole settings into `usize` evenly spaced /// subwindows; e.g. if `usize` is 2, the window will be split into 2 subwindows /// of equal width. -/// * `Uniform(SpanStep)` - Split the quadrupole settings into subwindows of -/// width `SpanStep.0` and step `SpanStep.1`; e.g. if `SpanStep` is (100, 50), -/// the window will be split into subwindows of width 100 and step 50 between their -/// scan start and end. +/// * `UniformMobility(SpanStep)` - Split the quadrupole settings into subwindows of +/// width `SpanStep.0` and step `SpanStep.1` in ion mobility space. +/// e.g. if `SpanStep` is (0.05, 0.02), +/// the window will be split into subwindows of width 0.05 and step 0.02 between their +/// in the mobility dimension. +/// * `UniformScan(SpanStep)` - Split the quadrupole settings into subwindows of +/// width `SpanStep.0` and step `SpanStep.1` in scan number space. +/// e.g. if `SpanStep` is (100, 80), +/// the window will be split into subwindows of width +/// 100 and step 80 between their in the scan number. /// #[derive(Debug, Copy, Clone)] pub enum QuadWindowExpansionStrategy { None, Even(usize), - Uniform(SpanStep), + UniformMobility(MobilitySpanStep, Scan2ImConverter), + UniformScan(ScanSpanStep), +} + +#[derive(Debug, Copy, Clone)] +pub enum QuadWindowExpansionConfiguration { + None, + Even(usize), + UniformMobility(MobilitySpanStep), + UniformScan(ScanSpanStep), +} + +impl Default for QuadWindowExpansionConfiguration { + fn default() -> Self { + Self::Even(1) + } +} + +impl QuadWindowExpansionConfiguration { + pub fn finalize( + self, + scan_converter: Scan2ImConverter, + ) -> QuadWindowExpansionStrategy { + match self { + QuadWindowExpansionConfiguration::None => { + QuadWindowExpansionStrategy::None + }, + QuadWindowExpansionConfiguration::Even(x) => { + QuadWindowExpansionStrategy::Even(x) + }, + QuadWindowExpansionConfiguration::UniformMobility((span, step)) => { + QuadWindowExpansionStrategy::UniformMobility( + (span, step), + scan_converter, + ) + }, + QuadWindowExpansionConfiguration::UniformScan((span, step)) => { + QuadWindowExpansionStrategy::UniformScan((span, step)) + }, + } + } } #[derive(Debug, Clone, Copy)] @@ -153,9 +202,33 @@ pub enum FrameWindowSplittingStrategy { Window(QuadWindowExpansionStrategy), } -impl Default for FrameWindowSplittingStrategy { +#[derive(Debug, Clone, Copy)] +pub enum FrameWindowSplittingConfiguration { + Quadrupole(QuadWindowExpansionConfiguration), + Window(QuadWindowExpansionConfiguration), +} + +impl Default for FrameWindowSplittingConfiguration { fn default() -> Self { - Self::Quadrupole(QuadWindowExpansionStrategy::Even(1)) + Self::Quadrupole(QuadWindowExpansionConfiguration::Even(1)) + } +} + +impl FrameWindowSplittingConfiguration { + pub fn finalize( + self, + scan_converter: Scan2ImConverter, + ) -> FrameWindowSplittingStrategy { + match self { + FrameWindowSplittingConfiguration::Quadrupole(x) => { + FrameWindowSplittingStrategy::Quadrupole( + x.finalize(scan_converter), + ) + }, + FrameWindowSplittingConfiguration::Window(x) => { + FrameWindowSplittingStrategy::Window(x.finalize(scan_converter)) + }, + } } } @@ -164,7 +237,7 @@ fn scan_range_subsplit( end: usize, strategy: &QuadWindowExpansionStrategy, ) -> Vec<(usize, usize)> { - let out = match strategy { + let out: Vec<(usize, usize)> = match strategy { QuadWindowExpansionStrategy::None => { vec![(start, end)] }, @@ -181,17 +254,44 @@ fn scan_range_subsplit( } out }, - QuadWindowExpansionStrategy::Uniform((span, step)) => { - let mut curr_start = start.clone(); - let mut curr_end = start + span; + QuadWindowExpansionStrategy::UniformMobility( + (span, step), + converter, + ) => { + // Since scan start < scan end but low scans are high IMs, we need to + // subtract instead of adding. + let mut curr_start_offset = start.clone(); + let mut curr_start_im = converter.convert(curr_start_offset as f64); + + let mut curr_end_im = curr_start_im - span; + let mut curr_end_offset = converter.invert(curr_end_im) as usize; + let mut out = Vec::new(); + while curr_end_offset < end { + out.push((curr_start_offset, curr_end_offset)); + + curr_start_im = curr_start_im - step; + curr_start_offset = converter.invert(curr_start_im) as usize; + + curr_end_im = curr_start_im - span; + curr_end_offset = converter.invert(curr_end_im) as usize; + } + if curr_start_offset < end { + out.push((curr_start_offset, end)); + } + out + }, + QuadWindowExpansionStrategy::UniformScan((span, step)) => { + let mut curr_start_offset = start; + let mut curr_end_offset = start + span; let mut out = Vec::new(); - while curr_end < end { - out.push((curr_start, curr_end)); - curr_start += step; - curr_end += step; + + while curr_end_offset < end { + out.push((curr_start_offset, curr_end_offset)); + curr_start_offset += step; + curr_end_offset += step; } - if curr_start < end { - out.push((curr_start, end)); + if curr_start_offset < end { + out.push((curr_start_offset, end)); } out }, diff --git a/src/io/readers/spectrum_reader.rs b/src/io/readers/spectrum_reader.rs index 7f905cc..3e5a533 100644 --- a/src/io/readers/spectrum_reader.rs +++ b/src/io/readers/spectrum_reader.rs @@ -9,7 +9,7 @@ use tdf::{TDFSpectrumReader, TDFSpectrumReaderError}; use crate::ms_data::Spectrum; -use super::FrameWindowSplittingStrategy; +use super::FrameWindowSplittingConfiguration; pub struct SpectrumReader { spectrum_reader: Box, @@ -147,5 +147,5 @@ impl Default for SpectrumProcessingParams { #[derive(Debug, Default, Clone)] pub struct SpectrumReaderConfig { pub spectrum_processing_params: SpectrumProcessingParams, - pub frame_splitting_params: FrameWindowSplittingStrategy, + pub frame_splitting_params: FrameWindowSplittingConfiguration, } diff --git a/src/io/readers/spectrum_reader/tdf.rs b/src/io/readers/spectrum_reader/tdf.rs index 7f29748..f166dc4 100644 --- a/src/io/readers/spectrum_reader/tdf.rs +++ b/src/io/readers/spectrum_reader/tdf.rs @@ -45,11 +45,14 @@ impl TDFSpectrumReader { .with_config(config.frame_splitting_params) .finalize()?; let acquisition_type = frame_reader.get_acquisition(); + let splitting_strategy = config + .frame_splitting_params + .finalize(metadata.im_converter); let raw_spectrum_reader = RawSpectrumReader::new( &tdf_sql_reader, frame_reader, acquisition_type, - config.frame_splitting_params, + splitting_strategy, )?; let reader = Self { path: path_name.as_ref().to_path_buf(), diff --git a/src/io/readers/spectrum_reader/tdf/dia.rs b/src/io/readers/spectrum_reader/tdf/dia.rs index 3313acf..3dad26c 100644 --- a/src/io/readers/spectrum_reader/tdf/dia.rs +++ b/src/io/readers/spectrum_reader/tdf/dia.rs @@ -27,7 +27,7 @@ impl DIARawSpectrumReader { ) -> Result { let expanded_quadrupole_settings = QuadrupoleSettingsReader::from_splitting( - tdf_sql_reader.get_path(), + &tdf_sql_reader, splitting_strategy, )?; let reader = Self { diff --git a/tests/spectrum_readers.rs b/tests/spectrum_readers.rs index 8f6f198..6b27e8c 100644 --- a/tests/spectrum_readers.rs +++ b/tests/spectrum_readers.rs @@ -1,7 +1,7 @@ use std::path::Path; use timsrust::{ io::readers::{ - FrameWindowSplittingStrategy, QuadWindowExpansionStrategy, + FrameWindowSplittingConfiguration, QuadWindowExpansionConfiguration, SpectrumProcessingParams, SpectrumReader, SpectrumReaderConfig, }, ms_data::{Precursor, Spectrum}, @@ -151,8 +151,8 @@ fn test_dia_even() { .with_path(&file_path) .with_config(SpectrumReaderConfig { frame_splitting_params: - FrameWindowSplittingStrategy::Quadrupole( - QuadWindowExpansionStrategy::Even(i), + FrameWindowSplittingConfiguration::Quadrupole( + QuadWindowExpansionConfiguration::Even(i), ), spectrum_processing_params: SpectrumProcessingParams::default(), }) @@ -165,32 +165,79 @@ fn test_dia_even() { } #[test] -fn test_dia_uniform() { +fn test_dia_uniform_mobility() { let file_name = "dia_test.d"; let file_path = get_local_directory() .join(file_name) .to_str() .unwrap() .to_string(); - for i in [100, 200, 300] { + for i in [0.02, 0.05, 0.1] { let spectra = SpectrumReader::build() .with_path(&file_path) .with_config(SpectrumReaderConfig { - frame_splitting_params: FrameWindowSplittingStrategy::Window( - QuadWindowExpansionStrategy::Uniform((i, i)), - ), + frame_splitting_params: + FrameWindowSplittingConfiguration::Window( + QuadWindowExpansionConfiguration::UniformMobility(( + i, i, + )), + ), spectrum_processing_params: SpectrumProcessingParams::default(), }) .finalize() .unwrap() .get_all(); for f in spectra.iter() { - println!("{:?}", f.as_ref().unwrap().precursor); + println!("i={} -> {:?}", i, f.as_ref().unwrap().precursor); } - // Not all frames have scan windows from 0 to 709 ... so ... I need to think + // Not all frames have scan windows from 0.5 to 1.5 ... so ... I need to think // on how to express this in the test - // assert_eq!(frames.len(), 4 * ((709 / i) + 1)); - assert!(spectra.len() > (709 / i)); - assert!(spectra.len() < 3 * ((709 / i) + 1)); + assert!(spectra.len() >= (1.0 / i) as usize); + + // 4 frames, each split in 1.0/i chunks max, 1.0 is the IMS width of a frame + // but not all frames span windows in that range + assert!(spectra.len() < 4 * (1.0 / i) as usize,); + + // TODO make a more accurate test where we measure the differences in ion + // mobilities and see if they are within the expected range + } +} + +#[test] +fn test_dia_uniform_scans() { + let file_name = "dia_test.d"; + let file_path = get_local_directory() + .join(file_name) + .to_str() + .unwrap() + .to_string(); + for i in [20, 100, 200] { + let spectra = SpectrumReader::build() + .with_path(&file_path) + .with_config(SpectrumReaderConfig { + frame_splitting_params: + FrameWindowSplittingConfiguration::Window( + QuadWindowExpansionConfiguration::UniformScan((i, i)), + ), + spectrum_processing_params: SpectrumProcessingParams::default(), + }) + .finalize() + .unwrap() + .get_all(); + for f in spectra.iter() { + println!("i={} -> {:?}", i, f.as_ref().unwrap().precursor); + } + + // Since there are 709 scans in the test data ... we can expect + // the number of breaks to be (709 / i) + 1 ... if we had a single + // window that spanned the entire scan range. + // ... A more strict test would filter for each frame index and + // within each make sure the number matches the ratio ... here I am + // Just checking the overall number. + const NUM_FRAMES: usize = 4; + const NUM_SCANS: usize = 709; + + assert!(spectra.len() >= (NUM_SCANS / i) as usize + 1); + assert!(spectra.len() < NUM_FRAMES * (NUM_SCANS / i) as usize + 1); } }