1use std::fs::File;
12use std::io::BufWriter;
13
14use openmassspec_core::{
15 write_indexed_mzml, write_mzml, Activation, Analyzer, ChromatogramRecord, CvTerm,
16 MobilityArrayKind, MsPower, Polarity, PrecursorInfo, RunMetadata, ScanMode, SpectrumRecord,
17 SpectrumSource,
18};
19
20struct SampleSource {
21 meta: RunMetadata,
22 spectra: Vec<SpectrumRecord>,
23 chroms: Vec<ChromatogramRecord>,
24 cursor: usize,
25}
26
27impl SampleSource {
28 fn new() -> Self {
29 let meta = RunMetadata {
30 source_file_name: "sample.raw".into(),
31 source_file_format: CvTerm::new("MS:1000563", "Thermo RAW format"),
32 native_id_format: CvTerm::new("MS:1000768", "Thermo nativeID format"),
33 instrument: CvTerm::new("MS:1001911", "Q Exactive"),
34 instrument_serial_number: Some("SN-EXAMPLE-001".into()),
35 software_name: "openmassspec-core-sample".into(),
36 software_version: env!("CARGO_PKG_VERSION").into(),
37 acquisition_software_name: Some("Xcalibur".into()),
41 acquisition_software_version: Some("4.4.16.14".into()),
42 start_timestamp: Some("2026-06-01T14:30:00Z".into()),
45 mobility_array_kind: Some(MobilityArrayKind::InverseReducedVsPerCm2),
46 analyzers: vec![Analyzer::FTMS, Analyzer::TOFMS],
51 };
52 let ms1 = SpectrumRecord {
53 index: 0,
54 scan_number: 1,
55 native_id: "controllerType=0 controllerNumber=1 scan=1".into(),
56 ms_level: MsPower::Ms1.ms_level(),
57 polarity: Some(Polarity::Positive),
58 scan_mode: Some(ScanMode::Centroid),
59 analyzer: Some(Analyzer::FTMS),
60 filter: Some("FTMS + p ESI Full ms".into()),
61 retention_time_sec: 0.123 * 60.0,
62 total_ion_current: None,
63 base_peak_mz: None,
64 base_peak_intensity: None,
65 low_mz: None,
66 high_mz: None,
67 ion_injection_time_ms: Some(20.0),
68 inv_mobility: None,
69 faims_cv: None,
70 precursor: None,
71 mz: vec![100.0, 200.0, 300.0],
72 intensity: vec![1.0, 5.0, 2.0],
73 inv_mobility_per_peak: None,
74 };
75 let ms2 = SpectrumRecord {
76 index: 1,
77 scan_number: 2,
78 native_id: "controllerType=0 controllerNumber=1 scan=2".into(),
79 ms_level: MsPower::Ms2.ms_level(),
80 polarity: Some(Polarity::Positive),
81 scan_mode: Some(ScanMode::Centroid),
82 analyzer: Some(Analyzer::FTMS),
83 filter: Some("FTMS + p ESI d Full ms2 200.00@hcd28.00".into()),
84 retention_time_sec: 0.5 * 60.0,
85 total_ion_current: Some(123.45),
86 base_peak_mz: Some(150.5),
87 base_peak_intensity: Some(99.0),
88 low_mz: Some(100.0),
89 high_mz: Some(180.0),
90 ion_injection_time_ms: Some(50.0),
91 inv_mobility: None,
92 faims_cv: Some(-45.0),
93 precursor: Some(PrecursorInfo {
94 target_mz: Some(200.0),
95 selected_mz: Some(200.001),
96 isolation_width: Some(2.0),
97 charge: Some(2),
98 intensity: None,
99 collision_energy: Some(28.0),
100 ce_is_nce: true,
101 precursor_native_id: Some("controllerType=0 controllerNumber=1 scan=1".into()),
102 activation: Some(Activation::CID),
103 analyzer: Some(Analyzer::FTMS),
104 ccs: Some(153.4),
107 }),
108 mz: vec![150.5, 160.0],
109 intensity: vec![99.0, 50.0],
110 inv_mobility_per_peak: None,
111 };
112 let ms1_mobility = SpectrumRecord {
117 index: 2,
118 scan_number: 3,
119 native_id: "frame=1 scan=0".into(),
120 ms_level: MsPower::Ms1.ms_level(),
121 polarity: Some(Polarity::Positive),
122 scan_mode: Some(ScanMode::Centroid),
123 analyzer: Some(Analyzer::TOFMS),
124 filter: None,
125 retention_time_sec: 0.75 * 60.0,
126 total_ion_current: None,
127 base_peak_mz: None,
128 base_peak_intensity: None,
129 low_mz: None,
130 high_mz: None,
131 ion_injection_time_ms: None,
132 inv_mobility: Some(0.95),
133 faims_cv: None,
134 precursor: None,
135 mz: vec![120.0, 240.0, 360.0],
136 intensity: vec![3.0, 7.0, 4.0],
137 inv_mobility_per_peak: Some(vec![0.92, 0.95, 0.98]),
138 };
139 let tic = ChromatogramRecord {
143 index: 0,
144 id: "TIC".into(),
145 chromatogram_type: Some(CvTerm::new("MS:1000235", "total ion current chromatogram")),
146 precursor_mz: None,
147 product_mz: None,
148 time_sec: vec![0.0, 30.0, 45.0],
149 intensity: vec![120.0, 340.0, 210.0],
150 };
151 let srm = ChromatogramRecord {
152 index: 1,
153 id: "SRM SIC Q1=524.3 Q3=136.1".into(),
154 chromatogram_type: Some(CvTerm::new(
155 "MS:1001473",
156 "selected reaction monitoring chromatogram",
157 )),
158 precursor_mz: Some(524.3),
159 product_mz: Some(136.1),
160 time_sec: vec![0.0, 30.0],
161 intensity: vec![50.0, 80.0],
162 };
163 Self {
164 meta,
165 spectra: vec![ms1, ms2, ms1_mobility],
166 chroms: vec![tic, srm],
167 cursor: 0,
168 }
169 }
170}
171
172impl SpectrumSource for SampleSource {
173 fn run_metadata(&self) -> RunMetadata {
174 self.meta.clone()
175 }
176
177 fn iter_spectra<'a>(&'a mut self) -> Box<dyn Iterator<Item = SpectrumRecord> + 'a> {
178 self.cursor = 0;
179 Box::new(std::iter::from_fn(move || {
180 let rec = self.spectra.get(self.cursor).cloned();
181 if rec.is_some() {
182 self.cursor += 1;
183 }
184 rec
185 }))
186 }
187
188 fn iter_chromatograms<'a>(&'a mut self) -> Box<dyn Iterator<Item = ChromatogramRecord> + 'a> {
189 Box::new(self.chroms.clone().into_iter())
190 }
191
192 fn spectrum_count_hint(&self) -> Option<usize> {
193 Some(self.spectra.len())
194 }
195}
196
197fn main() -> std::io::Result<()> {
198 let mut args = std::env::args().skip(1);
199 let plain_path = args.next().unwrap_or_else(|| "sample_plain.mzML".into());
200 let indexed_path = args.next().unwrap_or_else(|| "sample_indexed.mzML".into());
201
202 let mut src = SampleSource::new();
203 let mut plain = BufWriter::new(File::create(&plain_path)?);
204 write_mzml(&mut src, &mut plain)
205 .map_err(|e| std::io::Error::other(format!("write_mzml: {e}")))?;
206
207 let mut indexed = BufWriter::new(File::create(&indexed_path)?);
208 write_indexed_mzml(&mut src, &mut indexed)
209 .map_err(|e| std::io::Error::other(format!("write_indexed_mzml: {e}")))?;
210
211 eprintln!("wrote {plain_path} and {indexed_path}");
212 Ok(())
213}