///|
/// Configuration for a reproducible analysis pipeline.
pub struct ProcessingConfig {
remove_mean : Bool
remove_trend : Bool
taper_fraction : Double
lowpass_frequency : Double?
highpass_frequency : Double?
fir_cutoff : Double?
fir_taps : Int
resample_rate : Double?
zero_phase : Bool
quality_clip_limit : Double
quality_flat_tolerance : Double
}
///|
/// Output of the deterministic processing pipeline.
pub struct ProcessingResult {
original : TraceSummary
processed : Trace
quality : QualityReport
dominant_frequency : Double
events : Array[EventCandidate]
}
///|
/// Return a conservative pipeline configuration.
pub fn default_processing_config() -> ProcessingConfig {
{
remove_mean: true,
remove_trend: true,
taper_fraction: 0.05,
lowpass_frequency: None,
highpass_frequency: None,
fir_cutoff: None,
fir_taps: 31,
resample_rate: None,
zero_phase: true,
quality_clip_limit: 1.0e6,
quality_flat_tolerance: 0.0000001,
}
}
///|
/// Apply a configured sequence of preprocessing operations.
pub fn process_trace(
trace : Trace,
config : ProcessingConfig,
) -> Result[Trace, String] {
let validation = validate_trace(trace)
match validation {
Err(message) => Err(message)
Ok(_) => {
let mut result = preprocess_trace(
trace,
config.remove_mean,
config.remove_trend,
config.taper_fraction,
)
match config.highpass_frequency {
Some(frequency) => highpass(result, frequency, config.zero_phase)
None => ()
}
match config.lowpass_frequency {
Some(frequency) => lowpass(result, frequency, config.zero_phase)
None => ()
}
match config.fir_cutoff {
Some(cutoff) =>
match
design_fir_lowpass(
result.sampling_rate,
cutoff,
config.fir_taps,
fir_hann(),
) {
Ok(filter) =>
result = filter_trace_fir(result, filter, config.zero_phase)
Err(message) => return Err(message)
}
None => ()
}
match config.resample_rate {
Some(rate) => result = resample_trace(result, rate)
None => ()
}
Ok(result)
}
}
}
///|
/// Run preprocessing, QC, spectral analysis, and event detection together.
pub fn analyze_trace(
trace : Trace,
config : ProcessingConfig,
) -> Result[ProcessingResult, String] {
let original = summarize_trace(trace)
let processed = match process_trace(trace, config) {
Ok(value) => value
Err(message) => return Err(message)
}
let quality = assess_quality(
processed,
config.quality_clip_limit,
config.quality_flat_tolerance,
)
let spectrum = hann_periodogram(processed.data, processed.sampling_rate)
let detector = default_event_config(processed.delta(), processed.start_time)
Ok({
original,
processed,
quality,
dominant_frequency: dominant_frequency(spectrum),
events: detect_events(processed.data, detector),
})
}
///|
/// Run a low-pass processing shortcut with documented defaults.
pub fn clean_seismic_trace(
trace : Trace,
cutoff : Double,
) -> Result[Trace, String] {
let config = {
remove_mean: true,
remove_trend: true,
taper_fraction: 0.05,
lowpass_frequency: Some(cutoff),
highpass_frequency: None,
fir_cutoff: None,
fir_taps: 31,
resample_rate: None,
zero_phase: true,
quality_clip_limit: 1.0e6,
quality_flat_tolerance: 0.0000001,
}
process_trace(trace, config)
}
///|
/// Return a quality score after processing a trace.
pub fn processed_quality_score(
trace : Trace,
config : ProcessingConfig,
) -> Result[Double, String] {
match analyze_trace(trace, config) {
Ok(result) => Ok(result.quality.score)
Err(message) => Err(message)
}
}
///|
/// Return the strongest event from an analysis result.
pub fn strongest_processed_event(result : ProcessingResult) -> EventCandidate? {
strongest_event(result.events)
}
///|
/// Produce a compact pipeline status line for the CLI.
pub fn processing_status(result : ProcessingResult) -> String {
"samples=" +
result.processed.npts().to_string() +
" quality=" +
result.quality.score.to_string() +
" dominant_hz=" +
result.dominant_frequency.to_string() +
" events=" +
result.events.length().to_string()
}