Skip to main content

DeviceFamily

Enum DeviceFamily 

Source
pub enum DeviceFamily {
    LcqIonTrap,
    LtqIonTrap,
    LtqFt,
    LtqOrbitrap,
    QOrbitrap,
    Tribrid,
    ExplorisOrbitrap,
    OrbitrapAstral,
    TripleQuad,
    Unknown,
}
Expand description

Coarse instrument-family classification.

Variants§

§

LcqIonTrap

LCQ Classic/Deca/Advantage/Fleet - 3D ion trap (legacy).

§

LtqIonTrap

LTQ / LTQ XL / LTQ Velos / LTQ Velos Pro - 2D linear ion trap.

§

LtqFt

LTQ FT - ion trap coupled with FTICR (pre-Orbitrap era).

§

LtqOrbitrap

LTQ Orbitrap family - ion trap + Orbitrap hybrids (Classic / XL / Discovery / Velos / Elite).

§

QOrbitrap

Q Exactive family - quadrupole + C-trap + Orbitrap (Q Exactive / Plus / HF / HF-X / UHMR). No ion trap.

§

Tribrid

Tribrid Orbitrap - quadrupole + linear ion trap + Orbitrap (Fusion / Fusion Lumos / Eclipse / Ascend).

§

ExplorisOrbitrap

Single-stage Q-Orbitrap with advanced scan modes (Orbitrap Exploris 120 / 240 / 480).

§

OrbitrapAstral

Orbitrap Astral hybrid - Orbitrap plus asymmetric-track lossless analyzer.

§

TripleQuad

Triple quadrupole - TSQ Vantage / Quantum / Quantiva / Altis / Endura.

§

Unknown

Unknown / undetected.

Implementations§

Source§

impl DeviceFamily

Source

pub fn display_name(self) -> &'static str

Human-readable family name.

