1use std::collections::HashMap;
2use std::io::{Read, Seek, SeekFrom};
3
4use crate::error::{Error, Result};
5use crate::error_log::ErrorEntry;
6use crate::generic_data::{GenericDataHeader, GenericRecord, GenericValue};
7use crate::header::FileHeader;
8use crate::raw_file_info::RawFileInfo;
9use crate::run_header::RunHeader;
10use crate::scan_data::{
11 read_flat_peaks, read_scan_srm_v66, search_v63_transition, Peak, ScanDataPacket,
12};
13use crate::scan_event::{ScanEvent, ScanEventPreamble};
14use crate::scan_index::ScanIndexEntry;
15use crate::seq_row::SeqRow;
16
17pub(crate) struct BinaryReader<R> {
19 inner: R,
20 pos: u64,
21}
22
23impl<R: Read + Seek> BinaryReader<R> {
24 pub fn new(inner: R) -> Self {
25 Self { inner, pos: 0 }
26 }
27
28 pub fn into_inner(self) -> R {
29 self.inner
30 }
31
32 #[allow(dead_code)]
33 pub(crate) fn position(&self) -> u64 {
34 self.pos
35 }
36
37 pub fn seek_to(&mut self, offset: u64) -> Result<()> {
38 self.inner.seek(SeekFrom::Start(offset))?;
39 self.pos = offset;
40 Ok(())
41 }
42
43 pub fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>> {
44 let mut buf = vec![0u8; n];
45 self.inner.read_exact(&mut buf).map_err(|e| {
46 if e.kind() == std::io::ErrorKind::UnexpectedEof {
47 Error::UnexpectedEof {
48 offset: self.pos,
49 needed: n,
50 }
51 } else {
52 Error::Io(e)
53 }
54 })?;
55 self.pos += n as u64;
56 Ok(buf)
57 }
58
59 pub fn read_bytes_into(&mut self, buf: &mut [u8]) -> Result<()> {
60 let n = buf.len();
61 self.inner.read_exact(buf).map_err(|e| {
62 if e.kind() == std::io::ErrorKind::UnexpectedEof {
63 Error::UnexpectedEof {
64 offset: self.pos,
65 needed: n,
66 }
67 } else {
68 Error::Io(e)
69 }
70 })?;
71 self.pos += n as u64;
72 Ok(())
73 }
74
75 pub fn skip(&mut self, n: usize) -> Result<()> {
76 self.inner.seek(SeekFrom::Current(n as i64))?;
77 self.pos += n as u64;
78 Ok(())
79 }
80
81 pub fn length(&mut self) -> Result<u64> {
82 let cur = self.pos;
83 let end = self.inner.seek(SeekFrom::End(0))?;
84 self.inner.seek(SeekFrom::Start(cur))?;
85 self.pos = cur;
86 Ok(end)
87 }
88
89 pub fn read_u8(&mut self) -> Result<u8> {
90 let mut buf = [0u8; 1];
91 self.read_bytes_into(&mut buf)?;
92 Ok(buf[0])
93 }
94
95 pub fn read_u16(&mut self) -> Result<u16> {
96 let mut buf = [0u8; 2];
97 self.read_bytes_into(&mut buf)?;
98 Ok(u16::from_le_bytes(buf))
99 }
100
101 pub fn read_i16(&mut self) -> Result<i16> {
102 let mut buf = [0u8; 2];
103 self.read_bytes_into(&mut buf)?;
104 Ok(i16::from_le_bytes(buf))
105 }
106
107 pub fn read_u32(&mut self) -> Result<u32> {
108 let mut buf = [0u8; 4];
109 self.read_bytes_into(&mut buf)?;
110 Ok(u32::from_le_bytes(buf))
111 }
112
113 pub fn read_i32(&mut self) -> Result<i32> {
114 let mut buf = [0u8; 4];
115 self.read_bytes_into(&mut buf)?;
116 Ok(i32::from_le_bytes(buf))
117 }
118
119 pub fn read_u64(&mut self) -> Result<u64> {
120 let mut buf = [0u8; 8];
121 self.read_bytes_into(&mut buf)?;
122 Ok(u64::from_le_bytes(buf))
123 }
124
125 pub fn read_f32(&mut self) -> Result<f32> {
126 let mut buf = [0u8; 4];
127 self.read_bytes_into(&mut buf)?;
128 Ok(f32::from_le_bytes(buf))
129 }
130
131 pub fn read_f64(&mut self) -> Result<f64> {
132 let mut buf = [0u8; 8];
133 self.read_bytes_into(&mut buf)?;
134 Ok(f64::from_le_bytes(buf))
135 }
136
137 pub fn read_i8(&mut self) -> Result<i8> {
138 let mut buf = [0u8; 1];
139 self.read_bytes_into(&mut buf)?;
140 Ok(buf[0] as i8)
141 }
142
143 pub fn read_utf16_fixed(&mut self, byte_len: usize) -> Result<String> {
145 let pos = self.pos;
146 let raw = self.read_bytes(byte_len)?;
147 if byte_len % 2 != 0 {
148 return Err(Error::InvalidUtf16(pos));
149 }
150 let units: Vec<u16> = raw
151 .chunks_exact(2)
152 .map(|c| u16::from_le_bytes([c[0], c[1]]))
153 .collect();
154 let end = units.iter().position(|&u| u == 0).unwrap_or(units.len());
156 String::from_utf16(&units[..end]).map_err(|_| Error::InvalidUtf16(pos))
157 }
158
159 pub fn read_pascal_string(&mut self) -> Result<String> {
161 let pos = self.pos;
162 let char_count = self.read_u32()? as usize;
163 if char_count == 0 {
164 return Ok(String::new());
165 }
166 let byte_len = char_count.checked_mul(2).ok_or(Error::InvalidUtf16(pos))?;
167 let raw = self.read_bytes(byte_len)?;
168 let units: Vec<u16> = raw
169 .chunks_exact(2)
170 .map(|c| u16::from_le_bytes([c[0], c[1]]))
171 .collect();
172 let end = units.iter().position(|&u| u == 0).unwrap_or(units.len());
174 String::from_utf16(&units[..end]).map_err(|_| Error::InvalidUtf16(pos))
175 }
176
177 pub fn read_windows_filetime(&mut self) -> Result<f64> {
179 let ft = self.read_u64()?;
180 if ft == 0 {
181 return Ok(0.0);
182 }
183 Ok((ft as f64 / 10_000_000.0) - 11_644_473_600.0)
184 }
185}
186
187pub struct RawFileReader {
189 pub header: FileHeader,
190 pub seq_row: SeqRow,
191 pub raw_file_info: RawFileInfo,
192 pub run_header: RunHeader,
193 pub scan_index: Vec<ScanIndexEntry>,
194 pub scan_events: Vec<ScanEvent>,
195 pub scan_parameters_header: GenericDataHeader,
196 pub scan_parameters: Vec<GenericRecord>,
197 pub error_log: Vec<ErrorEntry>,
198 pub inst_log_header: GenericDataHeader,
200 pub inst_log: Vec<GenericRecord>,
201 pub version: u32,
203 pub num_scans: u32,
205 pub data_addr: u64,
207 pub flat_peaks: bool,
209 pub scan_format: crate::scan_format::ScanDataFormat,
211 pub device_family: crate::device::DeviceFamily,
213 pub instrument_model: Option<&'static str>,
217 pub srm_q1_by_event: HashMap<u16, f64>,
222 pub srm_q3_windows: HashMap<u16, Vec<(f32, f32)>>,
227 pub srm_ce_by_event: HashMap<u16, f64>,
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum ControllerType {
240 Ms,
241 Analog,
242 Adc,
243 Pda,
244 Uv,
245 Other,
246}
247
248impl ControllerType {
249 fn from_nsegs_ntrailer(ntrailer: u32, nsegs: u32) -> Self {
250 if ntrailer > 0 || nsegs > 1 {
255 Self::Ms
256 } else {
257 Self::Other
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
264pub struct ControllerInfo {
265 pub index: usize,
267 pub run_header_addr: u64,
269 pub is_ms_controller: bool,
271 pub controller_type: ControllerType,
273 pub first_scan: u32,
275 pub last_scan: u32,
277 pub start_time: f64,
279 pub end_time: f64,
281}
282
283impl RawFileReader {
284 pub fn open<R: Read + Seek>(source: R) -> Result<Self> {
286 let mut r = BinaryReader::new(source);
287
288 let header = FileHeader::read(&mut r)?;
290 let version = header.version;
291
292 let seq_row = SeqRow::read(&mut r, version)?;
294
295 let _as_preamble = r.read_bytes(24)?; let _as_text = r.read_pascal_string()?;
298
299 let raw_file_info = RawFileInfo::read(&mut r, version)?;
301
302 let data_addr = raw_file_info.preamble.data_addr;
304
305 let run_header = {
310 let addrs = &raw_file_info.preamble.run_header_addrs;
311 let mut chosen = None;
312 for &addr in addrs {
313 if addr == 0 {
314 continue;
315 }
316 r.seek_to(addr)?;
317 let rh = RunHeader::read(&mut r, version)?;
318 let is_ms = if version >= 64 {
325 rh.ntrailer > 0 || rh.data_addr == data_addr
326 } else {
327 rh.sample_info.last_scan_number >= rh.sample_info.first_scan_number
328 && rh.nsegs > 0
329 };
330 if is_ms {
331 chosen = Some(rh);
332 break;
333 }
334 }
335 match chosen {
337 Some(rh) => rh,
338 None => {
339 r.seek_to(addrs[0])?;
340 RunHeader::read(&mut r, version)?
341 }
342 }
343 };
344
345 let first_scan = run_header.sample_info.first_scan_number;
346 let last_scan = run_header.sample_info.last_scan_number;
347
348 let num_scans = if last_scan >= first_scan {
349 last_scan - first_scan + 1
350 } else {
351 0
352 };
353
354 r.seek_to(run_header.scan_index_addr)?;
356 let mut scan_index = Vec::with_capacity(num_scans as usize);
357 for _ in 0..num_scans {
358 scan_index.push(ScanIndexEntry::read(&mut r, version)?);
359 }
360
361 r.seek_to(run_header.scan_trailer_addr)?;
363 let n_events = if version >= 64 {
364 let _preamble = r.read_u32()?;
366 run_header.ntrailer
367 } else {
368 r.read_u32()?
369 };
370 let preamble_size = ScanEventPreamble::size_for_version(version);
384 let (v66_body_primary, v66_body_dependent): (usize, usize) =
385 if version >= 66 && n_events > 0 {
386 let stream_bytes = run_header
387 .scan_params_addr
388 .saturating_sub(run_header.scan_trailer_addr)
389 .saturating_sub(4);
390 let remainder = stream_bytes % n_events as u64;
391 if remainder == 0 {
392 let body = (stream_bytes / n_events as u64) as usize;
394 let body = body.saturating_sub(preamble_size);
395 (body, body)
396 } else {
397 const PRIMARY_EVENT: u64 = 232;
400 const DEPENDENT_EVENT: u64 = 344;
401 let gap = DEPENDENT_EVENT - PRIMARY_EVENT;
402 let n = n_events as u64;
403 let n_primary_numerator = n
407 .saturating_mul(DEPENDENT_EVENT)
408 .saturating_sub(stream_bytes);
409 if n_primary_numerator % gap == 0 {
410 let n_primary = n_primary_numerator / gap;
411 let n_dependent = n.saturating_sub(n_primary);
412 let total_check = n_primary * PRIMARY_EVENT + n_dependent * DEPENDENT_EVENT;
413 if total_check == stream_bytes {
414 (
416 (PRIMARY_EVENT as usize).saturating_sub(preamble_size),
417 (DEPENDENT_EVENT as usize).saturating_sub(preamble_size),
418 )
419 } else {
420 let body = ((stream_bytes / n) as usize).saturating_sub(preamble_size);
422 (body, body)
423 }
424 } else {
425 let body = ((stream_bytes / n) as usize).saturating_sub(preamble_size);
427 (body, body)
428 }
429 }
430 } else {
431 (0, 0)
432 };
433 let mut scan_events = Vec::with_capacity(n_events as usize);
434 for _ in 0..n_events {
435 scan_events.push(ScanEvent::read(
436 &mut r,
437 version,
438 v66_body_primary,
439 v66_body_dependent,
440 )?);
441 }
442
443 let n_errors = run_header.sample_info.error_log_length;
445 let error_log = if n_errors > 0 {
446 r.seek_to(run_header.error_log_addr)?;
447 if version >= 64 {
448 let _preamble = r.read_u32()?;
449 }
450 let mut log = Vec::with_capacity(n_errors as usize);
451 for _ in 0..n_errors {
452 log.push(ErrorEntry::read(&mut r)?);
453 }
454 log
455 } else {
456 r.seek_to(run_header.error_log_addr)?;
458 Vec::new()
459 };
460 let after_error_log = r.position();
466
467 let (scan_parameters_header, scan_parameters) = if version >= 64 {
474 let scan_distance = run_header.scan_trailer_addr.saturating_sub(after_error_log);
477 let file_size = r.length()?;
480 let tail = file_size.saturating_sub(run_header.scan_params_addr);
481 let expected_record_size = if num_scans > 0 && tail > 0 {
482 let per_scan = tail / num_scans as u64;
483 if per_scan >= 4 {
484 Some(per_scan as usize)
485 } else {
486 None
487 }
488 } else {
489 None
490 };
491 match GenericDataHeader::find_forward(&mut r, scan_distance, expected_record_size)? {
492 Some(hdr) => {
493 r.seek_to(run_header.scan_params_addr)?;
495 let mut params = Vec::with_capacity(num_scans as usize);
496 for _ in 0..num_scans {
497 params.push(GenericRecord::read(&mut r, &hdr)?);
498 }
499 (hdr, params)
500 }
501 None => (GenericDataHeader { fields: Vec::new() }, Vec::new()),
502 }
503 } else {
504 (GenericDataHeader { fields: Vec::new() }, Vec::new())
505 };
506
507 let (inst_log_header, inst_log) = if version >= 64 {
509 r.seek_to(run_header.inst_log_addr)?;
510 match GenericDataHeader::try_read(&mut r)? {
511 Some(hdr) => {
512 let n_inst = run_header.sample_info.inst_log_length;
513 let mut log = Vec::with_capacity(n_inst as usize);
514 for _ in 0..n_inst {
515 log.push(GenericRecord::read(&mut r, &hdr)?);
516 }
517 (hdr, log)
518 }
519 None => (GenericDataHeader { fields: Vec::new() }, Vec::new()),
520 }
521 } else {
522 (GenericDataHeader { fields: Vec::new() }, Vec::new())
523 };
524
525 let flat_peaks = run_header.ntrailer == 0
531 || scan_index
532 .first()
533 .map(|e| e.data_size < 100)
534 .unwrap_or(false);
535
536 let scan_format = crate::scan_format::ScanDataFormat::detect(version, flat_peaks);
538 let first_analyzer = scan_events.first().and_then(|e| e.preamble.analyzer());
539
540 let scan_window_cap = if flat_peaks { data_addr } else { 64 * 1024u64 };
544 let window_len = scan_window_cap.min(data_addr);
545 let metadata_window = if window_len > 0 {
546 r.seek_to(0)?;
547 r.read_bytes(window_len as usize).unwrap_or_default()
548 } else {
549 Vec::new()
550 };
551 let mut source = r.into_inner();
554 let detected = crate::device::DeviceFamily::detect_instrument(
555 &metadata_window,
556 &header.audit_start.tag2,
557 &seq_row.inst_method,
558 first_analyzer,
559 );
560 let device_family = detected.family;
561 let instrument_model = detected.model;
562
563 let (srm_q1_by_event, srm_q3_windows, srm_ce_by_event) = {
578 use crate::scan_format::ScanDataFormat;
579 match (flat_peaks, scan_format) {
580 (true, ScanDataFormat::FlatV66) if metadata_window.len() >= 24 => {
581 let mut event_q3_hi: HashMap<u16, f64> = HashMap::new();
583 for entry in &scan_index {
584 if entry.high_mz > 50.0 && entry.high_mz < 2000.0 {
585 event_q3_hi.entry(entry.scan_event).or_insert(entry.high_mz);
586 }
587 }
588 let data = &metadata_window;
589 let mut q1_map: HashMap<u16, f64> = HashMap::new();
590 'outer_v66: for (&event, &q3_hi_target) in &event_q3_hi {
591 let end = data.len().saturating_sub(8);
592 for i in 16..end {
593 let hi = crate::bytes::read_f64_le(data, i)?;
594 if (hi - q3_hi_target).abs() < 0.002 {
595 let lo = crate::bytes::read_f64_le(data, i - 8)?;
596 if hi > lo && (hi - lo) < 0.1 {
597 let q1 = crate::bytes::read_f64_le(data, i - 16)?;
598 if q1 > 50.0 && q1 < 3000.0 {
599 q1_map.insert(event, q1);
600 continue 'outer_v66;
601 }
602 }
603 }
604 }
605 }
606 let mut seen: HashMap<u16, bool> = HashMap::new();
608 let mut q3_map: HashMap<u16, Vec<(f32, f32)>> = HashMap::new();
609 for entry in &scan_index {
610 if seen.contains_key(&entry.scan_event) {
611 continue;
612 }
613 seen.insert(entry.scan_event, true);
614 if let Ok(windows) = crate::scan_data::read_scan_srm_v66_windows(
615 &mut source,
616 data_addr,
617 entry.offset,
618 ) {
619 if !windows.is_empty() {
620 q3_map.insert(entry.scan_event, windows);
621 }
622 }
623 }
624 (q1_map, q3_map, HashMap::new())
625 }
626 (true, ScanDataFormat::FlatV63) => {
627 let mut seen: HashMap<u16, bool> = HashMap::new();
633 let mut q1_map: HashMap<u16, f64> = HashMap::new();
634 let mut q3_map: HashMap<u16, Vec<(f32, f32)>> = HashMap::new();
635 let mut ce_map: HashMap<u16, f64> = HashMap::new();
636 let data = &metadata_window;
637 for entry in &scan_index {
638 let ev = entry.scan_event;
639 if seen.contains_key(&ev) {
640 continue;
641 }
642 seen.insert(ev, true);
643 let peaks = match read_flat_peaks(
644 &mut source,
645 data_addr,
646 entry.offset,
647 entry.data_size,
648 ) {
649 Ok(p) if !p.is_empty() => p,
650 _ => continue,
651 };
652 if let Some((q1, q3w, ce)) = search_v63_transition(data, peaks[0].mz) {
654 q1_map.insert(ev, q1);
655 ce_map.insert(ev, ce);
656 let half = (q3w / 2.0) as f32;
657 let windows: Vec<(f32, f32)> = peaks
658 .iter()
659 .map(|p| (p.mz as f32 - half, p.mz as f32 + half))
660 .collect();
661 q3_map.insert(ev, windows);
662 }
663 }
664 (q1_map, q3_map, ce_map)
665 }
666 _ => (HashMap::new(), HashMap::new(), HashMap::new()),
667 }
668 };
669
670 Ok(Self {
671 header,
672 seq_row,
673 raw_file_info,
674 run_header,
675 scan_index,
676 scan_events,
677 scan_parameters_header,
678 scan_parameters,
679 error_log,
680 inst_log_header,
681 inst_log,
682 version,
683 num_scans,
684 data_addr,
685 flat_peaks,
686 scan_format,
687 device_family,
688 instrument_model,
689 srm_q1_by_event,
690 srm_q3_windows,
691 srm_ce_by_event,
692 })
693 }
694
695 pub fn open_path(path: impl AsRef<std::path::Path>) -> Result<Self> {
697 let file = std::fs::File::open(path)?;
698 let reader = std::io::BufReader::new(file);
699 Self::open(reader)
700 }
701
702 pub fn controllers<R: Read + Seek>(&self, source: &mut R) -> Result<Vec<ControllerInfo>> {
713 let mut r = BinaryReader::new(source);
714 let addrs = &self.raw_file_info.preamble.run_header_addrs;
715 let mut infos = Vec::with_capacity(addrs.len());
716 for (i, &addr) in addrs.iter().enumerate() {
717 if addr == 0 {
718 continue;
719 }
720 r.seek_to(addr)?;
721 let rh = RunHeader::read(&mut r, self.version)?;
722 let is_ms = if self.version >= 64 {
723 rh.ntrailer > 0 || rh.data_addr == self.data_addr
724 } else {
725 rh.nsegs > 0
726 };
727 let ct = if is_ms {
728 ControllerType::Ms
729 } else {
730 ControllerType::from_nsegs_ntrailer(rh.ntrailer, rh.nsegs)
731 };
732 infos.push(ControllerInfo {
733 index: i,
734 run_header_addr: addr,
735 is_ms_controller: is_ms,
736 controller_type: ct,
737 first_scan: rh.sample_info.first_scan_number,
738 last_scan: rh.sample_info.last_scan_number,
739 start_time: rh.sample_info.start_time,
740 end_time: rh.sample_info.end_time,
741 });
742 }
743 Ok(infos)
744 }
745
746 pub fn read_scan<R: Read + Seek>(
748 &self,
749 source: &mut R,
750 scan_number: u32,
751 ) -> Result<ScanDataPacket> {
752 let idx = (scan_number - self.run_header.sample_info.first_scan_number) as usize;
753 if idx >= self.scan_index.len() {
754 return Err(Error::AddressOutOfRange(scan_number as u64));
755 }
756 let entry = &self.scan_index[idx];
757 let abs_offset = self.data_addr + entry.offset;
758 source.seek(SeekFrom::Start(abs_offset))?;
759 let mut r = BinaryReader::new(source);
760 ScanDataPacket::read(&mut r)
761 }
762
763 pub fn read_scan_labels<R: Read + Seek>(
769 &self,
770 source: &mut R,
771 scan_number: u32,
772 ) -> Result<ScanDataPacket> {
773 use crate::scan_format::ScanDataFormat;
774 if self.scan_format != ScanDataFormat::PacketHeader {
775 return Err(Error::UnsupportedOperation(
776 "centroid_labels / read_scan_labels requires a PacketHeader file (Orbitrap/ion-trap); TSQ/SRM files carry no FT label data",
777 ));
778 }
779 let idx = (scan_number - self.run_header.sample_info.first_scan_number) as usize;
780 if idx >= self.scan_index.len() {
781 return Err(Error::AddressOutOfRange(scan_number as u64));
782 }
783 let entry = &self.scan_index[idx];
784 let abs_offset = self.data_addr + entry.offset;
785 source.seek(SeekFrom::Start(abs_offset))?;
786 let mut r = BinaryReader::new(source);
787 ScanDataPacket::read_skip_profile(&mut r)
788 }
789
790 pub fn read_scan_flat<R: Read + Seek>(
795 &self,
796 source: &mut R,
797 scan_number: u32,
798 ) -> Result<Vec<Peak>> {
799 let idx = (scan_number - self.run_header.sample_info.first_scan_number) as usize;
800 if idx >= self.scan_index.len() {
801 return Err(Error::AddressOutOfRange(scan_number as u64));
802 }
803 let entry = &self.scan_index[idx];
804 read_flat_peaks(source, self.data_addr, entry.offset, entry.data_size)
805 }
806
807 pub fn read_scan_srm_v66<R: Read + Seek>(
813 &self,
814 source: &mut R,
815 scan_number: u32,
816 ) -> Result<Vec<Peak>> {
817 let idx = (scan_number - self.run_header.sample_info.first_scan_number) as usize;
818 if idx >= self.scan_index.len() {
819 return Err(Error::AddressOutOfRange(scan_number as u64));
820 }
821 let entry = &self.scan_index[idx];
822 read_scan_srm_v66(source, self.data_addr, entry.offset, entry.data_size)
823 }
824
825 pub fn read_scan_peaks<R: Read + Seek>(
837 &self,
838 source: &mut R,
839 scan_number: u32,
840 ) -> Result<Vec<Peak>> {
841 use crate::scan_format::ScanDataFormat;
842 match self.scan_format {
843 ScanDataFormat::PacketHeader => {
844 let pkt = self.read_scan(source, scan_number)?;
845 Ok(pkt.peaks)
846 }
847 ScanDataFormat::FlatV63 => self.read_scan_flat(source, scan_number),
848 ScanDataFormat::FlatV66 => self.read_scan_srm_v66(source, scan_number),
849 }
850 }
851
852 pub fn read_peaks_only<R: Read + Seek>(
861 &self,
862 source: &mut R,
863 scan_number: u32,
864 ) -> Result<Vec<Peak>> {
865 use crate::scan_format::ScanDataFormat;
866 match self.scan_format {
867 ScanDataFormat::PacketHeader => {
868 let idx = (scan_number - self.run_header.sample_info.first_scan_number) as usize;
869 if idx >= self.scan_index.len() {
870 return Err(Error::AddressOutOfRange(scan_number as u64));
871 }
872 let entry = &self.scan_index[idx];
873 let abs_offset = self.data_addr + entry.offset;
874 source.seek(SeekFrom::Start(abs_offset))?;
875 let mut r = BinaryReader::new(source);
876 ScanDataPacket::read_peaks_only(&mut r)
877 }
878 ScanDataFormat::FlatV63 => self.read_scan_flat(source, scan_number),
879 ScanDataFormat::FlatV66 => self.read_scan_srm_v66(source, scan_number),
880 }
881 }
882
883 pub fn scan_parameters(&self, scan_number: u32) -> Option<&GenericRecord> {
888 let first = self.run_header.sample_info.first_scan_number;
889 let idx = scan_number.checked_sub(first)? as usize;
890 self.scan_parameters.get(idx)
891 }
892
893 pub fn scan_params(&self, scan_number: u32) -> Option<ScanParams<'_>> {
899 self.scan_parameters(scan_number).map(ScanParams)
900 }
901
902 pub fn inst_log_record(&self, scan_number: u32) -> Option<&GenericRecord> {
908 let first = self.run_header.sample_info.first_scan_number;
909 let idx = scan_number.checked_sub(first)? as usize;
910 self.inst_log.get(idx)
911 }
912
913 pub fn status_log_entry(&self, scan_number: u32) -> Option<StatusLogEntry<'_>> {
918 self.inst_log_record(scan_number).map(StatusLogEntry)
919 }
920
921 pub fn scan_filter(&self, scan_number: u32) -> Option<String> {
928 let first = self.run_header.sample_info.first_scan_number;
929 let idx = scan_number.checked_sub(first)? as usize;
930 let entry = self.scan_index.get(idx)?;
931
932 if self.flat_peaks {
935 let q1 = self.srm_q1_by_event.get(&entry.scan_event).copied()?;
936 let windows = self.srm_q3_windows.get(&entry.scan_event)?;
937 use crate::scan_format::ScanDataFormat;
940 let ionization = match self.scan_format {
941 ScanDataFormat::FlatV63 => "NSI",
942 _ => "ESI",
943 };
944 let ce_part = if self.scan_format == ScanDataFormat::FlatV63 {
945 self.srm_ce_by_event
946 .get(&entry.scan_event)
947 .map(|&ce| format!("@cid{:.2}", ce))
948 .unwrap_or_default()
949 } else {
950 String::new()
951 };
952 let mut s = format!("+ c {} SRM ms2 {:.3}{}", ionization, q1, ce_part);
954 if !windows.is_empty() {
955 s.push(' ');
956 s.push('[');
957 for (i, (lo, hi)) in windows.iter().enumerate() {
958 if i > 0 {
959 s.push_str(", ");
960 }
961 s.push_str(&format!("{:.3}-{:.3}", lo, hi));
962 }
963 s.push(']');
964 }
965 return Some(s);
966 }
967
968 let event = self.scan_events.get(idx)?;
969 let params = self.scan_params(scan_number);
972 let precursor = params.as_ref().and_then(|p| p.monoisotopic_mz());
973 let energy = params.as_ref().and_then(|p| p.activation_energy());
974 let supplemental = params
975 .as_ref()
976 .and_then(|p| p.supplemental_activation_energy());
977 Some(crate::scan_filter::build_filter(
978 event,
979 entry,
980 precursor,
981 energy,
982 supplemental,
983 ))
984 }
985
986 pub fn retention_times(&self) -> Vec<f64> {
991 self.scan_index.iter().map(|e| e.start_time).collect()
992 }
993
994 pub fn tic_chromatogram(&self) -> Vec<(f64, f64)> {
996 self.scan_index
997 .iter()
998 .map(|e| (e.start_time, e.total_current))
999 .collect()
1000 }
1001
1002 pub fn bpc_chromatogram(&self) -> Vec<(f64, f64, f64)> {
1004 self.scan_index
1005 .iter()
1006 .map(|e| (e.start_time, e.base_intensity, e.base_mz))
1007 .collect()
1008 }
1009
1010 pub fn instrument_method_name(&self) -> &str {
1017 &self.seq_row.inst_method
1018 }
1019
1020 pub fn instrument_method_text<R: Read + Seek>(&self, source: &mut R) -> Option<String> {
1034 if !self.raw_file_info.preamble.method_file_present {
1035 return None;
1036 }
1037 const MAX_WINDOW: u64 = 512 * 1024;
1040 let window_len = MAX_WINDOW.min(self.data_addr) as usize;
1041 if window_len < 4 {
1042 return None;
1043 }
1044 source.seek(std::io::SeekFrom::Start(0)).ok()?;
1045 let mut buf = vec![0u8; window_len];
1046 source.read_exact(&mut buf).ok()?;
1047
1048 extract_utf16le_text(&buf, 256)
1052 }
1053}
1054
1055fn extract_utf16le_text(buf: &[u8], min_chars: usize) -> Option<String> {
1059 if buf.len() < 2 {
1060 return None;
1061 }
1062 let mut best: Option<String> = None;
1063 let mut best_len = 0usize;
1064
1065 for alignment in 0..2usize {
1067 let start = alignment;
1068 let usable = buf.len().saturating_sub(start);
1069 let n_units = usable / 2;
1070 if n_units < min_chars {
1071 continue;
1072 }
1073
1074 let mut run_start = 0usize;
1075 let mut run_chars: Vec<u16> = Vec::with_capacity(min_chars);
1076
1077 let flush = |run_chars: &Vec<u16>,
1078 run_start: usize,
1079 best: &mut Option<String>,
1080 best_len: &mut usize| {
1081 if run_chars.len() >= min_chars {
1082 if let Ok(s) = String::from_utf16(run_chars) {
1083 let _ = run_start; if run_chars.len() > *best_len {
1085 *best_len = run_chars.len();
1086 *best = Some(s);
1087 }
1088 }
1089 }
1090 };
1091
1092 for i in 0..n_units {
1093 let off = start + i * 2;
1094 let u = u16::from_le_bytes([buf[off], buf[off + 1]]);
1095 let is_ok = matches!(u, 0x0009 | 0x000A | 0x000D | 0x0020..=0xFFFD);
1096 if is_ok {
1097 run_chars.push(u);
1098 } else {
1099 flush(&run_chars, run_start, &mut best, &mut best_len);
1100 run_start = i + 1;
1101 run_chars.clear();
1102 }
1103 }
1104 flush(&run_chars, run_start, &mut best, &mut best_len);
1105 }
1106 best
1107}
1108
1109pub struct ScanParams<'a>(pub &'a GenericRecord);
1128
1129impl<'a> ScanParams<'a> {
1130 #[inline]
1132 pub fn record(&self) -> &GenericRecord {
1133 self.0
1134 }
1135
1136 pub fn ion_injection_time_ms(&self) -> Option<f64> {
1141 self.0
1143 .get_f64("Ion Injection Time (ms):")
1144 .or_else(|| self.0.get_f64("Ion Inject Time (ms):"))
1145 }
1146
1147 pub fn charge_state(&self) -> Option<i32> {
1149 self.0
1150 .get_i32("Charge State:")
1151 .or_else(|| {
1153 self.0.get("Charge State:").and_then(|v| match v {
1154 GenericValue::UInt8(n) => Some(*n as i32),
1155 _ => None,
1156 })
1157 })
1158 }
1159
1160 pub fn monoisotopic_mz(&self) -> Option<f64> {
1169 let v = self
1170 .0
1171 .get_f64("Monoisotopic M/Z:")
1172 .or_else(|| self.0.get_f64("MS2 Isolation M/Z:"))
1173 .or_else(|| self.0.get_f64("Isolation Center M/Z:"))
1174 .or_else(|| self.0.get_f64("Precursor M/Z:"))?;
1175 if v > 0.0 {
1176 Some(v)
1177 } else {
1178 None
1179 }
1180 }
1181
1182 pub fn micro_scan_count(&self) -> Option<i32> {
1184 self.0.get_i32("Micro Scan Count:")
1185 }
1186
1187 pub fn master_scan_number(&self) -> Option<i32> {
1190 self.0
1191 .get_i32("Master Scan Number:")
1192 .or_else(|| self.0.get_i32("Master Index:"))
1193 }
1194
1195 pub fn ft_resolution(&self) -> Option<i32> {
1197 self.orbitrap_resolution()
1198 }
1199
1200 pub fn number_of_lm_found(&self) -> Option<i32> {
1202 self.number_of_lock_masses()
1203 }
1204
1205 pub fn lm_correction_ppm(&self) -> Option<f64> {
1207 self.lock_mass_correction_ppm()
1208 }
1209
1210 pub fn agc_target(&self) -> Option<i32> {
1212 self.0.get_i32("AGC Target:")
1213 }
1214
1215 pub fn agc_enabled(&self) -> Option<bool> {
1217 match self.0.get("AGC:")? {
1218 GenericValue::Bool(b) => Some(*b),
1219 GenericValue::String(s) => Some(s.to_ascii_lowercase().contains("on")),
1220 _ => None,
1221 }
1222 }
1223
1224 pub fn elapsed_scan_time_s(&self) -> Option<f64> {
1226 self.0.get_f64("Elapsed Scan Time (sec):")
1227 }
1228
1229 pub fn max_ion_time_ms(&self) -> Option<f64> {
1231 self.0.get_f64("Max. Ion Time (ms):")
1232 }
1233
1234 pub fn isolation_width_mz(&self) -> Option<f64> {
1239 self.0
1240 .get_f64("MS2 Isolation Width:")
1241 .or_else(|| self.0.get_f64("MSn Isolation Width:"))
1242 .or_else(|| self.0.get_f64("Isolation Width (M/Z):"))
1243 .or_else(|| self.0.get_f64("MS2 Isolation Width (M/Z):"))
1244 }
1245
1246 pub fn isolation_target_mz(&self) -> Option<f64> {
1252 self.0
1253 .get_f64("MS2 Isolation Offset:")
1254 .or_else(|| self.0.get_f64("Target M/Z:"))
1255 }
1256
1257 pub fn activation_energy(&self) -> Option<f64> {
1272 for label in &["HCD Energy:", "HCD Energy V:", "CE:"] {
1275 if let Some(s) = self.0.get_string(label) {
1276 if let Ok(v) = s.trim().trim_end_matches('%').parse::<f64>() {
1277 if v > 0.0 {
1278 return Some(v);
1279 }
1280 }
1281 }
1282 }
1283 if let Some(v) = self
1284 .0
1285 .get_f64("Normalized Collision Energy:")
1286 .filter(|&v| v > 0.0)
1287 {
1288 return Some(v);
1289 }
1290 if let Some(v) = self.0.get_f64("HCD Energy (eV):").filter(|&v| v > 0.0) {
1292 return Some(v);
1293 }
1294 if let Some(v) = self.0.get_f64("HCD Energy eV:").filter(|&v| v > 0.0) {
1295 return Some(v);
1296 }
1297 self.0
1298 .get_f64("Collision Energy (eV):")
1299 .filter(|&v| v > 0.0)
1300 }
1301
1302 pub fn activation_energy_is_nce(&self) -> bool {
1309 for label in &["HCD Energy:", "HCD Energy V:", "CE:"] {
1311 if let Some(s) = self.0.get_string(label) {
1312 if let Ok(v) = s.trim().trim_end_matches('%').parse::<f64>() {
1313 if v > 0.0 {
1314 return true;
1315 }
1316 }
1317 }
1318 }
1319 self.0
1320 .get_f64("Normalized Collision Energy:")
1321 .filter(|&v| v > 0.0)
1322 .is_some()
1323 }
1324
1325 pub fn supplemental_activation_energy(&self) -> Option<f64> {
1329 if let Some(v) = self.0.get_f64("Supplemental Activation CE:") {
1330 return Some(v);
1331 }
1332 if let Some(s) = self.0.get_string("Supplemental Activation:") {
1333 return s.trim().trim_end_matches('%').parse::<f64>().ok();
1334 }
1335 None
1336 }
1337
1338 pub fn possible_charge_states(&self) -> Option<Vec<u32>> {
1344 if let Some(s) = self.0.get_string("Possible Charge States:") {
1346 let v: Vec<u32> = s
1347 .split_whitespace()
1348 .filter_map(|t| t.parse::<u32>().ok())
1349 .collect();
1350 if !v.is_empty() {
1351 return Some(v);
1352 }
1353 }
1354 if let Some(c) = self.charge_state() {
1356 if c > 0 {
1357 return Some(vec![c as u32]);
1358 }
1359 }
1360 None
1361 }
1362
1363 pub fn faims_cv(&self) -> Option<f64> {
1365 self.0
1366 .get_f64("FAIMS CV:")
1367 .or_else(|| self.0.get_f32("FAIMS CV:").map(f64::from))
1368 }
1369
1370 pub fn faims_voltage_on(&self) -> Option<bool> {
1372 match self.0.get("FAIMS Voltage On:")? {
1373 GenericValue::Bool(b) => Some(*b),
1374 GenericValue::String(s) => Some(s.to_ascii_lowercase().contains("on")),
1375 _ => None,
1376 }
1377 }
1378
1379 pub fn s_lens_rf_level(&self) -> Option<f64> {
1381 self.0.get_f64("S-Lens RF Level:")
1382 }
1383
1384 pub fn agc_fill(&self) -> Option<f64> {
1386 self.0.get_f64("AGC Fill:")
1387 }
1388
1389 pub fn analyzer_temperature(&self) -> Option<f64> {
1391 self.0.get_f64("Analyzer Temperature:")
1392 }
1393
1394 pub fn ps_injection_time_ms(&self) -> Option<f64> {
1396 self.0.get_f64("PS Inj. Time (ms):")
1397 }
1398
1399 pub fn reagent_ion_injection_time_ms(&self) -> Option<f64> {
1401 self.0
1402 .get_f32("Reagent Ion Injection Time (ms):")
1403 .map(f64::from)
1404 }
1405
1406 pub fn reagent_ion_agc(&self) -> Option<bool> {
1408 match self.0.get("Reagent Ion AGC:")? {
1409 GenericValue::Bool(b) => Some(*b),
1410 _ => None,
1411 }
1412 }
1413
1414 pub fn source_cid_energy_ev(&self) -> Option<f64> {
1416 self.0
1417 .get_f64("Source CID eV:")
1418 .or_else(|| self.0.get_f32("API Source CID Energy:").map(f64::from))
1419 }
1420
1421 pub fn dynamic_rt_shift_min(&self) -> Option<f64> {
1423 self.0.get_f64("Dynamic RT Shift (min):")
1424 }
1425
1426 pub fn lock_mass_correction_ppm(&self) -> Option<f64> {
1428 self.0
1429 .get_f64("LM Correction (ppm):")
1430 .or_else(|| self.0.get_f64("LM m/z-Correction (ppm):"))
1431 }
1432
1433 pub fn number_of_lock_masses(&self) -> Option<i32> {
1435 self.0
1436 .get_i32("Number of LM Found:")
1437 .or_else(|| self.0.get_i32("Number of Lock Masses:"))
1438 }
1439
1440 pub fn orbitrap_resolution(&self) -> Option<i32> {
1442 self.0
1443 .get_i32("Orbitrap Resolution:")
1444 .or_else(|| self.0.get_i32("FT Resolution:"))
1445 }
1446
1447 pub fn sps_mass(&self, channel: usize) -> Option<f32> {
1451 let label = format!("SPS Mass {}:", channel + 1);
1452 self.0.get_f32(&label)
1453 }
1454
1455 pub fn conversion_parameter_a(&self) -> Option<f64> {
1457 self.0.get_f64("Conversion Parameter A:")
1458 }
1459
1460 pub fn conversion_parameter_b(&self) -> Option<f64> {
1462 self.0.get_f64("Conversion Parameter B:")
1463 }
1464
1465 pub fn conversion_parameter_c(&self) -> Option<f64> {
1467 self.0.get_f64("Conversion Parameter C:")
1468 }
1469
1470 pub fn raw_ovft(&self) -> Option<f64> {
1472 self.0.get_f64("RawOvFtT:")
1473 }
1474
1475 pub fn isotopic_fit_error(&self) -> Option<f64> {
1477 self.0.get_f64("Error in isotopic envelope fit:")
1478 }
1479
1480 pub fn scan_description(&self) -> Option<&str> {
1482 self.0.get_string("Scan Description:")
1483 }
1484
1485 pub fn multi_inject_info(&self) -> Option<&str> {
1487 self.0.get_string("Multi Inject Info:")
1488 }
1489
1490 pub fn hcd_energy(&self) -> Option<&str> {
1492 self.0
1493 .get_string("HCD Energy:")
1494 .or_else(|| self.0.get_string("HCD Energy V:"))
1495 }
1496}
1497
1498pub struct StatusLogEntry<'a>(pub &'a GenericRecord);
1506
1507impl<'a> StatusLogEntry<'a> {
1508 #[inline]
1510 pub fn record(&self) -> &GenericRecord {
1511 self.0
1512 }
1513
1514 pub fn ion_injection_time_ms(&self) -> Option<f64> {
1516 self.0
1517 .get_f64("Ion Injection Time (ms):")
1518 .or_else(|| self.0.get_f64("Ion Inject Time (ms):"))
1519 }
1520
1521 pub fn ft_resolution(&self) -> Option<i32> {
1523 self.0
1524 .get_i32("Orbitrap Resolution:")
1525 .or_else(|| self.0.get_i32("FT Resolution:"))
1526 }
1527
1528 pub fn faims_cv(&self) -> Option<f64> {
1530 self.0
1531 .get_f64("FAIMS CV:")
1532 .or_else(|| self.0.get_f32("FAIMS CV:").map(f64::from))
1533 }
1534
1535 pub fn s_lens_rf_level(&self) -> Option<f64> {
1537 self.0.get_f64("S-Lens RF Level:")
1538 }
1539
1540 pub fn analyzer_temperature(&self) -> Option<f64> {
1542 self.0
1543 .get_f64("Analyzer Temperature:")
1544 .or_else(|| self.0.get_f32("Analyzer Temperature:").map(f64::from))
1545 }
1546
1547 pub fn spray_voltage(&self) -> Option<f64> {
1549 self.0
1550 .get_f64("Spray Voltage (V):")
1551 .or_else(|| self.0.get_f64("Spray Voltage:"))
1552 .or_else(|| self.0.get_f32("Spray Voltage:").map(f64::from))
1553 }
1554
1555 pub fn lock_mass_correction_ppm(&self) -> Option<f64> {
1557 self.0
1558 .get_f64("LM Correction (ppm):")
1559 .or_else(|| self.0.get_f64("LM m/z-Correction (ppm):"))
1560 }
1561
1562 pub fn capillary_temperature(&self) -> Option<f64> {
1564 self.0
1565 .get_f64("Capillary Temp (°C):")
1566 .or_else(|| self.0.get_f64("Capillary Temp:"))
1567 .or_else(|| self.0.get_f32("Capillary Temp:").map(f64::from))
1568 }
1569
1570 pub fn number_of_lock_masses(&self) -> Option<i32> {
1572 self.0
1573 .get_i32("Number of LM Found:")
1574 .or_else(|| self.0.get_i32("Number of Lock Masses:"))
1575 }
1576
1577 pub fn get(&self, label: &str) -> Option<&GenericValue> {
1579 self.0.get(label)
1580 }
1581
1582 pub fn get_f64(&self, label: &str) -> Option<f64> {
1584 self.0.get_f64(label)
1585 }
1586
1587 pub fn get_i32(&self, label: &str) -> Option<i32> {
1589 self.0.get_i32(label)
1590 }
1591
1592 pub fn get_string(&self, label: &str) -> Option<&str> {
1594 self.0.get_string(label)
1595 }
1596}