Skip to main content

Quickstart: Rust

Add the umbrella crate to your Cargo.toml:

[dependencies]
openmassspec-io = { path = "../OpenMassSpec/crates/openmassspec-io", features = ["all"] }
openmassspec-core = { path = "../OpenMassSpecCore" }

The all feature pulls in every vendor. To trim binary size you can opt in to one vendor at a time: features = ["thermo"], features = ["bruker"], features = ["waters"], features = ["agilent"], features = ["sciex"], features = ["shimadzu"].

Convert a file to mzML​

use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = Path::new("sample.raw");
let output = Path::new("sample.mzML");

let detected = openmassspec_io::detect_format(input)
.ok_or("not a recognized vendor format")?;
let opts = openmassspec_io::ConvertOptions::new().with_indexed(true);
let summary = openmassspec_io::convert_to_mzml(&detected, output, &opts)?;
println!("{} spectra written", summary.spectra_written);
Ok(())
}

Unreadable spectra​

Every vendor source yields Result records: a spectrum or chromatogram the reader cannot decode is an Err, never silently dropped. convert_to_mzml, collect, and stream stop at the first one and return it as openmassspec_io::Error::Reader. To write everything that can be read and leave the rest out, select the skip policy and report the count to your users:

use std::path::Path;
use openmassspec_io::core::RecordErrorPolicy;
use openmassspec_io::ConvertOptions;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let detected = openmassspec_io::detect_format(Path::new("sample.raw"))
.ok_or("not a recognized vendor format")?;
let opts = ConvertOptions::new().with_record_error_policy(RecordErrorPolicy::Skip);
let summary = openmassspec_io::convert_to_mzml(&detected, Path::new("sample.mzML"), &opts)?;
if summary.skipped() > 0 {
eprintln!("warning: {} unreadable records skipped", summary.skipped());
}
Ok(())
}

Reader-specific errors​

Every reader failure, whether opening the input or decoding a record, is Error::Reader { vendor, source }, where source is the reader crate's own error boxed as dyn std::error::Error. The reader crates are not re-exported, so openmassspec-io's API does not change when a reader releases a new major version. To inspect reader-specific detail, depend on that reader crate directly and downcast Error::reader_error():

// Cargo.toml: opentfraw = "3" alongside openmassspec-io.
match openmassspec_io::collect(&detected) {
Ok((records, _meta)) => println!("{} spectra", records.len()),
Err(err) => {
match err.reader_error().and_then(|e| e.downcast_ref::<opentfraw::Error>()) {
Some(thermo_err) => eprintln!("opentfraw: {thermo_err:?}"),
None => eprintln!("error: {err}"),
}
}
}

Error::reader_vendor() returns the vendor name ("thermo", ...) without downcasting.

Centroid on the way out​

ConvertOptions::with_centroid, collect_centroided, and stream_centroided peak-pick every profile-mode spectrum first (spectra already tagged centroid pass through unchanged) via openmassspec_core::Centroided, with an optional min_intensity noise floor. Plain collect / convert_to_mzml never centroid - this is always opt-in:

use std::path::Path;
use openmassspec_io::ConvertOptions;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let detected = openmassspec_io::detect_format(Path::new("sample.raw"))
.ok_or("not a recognized vendor format")?;
let opts = ConvertOptions::new()
.with_indexed(true)
.with_centroid(true)
.with_centroid_min_intensity(500.0);
openmassspec_io::convert_to_mzml(&detected, Path::new("sample.mzML"), &opts)?;
Ok(())
}

Iterate spectra without writing mzML​

use openmassspec_core::SpectrumSource;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let detected = openmassspec_io::detect_format(std::path::Path::new("sample.raw"))
.ok_or("not a vendor format")?;
let (records, meta) = openmassspec_io::collect(&detected)?;
println!("{} spectra from {}", records.len(), meta.instrument.name);
for s in records.iter().take(5) {
println!(
"idx={} ms={} rt={:.2}s peaks={}",
s.index, s.ms_level, s.retention_time_sec, s.mz.len()
);
}
Ok(())
}

For long runs you usually want the streaming variant, which visits one spectrum at a time:

let detected = openmassspec_io::detect_format(std::path::Path::new("sample.d")).unwrap();
let meta = openmassspec_io::stream(&detected, |s| {
// process one spectrum at a time
Ok(())
})?;
# Ok::<(), Box<dyn std::error::Error>>(())

with_source hands you the vendor source as a &mut dyn SpectrumSource for anything the convenience functions do not cover; iter_spectra yields Result items:

let detected = openmassspec_io::detect_format(std::path::Path::new("sample.d")).unwrap();
openmassspec_io::with_source(&detected, |src| {
for s in src.iter_spectra() {
let s = s?;
// ...
}
Ok(())
})?;
# Ok::<(), Box<dyn std::error::Error>>(())

Validate​

use openmassspec_core::conformance::assert_iter_invariants;

let detected = openmassspec_io::detect_format(std::path::Path::new("sample.raw")).unwrap();
let (records, _) = openmassspec_io::collect(&detected)?;
let n = assert_iter_invariants(records.iter().cloned())?;
println!("conformance ok: {n} spectra");
# Ok::<(), Box<dyn std::error::Error>>(())

Arrow​

Enable the arrow feature on openmassspec-io, which re-exports the openmassspec-arrow crate as openmassspec_io::arrow, and build a record batch:

use openmassspec_io::arrow::SpectrumBatchBuilder;

let detected = openmassspec_io::detect_format(std::path::Path::new("sample.d")).unwrap();
let meta = openmassspec_io::metadata_only(&detected)?;
let mut b = SpectrumBatchBuilder::new(meta.mobility_array_kind);
openmassspec_io::stream(&detected, |s| {
b.push(&s);
Ok(())
})?;
let batch = b.finish()?;
println!("{} rows x {} cols", batch.num_rows(), batch.num_columns());
# Ok::<(), Box<dyn std::error::Error>>(())