Examples found in repository?
examples/dump.rs (line 24)
3fn main() {
4    let args: Vec<String> = env::args().collect();
5    if args.len() < 2 {
6        eprintln!("Usage: dump <file.raw> [--max-scans N]");
7        std::process::exit(1);
8    }
9
10    let path = &args[1];
11
12    // Parse optional --max-scans flag
13    let max_scans: Option<u32> = args
14        .windows(2)
15        .find(|w| w[0] == "--max-scans")
16        .and_then(|w| w[1].parse().ok());
17    match opentfraw::RawFileReader::open_path(path) {
18        Ok(raw) => {
19            println!("=== Thermo RAW File ===");
20            println!("Version:  {}", raw.version);
21            println!(
22                "Instrument: {} ({})",
23                raw.instrument_model.unwrap_or("unknown model"),
24                raw.device_family.display_name()
25            );
26            println!(
27                "Scans:    {} ({} to {})",
28                raw.num_scans,
29                raw.run_header.sample_info.first_scan_number,
30                raw.run_header.sample_info.last_scan_number,
31            );
32            println!();
33
34            println!("--- Header ---");
35            println!("Signature:    {}", raw.header.signature);
36            println!(
37                "Audit start:  {} (unix ts: {:.0})",
38                raw.header.audit_start.tag1, raw.header.audit_start.time
39            );
40            println!("Audit tag2:   {}", raw.header.audit_start.tag2);
41            println!();
42
43            println!("--- Acquisition Date ---");
44            let p = &raw.raw_file_info.preamble;
45            println!(
46                "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
47                p.year, p.month, p.day, p.hour, p.minute, p.second, p.millisecond
48            );
49            println!("Controllers:  {}", p.controller_count);
50            println!("Data addr:    {:#x}", p.data_addr);
51            println!("RunHdr addr:  {:#x}", p.run_header_addr);
52            println!();
53
54            println!("--- Sequence Row ---");
55            println!("Comment:      {}", raw.seq_row.comment);
56            println!("Inst method:  {}", raw.seq_row.inst_method);
57            println!("File name:    {}", raw.seq_row.file_name);
58            println!();
59
60            println!("--- RawFileInfo ---");
61            println!("Computer:     {}", raw.raw_file_info.computer_name);
62            for (i, h) in raw.raw_file_info.label_headings.iter().enumerate() {
63                if !h.is_empty() {
64                    println!("Label[{}]:     {}", i + 1, h);
65                }
66            }
67            println!();
68
69            println!("--- Sample Info ---");
70            let si = &raw.run_header.sample_info;
71            println!("M/z range:    {:.2} - {:.2}", si.low_mz, si.high_mz);
72            println!(
73                "RT range:     {:.2} - {:.2} min",
74                si.start_time, si.end_time
75            );
76            println!("Max TIC:      {:.2e}", si.max_ion_current);
77            println!("Error log:    {} entries", si.error_log_length);
78            println!("Inst log:     {} entries", si.inst_log_length);
79            println!();
80
81            println!("--- Run Header ---");
82            let rh = &raw.run_header;
83            println!("Scan index:   {:#x}", rh.scan_index_addr);
84            println!("Data:         {:#x}", rh.data_addr);
85            println!("Trailer:      {:#x}", rh.scan_trailer_addr);
86            println!("Params:       {:#x}", rh.scan_params_addr);
87            println!("Inst log:     {:#x}", rh.inst_log_addr);
88            println!("Error log:    {:#x}", rh.error_log_addr);
89            println!("Self addr:    {:#x}", rh.own_addr);
90            println!("ntrailer:     {}", rh.ntrailer);
91            println!("nparams:      {}", rh.nparams);
92            println!("nsegs:        {}", rh.nsegs);
93            println!();
94
95            // First few scans
96            let n_show = std::cmp::min(5, raw.scan_index.len());
97            println!("--- First {} Scan Index Entries ---", n_show);
98            for entry in &raw.scan_index[..n_show] {
99                println!("  Scan {}: RT={:.4} min, TIC={:.2e}, base={:.2} @ {:.4} m/z, range=[{:.2}-{:.2}], offset={:#x}, size={}",
100                    entry.index + 1,
101                    entry.start_time,
102                    entry.total_current,
103                    entry.base_intensity,
104                    entry.base_mz,
105                    entry.low_mz,
106                    entry.high_mz,
107                    entry.offset,
108                    entry.data_size,
109                );
110            }
111            println!();
112
113            // First few scan events
114            let n_events = std::cmp::min(3, raw.scan_events.len());
115            println!("--- First {} Scan Events ---", n_events);
116            for (i, evt) in raw.scan_events[..n_events].iter().enumerate() {
117                let p = &evt.preamble;
118                println!("  Event {}: analyzer={:?}, polarity={:?}, mode={:?}, ms_power={:?}, dependent={}, ionization={:?}, activation={:?}",
119                    i,
120                    p.analyzer(),
121                    p.polarity(),
122                    p.scan_mode(),
123                    p.ms_power(),
124                    p.is_dependent(),
125                    p.ionization(),
126                    p.activation(),
127                );
128                if !evt.reactions.is_empty() {
129                    for rx in &evt.reactions {
130                        println!("    Precursor: {:.4} @ {:.1}", rx.precursor_mz, rx.energy);
131                    }
132                }
133                println!("    Coefficients: {} params", evt.coefficients.len());
134                for fc in &evt.fraction_collectors {
135                    println!("    Range: [{:.2}-{:.2}]", fc.low_mz, fc.high_mz);
136                }
137            }
138            println!();
139
140            // Scan parameters header (trailer extra schema)
141            println!(
142                "--- Scan Parameters Schema ({} fields) ---",
143                raw.scan_parameters_header.fields.len()
144            );
145            for desc in &raw.scan_parameters_header.fields {
146                println!(
147                    "  {:?}: \"{}\" (len={})",
148                    desc.field_type, desc.label, desc.length
149                );
150            }
151            println!();
152
153            // First scan's parameters
154            if let Some(first_params) = raw.scan_parameters.first() {
155                println!("--- Scan 1 Parameters ---");
156                for (label, value) in &first_params.values {
157                    match value {
158                        opentfraw::generic_data::GenericValue::Gap => {}
159                        _ => println!("  {}: {:?}", label, value),
160                    }
161                }
162            }
163            println!();
164
165            // Typed accessor summary for scan 1
166            let first = raw.run_header.sample_info.first_scan_number;
167            if let Some(p) = raw.scan_params(first) {
168                println!("--- Scan 1 Typed Summary ---");
169                println!("  injection_time_ms  : {:?}", p.ion_injection_time_ms());
170                println!("  charge_state       : {:?}", p.charge_state());
171                println!("  monoisotopic_mz    : {:?}", p.monoisotopic_mz());
172                println!("  micro_scan_count   : {:?}", p.micro_scan_count());
173                println!("  ft_resolution      : {:?}", p.ft_resolution());
174                println!("  hcd_energy         : {:?}", p.hcd_energy());
175                println!("  master_scan_number : {:?}", p.master_scan_number());
176                println!("  agc_enabled        : {:?}", p.agc_enabled());
177                println!("  agc_target         : {:?}", p.agc_target());
178                println!("  max_ion_time_ms    : {:?}", p.max_ion_time_ms());
179                println!("  elapsed_scan_time_s: {:?}", p.elapsed_scan_time_s());
180                println!("  lm_correction_ppm  : {:?}", p.lm_correction_ppm());
181            }
182            println!();
183
184            // Error log
185            if !raw.error_log.is_empty() {
186                println!("--- Error Log ({} entries) ---", raw.error_log.len());
187                for (i, e) in raw.error_log.iter().enumerate().take(5) {
188                    println!("  [{}] RT={:.2}: {}", i, e.time, e.message);
189                }
190            }
191
192            // Instrument log schema
193            println!(
194                "--- Instrument Log Schema ({} fields) ---",
195                raw.inst_log_header.fields.len()
196            );
197            for desc in &raw.inst_log_header.fields {
198                println!("  {:?}: \"{}\"", desc.field_type, desc.label);
199            }
200            println!();
201
202            // Scan data validation: read first few scans and cross-check against index
203            println!(
204                "--- Scan Data Validation (device={}, format={}) ---",
205                raw.device_family.display_name(),
206                raw.scan_format.display_name()
207            );
208            let mut file = std::fs::File::open(path).expect("reopen file");
209            let first_scan = raw.run_header.sample_info.first_scan_number;
210            let cap = max_scans.unwrap_or(5);
211            let n_validate = std::cmp::min(cap, raw.num_scans);
212            for i in 0..n_validate {
213                let scan_num = first_scan + i;
214                let idx_entry = &raw.scan_index[i as usize];
215
216                if raw.flat_peaks {
217                    // Flat-peak (TSQ/SRM) format - use unified router
218                    match raw.read_scan_peaks(&mut file, scan_num) {
219                        Ok(peaks) => {
220                            let n_peaks = peaks.len();
221                            let peak_tic: f64 = peaks.iter().map(|p| p.abundance as f64).sum();
222                            let nonzero: Vec<_> =
223                                peaks.iter().filter(|p| p.abundance != 0.0).collect();
224                            println!("  Scan {} (evt={}): {} peaks ({} nonzero) | peak_tic={:.2e} vs index_tic={:.2e}",
225                                scan_num, idx_entry.scan_event, n_peaks, nonzero.len(),
226                                peak_tic, idx_entry.total_current);
227                            if i == 0 {
228                                for (j, pk) in peaks.iter().enumerate().take(5) {
229                                    println!(
230                                        "    Peak {}: m/z={:.4}, abundance={:.4}",
231                                        j, pk.mz, pk.abundance
232                                    );
233                                }
234                                println!(
235                                    "    Index base peak: m/z={:.4}, intensity={:.2e}",
236                                    idx_entry.base_mz, idx_entry.base_intensity
237                                );
238                            }
239                        }
240                        Err(e) => {
241                            println!("  Scan {}: ERROR - {}", scan_num, e);
242                        }
243                    }
244                } else {
245                    // PacketHeader format
246                    match raw.read_scan(&mut file, scan_num) {
247                        Ok(pkt) => {
248                            let h = &pkt.header;
249                            let profile_bins: usize = pkt
250                                .profile
251                                .as_ref()
252                                .map(|p| p.chunks.iter().map(|c| c.signal.len()).sum())
253                                .unwrap_or(0);
254                            let n_peaks = pkt.peaks.len();
255
256                            // Cross-check: scan index says range [low-high]
257                            let range_ok = if n_peaks > 0 {
258                                let first_mz = pkt.peaks[0].mz;
259                                let last_mz = pkt.peaks[n_peaks - 1].mz;
260                                // Peaks should be within the declared range (with some tolerance)
261                                first_mz >= idx_entry.low_mz * 0.99
262                                    && last_mz <= idx_entry.high_mz * 1.01
263                            } else {
264                                true
265                            };
266
267                            // Compute TIC from centroid peaks and compare
268                            let peak_tic: f64 = pkt.peaks.iter().map(|p| p.abundance as f64).sum();
269
270                            // Show scan event info
271                            let evt = raw.scan_events.get(i as usize);
272                            let mode_str = evt
273                                .and_then(|e| e.preamble.scan_mode())
274                                .map(|m| format!("{:?}", m))
275                                .unwrap_or_else(|| "?".into());
276
277                            println!("  Scan {}: {} | profile={} bins, peaks={}, layout={} | mz=[{:.2}-{:.2}] | range_ok={} | peak_tic={:.2e} vs index_tic={:.2e}",
278                            scan_num, mode_str, profile_bins, n_peaks, h.layout,
279                            h.low_mz, h.high_mz,
280                            range_ok, peak_tic, idx_entry.total_current,
281                        );
282
283                            // Show top 3 peaks for first scan
284                            if i == 0 && !pkt.peaks.is_empty() {
285                                let mut sorted: Vec<_> = pkt.peaks.iter().collect();
286                                sorted
287                                    .sort_by(|a, b| b.abundance.partial_cmp(&a.abundance).unwrap());
288                                let n_top = std::cmp::min(3, sorted.len());
289                                for (j, pk) in sorted[..n_top].iter().enumerate() {
290                                    println!(
291                                        "    Top {}: m/z={:.4}, abundance={:.2e}",
292                                        j + 1,
293                                        pk.mz,
294                                        pk.abundance
295                                    );
296                                }
297                                println!(
298                                    "    Index base peak: m/z={:.4}, intensity={:.2e}",
299                                    idx_entry.base_mz, idx_entry.base_intensity
300                                );
301                            }
302
303                            // Show profile info for first scan
304                            if i == 0 {
305                                if let Some(ref prof) = pkt.profile {
306                                    println!("    Profile: first_value={:.6e}, step={:.6e}, {} chunks, {} total bins",
307                                    prof.first_value, prof.step, prof.chunks.len(), profile_bins);
308                                    // Convert and show top signal
309                                    let coeffs: Vec<f64> =
310                                        evt.map(|e| e.coefficients.clone()).unwrap_or_default();
311                                    if !coeffs.is_empty() {
312                                        let mz_int = prof.to_mz_intensity(&coeffs);
313                                        if let Some(max_pt) = mz_int
314                                            .iter()
315                                            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
316                                        {
317                                            println!("    Profile max: m/z={:.4}, intensity={:.2e} (coeffs={})",
318                                            max_pt.0, max_pt.1, coeffs.len());
319                                        }
320                                    }
321                                }
322                            }
323                        }
324                        Err(e) => {
325                            println!("  Scan {}: ERROR - {}", scan_num, e);
326                        }
327                    }
328                } // end else (PacketHeader)
329            }
330        }
331        Err(e) => {
332            eprintln!("Error: {}", e);
333            std::process::exit(1);
334        }
335    }
336}
Source

