use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HillsConfig {
pub min_scans: usize,
pub max_gap: usize,
pub split_hills: bool,
#[serde(default = "default_split_valley_ratio")]
pub split_valley_ratio: f64,
#[serde(default = "default_split_sigma_mult")]
pub split_sigma_mult: f64,
#[serde(default = "default_split_height_frac")]
pub split_height_frac: f64,
pub lfc_weight: f64,
#[serde(default)]
pub gap_fill_enabled: bool,
#[serde(default)]
pub smoothing_enabled: bool,
#[serde(default = "default_smoothing_window")]
pub smoothing_window: usize,
#[serde(default)]
pub filter_large_baseline_hills: bool,
#[serde(default = "default_large_hill_min_scans")]
pub large_hill_min_scans: usize,
#[serde(default = "default_large_hill_peak_factor")]
pub large_hill_peak_factor: f64,
#[serde(default)]
pub tic_norm_window: usize,
#[serde(default = "default_tic_norm_min_scale")]
pub tic_norm_min_scale: f64,
#[serde(default = "default_tic_norm_max_scale")]
pub tic_norm_max_scale: f64,
#[serde(default = "default_tic_norm_mode")]
pub tic_norm_mode: String,
}
fn default_smoothing_window() -> usize {
1
}
fn default_large_hill_min_scans() -> usize {
40
}
fn default_large_hill_peak_factor() -> f64 {
2.0
}
fn default_tic_norm_min_scale() -> f64 {
0.5
}
fn default_tic_norm_max_scale() -> f64 {
3.0
}
fn default_tic_norm_mode() -> String {
"median".to_string()
}
fn default_split_valley_ratio() -> f64 {
0.60
}
fn default_split_sigma_mult() -> f64 {
5.0
}
fn default_split_height_frac() -> f64 {
0.10
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HillsMs2Overrides {
pub min_scans: Option<usize>,
pub max_gap: Option<usize>,
pub split_hills: Option<bool>,
pub split_valley_ratio: Option<f64>,
pub split_sigma_mult: Option<f64>,
pub split_height_frac: Option<f64>,
pub lfc_weight: Option<f64>,
pub gap_fill_enabled: Option<bool>,
pub smoothing_enabled: Option<bool>,
pub smoothing_window: Option<usize>,
pub filter_large_baseline_hills: Option<bool>,
pub large_hill_min_scans: Option<usize>,
pub large_hill_peak_factor: Option<f64>,
pub tic_norm_window: Option<usize>,
pub tic_norm_min_scale: Option<f64>,
pub tic_norm_max_scale: Option<f64>,
pub tic_norm_mode: Option<String>,
}
impl HillsMs2Overrides {
pub fn apply_to(&self, base: &HillsConfig) -> HillsConfig {
let mut c = base.clone();
if let Some(v) = self.min_scans {
c.min_scans = v;
}
if let Some(v) = self.max_gap {
c.max_gap = v;
}
if let Some(v) = self.split_hills {
c.split_hills = v;
}
if let Some(v) = self.split_valley_ratio {
c.split_valley_ratio = v;
}
if let Some(v) = self.split_sigma_mult {
c.split_sigma_mult = v;
}
if let Some(v) = self.split_height_frac {
c.split_height_frac = v;
}
if let Some(v) = self.lfc_weight {
c.lfc_weight = v;
}
if let Some(v) = self.gap_fill_enabled {
c.gap_fill_enabled = v;
}
if let Some(v) = self.smoothing_enabled {
c.smoothing_enabled = v;
}
if let Some(v) = self.smoothing_window {
c.smoothing_window = v;
}
if let Some(v) = self.filter_large_baseline_hills {
c.filter_large_baseline_hills = v;
}
if let Some(v) = self.large_hill_min_scans {
c.large_hill_min_scans = v;
}
if let Some(v) = self.large_hill_peak_factor {
c.large_hill_peak_factor = v;
}
if let Some(v) = self.tic_norm_window {
c.tic_norm_window = v;
}
if let Some(v) = self.tic_norm_min_scale {
c.tic_norm_min_scale = v;
}
if let Some(v) = self.tic_norm_max_scale {
c.tic_norm_max_scale = v;
}
if let Some(ref v) = self.tic_norm_mode {
c.tic_norm_mode = v.clone();
}
c
}
}
impl Default for HillsConfig {
fn default() -> Self {
Self {
min_scans: 3,
max_gap: 1,
split_hills: true,
split_valley_ratio: default_split_valley_ratio(),
split_sigma_mult: default_split_sigma_mult(),
split_height_frac: default_split_height_frac(),
lfc_weight: 0.3,
gap_fill_enabled: false,
smoothing_enabled: false,
smoothing_window: 1,
filter_large_baseline_hills: false,
large_hill_min_scans: default_large_hill_min_scans(),
large_hill_peak_factor: default_large_hill_peak_factor(),
tic_norm_window: 0,
tic_norm_min_scale: default_tic_norm_min_scale(),
tic_norm_max_scale: default_tic_norm_max_scale(),
tic_norm_mode: default_tic_norm_mode(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_override_field_is_applied() {
let base = HillsConfig {
min_scans: 5,
max_gap: 2,
split_hills: false,
split_valley_ratio: 0.5,
split_sigma_mult: 3.0,
split_height_frac: 0.2,
lfc_weight: 0.7,
gap_fill_enabled: true,
smoothing_enabled: true,
smoothing_window: 3,
filter_large_baseline_hills: true,
large_hill_min_scans: 30,
large_hill_peak_factor: 1.5,
tic_norm_window: 7,
tic_norm_min_scale: 0.4,
tic_norm_max_scale: 2.5,
tic_norm_mode: "tic".to_string(),
};
let ov = HillsMs2Overrides {
min_scans: Some(9),
max_gap: Some(4),
split_hills: Some(true),
split_valley_ratio: Some(0.8),
split_sigma_mult: Some(6.0),
split_height_frac: Some(0.3),
lfc_weight: Some(0.9),
gap_fill_enabled: Some(false),
smoothing_enabled: Some(false),
smoothing_window: Some(6),
filter_large_baseline_hills: Some(false),
large_hill_min_scans: Some(50),
large_hill_peak_factor: Some(3.0),
tic_norm_window: Some(11),
tic_norm_min_scale: Some(0.6),
tic_norm_max_scale: Some(4.0),
tic_norm_mode: Some("median".to_string()),
};
let got = ov.apply_to(&base);
assert_ne!(got.min_scans, base.min_scans);
assert_ne!(got.max_gap, base.max_gap);
assert_ne!(got.split_hills, base.split_hills);
assert_ne!(got.split_valley_ratio, base.split_valley_ratio);
assert_ne!(got.split_sigma_mult, base.split_sigma_mult);
assert_ne!(got.split_height_frac, base.split_height_frac);
assert_ne!(got.lfc_weight, base.lfc_weight);
assert_ne!(got.gap_fill_enabled, base.gap_fill_enabled);
assert_ne!(got.smoothing_enabled, base.smoothing_enabled);
assert_ne!(got.smoothing_window, base.smoothing_window);
assert_ne!(
got.filter_large_baseline_hills,
base.filter_large_baseline_hills
);
assert_ne!(got.large_hill_min_scans, base.large_hill_min_scans);
assert_ne!(got.large_hill_peak_factor, base.large_hill_peak_factor);
assert_ne!(got.tic_norm_window, base.tic_norm_window);
assert_ne!(got.tic_norm_min_scale, base.tic_norm_min_scale);
assert_ne!(got.tic_norm_max_scale, base.tic_norm_max_scale);
assert_ne!(got.tic_norm_mode, base.tic_norm_mode);
assert_eq!(got.min_scans, 9);
assert_eq!(got.tic_norm_mode, "median");
}
#[test]
fn none_override_keeps_base_value() {
let base = HillsConfig {
min_scans: 5,
..HillsConfig::default()
};
let got = HillsMs2Overrides::default().apply_to(&base);
assert_eq!(got.min_scans, 5);
}
}