Skip to main content

lfq_group_audit/
lfq_group_audit.rs

1//! Export all candidate groups (including rejected groups) without loading hills.
2//! cargo run --release --example lfq_group_audit -- BATCH CONFIG OUT
3use koth_ms::{
4    alignment::{align_runs, RunInput},
5    config::AlignConfig,
6    input::{discover_runs, read_features},
7    lfq::consensus::build_consensus_candidates,
8};
9use std::{io::Write, path::PathBuf};
10
11fn main() -> anyhow::Result<()> {
12    env_logger::init();
13    let args: Vec<_> = std::env::args().skip(1).map(PathBuf::from).collect();
14    anyhow::ensure!(args.len() == 3, "usage: lfq_group_audit BATCH CONFIG OUT");
15    let cfg = AlignConfig::from_toml(&args[1])?;
16    let paths = discover_runs(&args[0])?;
17    anyhow::ensure!(!paths.is_empty(), "no feature runs found");
18    let runs: Vec<_> = paths
19        .iter()
20        .map(|r| {
21            Ok(RunInput {
22                name: r.name.clone(),
23                features: read_features(&r.features_path)?,
24                hills: Vec::new(),
25                scan_times: Vec::new(),
26                rt_bounds: None,
27            })
28        })
29        .collect::<anyhow::Result<_>>()?;
30    let alignment = align_runs(&runs, &cfg.alignment);
31    let groups = build_consensus_candidates(&runs, &alignment, &cfg.lfq);
32    std::fs::create_dir_all(&args[2])?;
33    let mut out = std::io::BufWriter::new(std::fs::File::create(args[2].join("candidates.tsv"))?);
34    writeln!(out, "candidate_id\tmassCalib\tmz\tcharge\trtApex\tim\tcombined_score\tn_contributing_runs\tgroup_score\tgroup_qvalue\tweak_members\tretained")?;
35    for (i, g) in groups.iter().enumerate() {
36        let weak = g
37            .members
38            .iter()
39            .filter(|&&(r, f)| runs[r].features[f as usize].combined_score < 0.5)
40            .count();
41        writeln!(
42            out,
43            "{i}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{weak}\t{}",
44            g.neutral_mass,
45            g.ref_mz,
46            g.charge,
47            g.ref_rt,
48            g.ref_im,
49            g.seed_combined_score,
50            g.n_contributing_runs,
51            g.group_score,
52            g.group_qvalue,
53            g.n_contributing_runs >= 2 && g.group_qvalue <= cfg.lfq.consensus.max_group_qvalue
54        )?;
55    }
56    out.flush()?;
57    Ok(())
58}