pub fn uses_flat_peaks(self) -> bool

Whether the device is expected to use the flat-peaks (SRM) scan layout.

Source§

impl DeviceFamily

Source

pub fn detect_instrument( metadata_bytes: &[u8], tag2: &str, inst_method: &str, first_analyzer: Option<Analyzer>, ) -> DetectedInstrument

Scan metadata_bytes (a raw prefix of the RAW file) for a canonical Thermo instrument model encoded as UTF-16LE, then fall back to the heuristic over tag2 + inst_method + first_analyzer.

Source

pub fn detect_heuristic( tag2: &str, inst_method: &str, first_analyzer: Option<Analyzer>, ) -> Self

Keyword heuristic over audit-tag + method path, with analyzer-type fallback. Retained as a secondary path when no model string is found.

Source

pub fn detect( tag2: &str, inst_method: &str, first_analyzer: Option<Analyzer>, ) -> Self

👎Deprecated:

Use DeviceFamily::detect_instrument with the metadata byte window for reliable detection

Legacy compatibility wrapper (no metadata byte window).

Trait Implementations§

Source§

impl Clone for DeviceFamily

Source§

fn clone(&self) -> DeviceFamily

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for DeviceFamily

Source§

impl Debug for DeviceFamily

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for DeviceFamily

Source§

impl PartialEq for DeviceFamily

Source§

fn eq(&self, other: &DeviceFamily) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for DeviceFamily

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.