diff --git a/crates/timsrust-tdf/src/frame_reader.rs b/crates/timsrust-tdf/src/frame_reader.rs index e3882a2..dc8aa77 100644 --- a/crates/timsrust-tdf/src/frame_reader.rs +++ b/crates/timsrust-tdf/src/frame_reader.rs @@ -2,7 +2,7 @@ pub(crate) mod compression1; pub(crate) mod compression2; pub(crate) mod frame_info_reader; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use timsrust_core::{FrameIons, utils::reader::Reader}; @@ -31,13 +31,19 @@ use super::{ struct TdfOffsetIonReader { blob_reader: B, offsets: HashMap, + empty_frames: HashSet, } impl TdfOffsetIonReader { - fn new(blob_reader: B, offsets: HashMap) -> Self { + fn new( + blob_reader: B, + offsets: HashMap, + empty_frames: HashSet, + ) -> Self { Self { blob_reader, offsets, + empty_frames, } } } @@ -54,6 +60,9 @@ where .get(&index) .copied() .ok_or(FrameReaderError::IndexOutOfBounds)?; + if self.empty_frames.contains(&index) { + return Ok(FrameIons::default()); + } self.blob_reader.get(offset).map_err(FrameReaderError::from) } } @@ -113,19 +122,24 @@ impl TdfFrameReader { ) -> Result { let info_reader = FrameInfoReader::new(&path)?; let offsets = info_reader.offsets_map(); + let empty_frames = info_reader.empty_frames(); let ion_reader = match compression_type { 1 => { let mut blob = TdfBlobReaderCompression1::new(path.to_timstof_path())?; blob.set_max_peaks_per_scan(max_peaks_per_scan); TdfIonReader::Compression1(TdfOffsetIonReader::new( - blob, offsets, + blob, + offsets, + empty_frames, )) }, 2 => { let blob = TdfBlobReader::new(path.to_timstof_path())?; TdfIonReader::Compression2(TdfOffsetIonReader::new( - blob, offsets, + blob, + offsets, + empty_frames, )) }, _ => { @@ -186,3 +200,50 @@ pub enum FrameReaderError { #[error("{0}")] CoreFrameReaderError(#[from] timsrust_core::FrameReaderError), } + +#[cfg(test)] +mod tests { + use std::{ + cell::Cell, + collections::{HashMap, HashSet}, + }; + + use timsrust_core::{FrameIons, utils::reader::Reader}; + + use super::{FrameReaderError, TdfOffsetIonReader}; + use crate::frame_reader::compression2::TdfBlobReaderError; + + struct FailingBlobReader(Cell); + + impl Reader for FailingBlobReader { + type Error = TdfBlobReaderError; + + fn get(&self, _offset: usize) -> Result { + self.0.set(self.0.get() + 1); + Err(TdfBlobReaderError::CorruptFrame) + } + } + + #[test] + fn zero_peak_frame_skips_the_binary_reader_without_masking_other_errors() { + let reader = TdfOffsetIonReader::new( + FailingBlobReader(Cell::new(0)), + HashMap::from([(1, 0), (2, 8)]), + HashSet::from([1]), + ); + + assert_eq!(reader.get(1).unwrap(), FrameIons::default()); + assert_eq!(reader.blob_reader.0.get(), 0); + assert!(matches!( + reader.get(2), + Err(FrameReaderError::TdfBlobReaderError( + TdfBlobReaderError::CorruptFrame + )) + )); + assert_eq!(reader.blob_reader.0.get(), 1); + assert!(matches!( + reader.get(3), + Err(FrameReaderError::IndexOutOfBounds) + )); + } +} diff --git a/crates/timsrust-tdf/src/frame_reader/frame_info_reader.rs b/crates/timsrust-tdf/src/frame_reader/frame_info_reader.rs index e800e3c..fcf716c 100644 --- a/crates/timsrust-tdf/src/frame_reader/frame_info_reader.rs +++ b/crates/timsrust-tdf/src/frame_reader/frame_info_reader.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use timsrust_core::{ AcquisitionType, FrameInfo, MSLevel, QuadrupoleSettings, @@ -17,6 +20,7 @@ use crate::{ pub struct FrameInfoReader { frame_infos: HashMap, offsets: HashMap, + empty_frames: HashSet, acquisition: AcquisitionType, } @@ -26,6 +30,11 @@ impl FrameInfoReader { ) -> Result { let tdf_sql_reader = SqlReader::open(&path)?; let sql_frames = SqlFrame::from_sql_reader(&tdf_sql_reader)?; + let empty_frames = sql_frames + .iter() + .filter(|frame| frame.peak_count == 0) + .map(|frame| frame.id) + .collect(); let acquisition = if sql_frames.iter().any(|x| x.msms_type == 8) { AcquisitionType::DDAPASEF } else if sql_frames.iter().any(|x| x.msms_type == 9) { @@ -94,6 +103,7 @@ impl FrameInfoReader { let reader = Self { frame_infos, offsets, + empty_frames, acquisition, }; Ok(reader) @@ -149,6 +159,10 @@ impl FrameInfoReader { ) -> std::collections::HashMap { self.offsets.clone() } + + pub(crate) fn empty_frames(&self) -> HashSet { + self.empty_frames.clone() + } } impl IndexedReader for FrameInfoReader {