pub struct Feature {
pub hills: Vec<Hill>,
pub charge: u8,
pub cosine_score: f64,
pub ppm_error: f64,
}Expand description
A detected isotope feature: a group of hills forming an isotope envelope.
hills are sorted by mz (monoisotopic first).
charge == 0 means unknown charge (single peak, no isotope partners found).
Fields§
§hills: Vec<Hill>§charge: u8§cosine_score: f64Mean chromatographic cosine similarity (in scan/RT space) between adjacent isotope hills in the chain. Bounded [0, 1]. Measures whether the isotope hills co-elute in time — not whether their intensities match an averagine pattern.
ppm_error: f64Implementations§
Source§impl Feature
impl Feature
pub fn monoisotopic_hill(&self) -> &Hill
Sourcepub fn monoisotopic_mz(&self) -> f64
pub fn monoisotopic_mz(&self) -> f64
Examples found in repository?
108fn tracer_with_ms2(path: &std::path::Path, config: &KothConfig) {
109 // `run_pipeline_with_ms2` forces MS2 emission on; equivalently, set
110 // `PipelineOptions { emit_ms2: true, ..Default::default() }` on any entry.
111 let out = run_pipeline_with_ms2(path, config, &PipelineOptions::default())
112 .expect("pipeline with ms2");
113
114 // Group the flat MS2 hill set into DIA channels (each hill already carries
115 // its window in `hill.isolation_window`; this is just the convenience view).
116 let windows: Vec<(IsolationWindow, Vec<Hill>)> = group_ms2_hills_by_window(out.ms2_hills);
117 println!(
118 "[tracer+ms2] {} MS1 features, {} DIA isolation windows",
119 out.features.len(),
120 windows.len()
121 );
122
123 // Precursor -> window mapping: a precursor feature is isolated by every
124 // window whose [lower, upper] contains its (charge-1) precursor m/z.
125 for f in out.features.iter().filter(|f| f.feature.charge > 0).take(3) {
126 let mz = f.feature.monoisotopic_mz(); // precursor m/z of the feature
127 let isolating: Vec<&(IsolationWindow, Vec<Hill>)> = windows
128 .iter()
129 .filter(|(w, _)| w.lower <= mz && mz <= w.upper)
130 .collect();
131 let n_frag: usize = isolating.iter().map(|(_, hs)| hs.len()).sum();
132 println!(
133 "[tracer+ms2] precursor m/z {:.4} z{} -> {} window(s), {} candidate fragment hills",
134 mz,
135 f.feature.charge,
136 isolating.len(),
137 n_frag
138 );
139 }
140}Sourcepub fn faims_cv(&self) -> Option<f32>
pub fn faims_cv(&self) -> Option<f32>
FAIMS compensation voltage of this feature, inherited from its monoisotopic hill. Feature assembly guarantees all member hills share this value.
Sourcepub fn monoisotopic_neutral_mass(&self, polarity: Polarity) -> Option<f64>
pub fn monoisotopic_neutral_mass(&self, polarity: Polarity) -> Option<f64>
Neutral monoisotopic mass, undoing polarity’s adduct (mz·z ∓ z·proton).
Sourcepub fn rt_apex(&self) -> f64
pub fn rt_apex(&self) -> f64
Examples found in repository?
8fn main() -> anyhow::Result<()> {
9 let args: Vec<_> = std::env::args().skip(1).collect();
10 anyhow::ensure!(
11 args.len() == 5,
12 "usage: lfq_member_geometry BATCH CONFIG MEMBERS_TSV OUT_TSV ID,ID"
13 );
14 let cfg = AlignConfig::from_toml(&PathBuf::from(&args[1]))?;
15 let paths = discover_runs(&PathBuf::from(&args[0]))?;
16 let runs: Vec<_> = paths
17 .iter()
18 .map(|r| {
19 Ok(RunInput {
20 name: r.name.clone(),
21 features: read_features(&r.features_path)?,
22 hills: Vec::new(),
23 scan_times: Vec::new(),
24 rt_bounds: None,
25 })
26 })
27 .collect::<anyhow::Result<_>>()?;
28 check_batch_mobility_scales(
29 paths
30 .iter()
31 .zip(&runs)
32 .map(|(p, r)| (p, r.features.as_slice())),
33 )?;
34 let alignment = align_runs(&runs, &cfg.alignment);
35 let ids: HashSet<usize> = args[4]
36 .split(',')
37 .map(str::parse)
38 .collect::<Result<_, _>>()?;
39 let mut input = csv::ReaderBuilder::new()
40 .delimiter(b'\t')
41 .from_path(&args[2])?;
42 let mut output = csv::WriterBuilder::new()
43 .delimiter(b'\t')
44 .from_path(&args[3])?;
45 output.write_record([
46 "candidate_id",
47 "run_id",
48 "feature_idx",
49 "mass",
50 "rt",
51 "im",
52 "quality",
53 "is_primary",
54 "neutron_offset",
55 ])?;
56 for row in input.records() {
57 let row = row?;
58 let id: usize = row[0].parse()?;
59 if !ids.contains(&id) {
60 continue;
61 }
62 let r: usize = row[1].parse()?;
63 let f: usize = row[2].parse()?;
64 let feat = &runs[r].features[f];
65 let mz = feat.monoisotopic_mz();
66 let rt = feat.feature.rt_apex();
67 let im = feat.feature.im_apex();
68 let (mz, rt, im) = if r == alignment.reference_idx {
69 (mz, rt, im)
70 } else {
71 let a = &alignment.alignments[&runs[r].name];
72 (a.correct_mz(mz, rt), a.warp_rt(rt), a.correct_im(im, rt))
73 };
74 let mass = (mz - 1.007276466621) * feat.feature.charge as f64;
75 output.write_record([
76 id.to_string(),
77 r.to_string(),
78 f.to_string(),
79 mass.to_string(),
80 rt.to_string(),
81 im.to_string(),
82 feat.combined_score.to_string(),
83 row[3].to_string(),
84 feat.neutron_offset.to_string(),
85 ])?;
86 }
87 output.flush()?;
88 Ok(())
89}pub fn rt_start(&self) -> f64
pub fn rt_end(&self) -> f64
Sourcepub fn im_apex(&self) -> f64
pub fn im_apex(&self) -> f64
Examples found in repository?
8fn main() -> anyhow::Result<()> {
9 let args: Vec<_> = std::env::args().skip(1).collect();
10 anyhow::ensure!(
11 args.len() == 5,
12 "usage: lfq_member_geometry BATCH CONFIG MEMBERS_TSV OUT_TSV ID,ID"
13 );
14 let cfg = AlignConfig::from_toml(&PathBuf::from(&args[1]))?;
15 let paths = discover_runs(&PathBuf::from(&args[0]))?;
16 let runs: Vec<_> = paths
17 .iter()
18 .map(|r| {
19 Ok(RunInput {
20 name: r.name.clone(),
21 features: read_features(&r.features_path)?,
22 hills: Vec::new(),
23 scan_times: Vec::new(),
24 rt_bounds: None,
25 })
26 })
27 .collect::<anyhow::Result<_>>()?;
28 check_batch_mobility_scales(
29 paths
30 .iter()
31 .zip(&runs)
32 .map(|(p, r)| (p, r.features.as_slice())),
33 )?;
34 let alignment = align_runs(&runs, &cfg.alignment);
35 let ids: HashSet<usize> = args[4]
36 .split(',')
37 .map(str::parse)
38 .collect::<Result<_, _>>()?;
39 let mut input = csv::ReaderBuilder::new()
40 .delimiter(b'\t')
41 .from_path(&args[2])?;
42 let mut output = csv::WriterBuilder::new()
43 .delimiter(b'\t')
44 .from_path(&args[3])?;
45 output.write_record([
46 "candidate_id",
47 "run_id",
48 "feature_idx",
49 "mass",
50 "rt",
51 "im",
52 "quality",
53 "is_primary",
54 "neutron_offset",
55 ])?;
56 for row in input.records() {
57 let row = row?;
58 let id: usize = row[0].parse()?;
59 if !ids.contains(&id) {
60 continue;
61 }
62 let r: usize = row[1].parse()?;
63 let f: usize = row[2].parse()?;
64 let feat = &runs[r].features[f];
65 let mz = feat.monoisotopic_mz();
66 let rt = feat.feature.rt_apex();
67 let im = feat.feature.im_apex();
68 let (mz, rt, im) = if r == alignment.reference_idx {
69 (mz, rt, im)
70 } else {
71 let a = &alignment.alignments[&runs[r].name];
72 (a.correct_mz(mz, rt), a.warp_rt(rt), a.correct_im(im, rt))
73 };
74 let mass = (mz - 1.007276466621) * feat.feature.charge as f64;
75 output.write_record([
76 id.to_string(),
77 r.to_string(),
78 f.to_string(),
79 mass.to_string(),
80 rt.to_string(),
81 im.to_string(),
82 feat.combined_score.to_string(),
83 row[3].to_string(),
84 feat.neutron_offset.to_string(),
85 ])?;
86 }
87 output.flush()?;
88 Ok(())
89}Sourcepub fn total_intensity(&self) -> f64
pub fn total_intensity(&self) -> f64
Examples found in repository?
30fn uno_style(path: &std::path::Path, config: &KothConfig) {
31 // One call, everything owned in memory. No files touched.
32 let out = run_pipeline(path, config, &PipelineOptions::default()).expect("pipeline");
33
34 // uno searches MS1 features by neutral mass + charge + RT.
35 let searchable: Vec<&ScoredFeature> = out
36 .features
37 .iter()
38 .filter(|f| f.feature.charge > 0) // same set the features file would hold
39 .collect();
40
41 println!(
42 "[uno] {} hills, {} charged features available for MS1 search",
43 out.hills.len(),
44 searchable.len()
45 );
46 if let Some(top) = searchable.iter().max_by(|a, b| {
47 a.feature
48 .total_intensity()
49 .partial_cmp(&b.feature.total_intensity())
50 .unwrap()
51 }) {
52 println!(
53 "[uno] most intense: mass {:?} z{} score {:.3}",
54 top.monoisotopic_neutral_mass(koth_ms::Polarity::Positive),
55 top.feature.charge,
56 top.combined_score
57 );
58 }
59}
60
61// ---------------------------------------------------------------------------
62// koth_tracer-style consumer: take ownership of the hill set to build a trace
63// index, then fold features incrementally without holding the whole vector.
64// ---------------------------------------------------------------------------
65#[derive(Default)]
66struct TracerSink {
67 hills: Vec<Hill>, // owned once, up front — used to trace precursor<->fragment
68 n_features: usize,
69 total_intensity: f64,
70}
71
72impl PipelineSink for TracerSink {
73 fn on_hills(&mut self, hills: Vec<Hill>) {
74 // koth_tracer indexes the finalized hill set here (m/z, RT, IM) so it
75 // can attribute fragment hills to precursor features as they arrive.
76 println!("[tracer] indexing {} finalized hills", hills.len());
77 self.hills = hills;
78 }
79
80 fn on_feature(&mut self, feature: ScoredFeature) {
81 // Consume-and-drop: fold each feature into whatever the tracer keeps.
82 // The full feature vector is never materialized on the consumer side.
83 let f: &Feature = &feature.feature;
84 self.total_intensity += f.total_intensity();
85 self.n_features += 1;
86 // `feature` is dropped here, freeing its memory before the next arrives.
87 }pub fn n_scans_total(&self) -> usize
Sourcepub fn elution_profile(&self) -> (usize, usize, Vec<f64>)
pub fn elution_profile(&self) -> (usize, usize, Vec<f64>)
Combined elution profile: sum of all isotope hill intensities per scan. Returns (min_scan, max_scan, profile_vec).
Sourcepub fn total_intensity_at_apex(&self) -> f64
pub fn total_intensity_at_apex(&self) -> f64
Total intensity at the apex scan (summed across all isotope hills).
Sourcepub fn apex_scan(&self) -> usize
pub fn apex_scan(&self) -> usize
Apex scan index: scan where the sum of all isotope hill intensities is highest.
Sourcepub fn isotope_profile_apex(&self) -> Vec<f64>
pub fn isotope_profile_apex(&self) -> Vec<f64>
Isotope profile at the apex scan: one intensity per hill.
Sourcepub fn mono_scan_list(&self) -> Vec<usize>
pub fn mono_scan_list(&self) -> Vec<usize>
Scan list for monoisotopic hill.
Sourcepub fn mono_intensity_list(&self) -> &[f32]
pub fn mono_intensity_list(&self) -> &[f32]
Intensity list for monoisotopic hill.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Feature
impl RefUnwindSafe for Feature
impl Send for Feature
impl Sync for Feature
impl Unpin for Feature
impl UnsafeUnpin for Feature
impl UnwindSafe for Feature
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.