///|
/// Quality record for one phase pick.
pub struct PhasePickQuality {
station : String
phase : String
pick_time : Double
snr_db : Double
polarization_score : Double
waveform_score : Double
residual_seconds : Double
confidence : Double
status : String
}
///|
/// Compute a combined phase-pick confidence score.
pub fn phase_pick_confidence(
snr_db : Double,
polarization : Double,
waveform : Double,
residual_seconds : Double,
) -> Double {
let snr_score = ((snr_db + 5.0) / 35.0).max(0.0).min(1.0)
let residual_score = @math.exp(-residual_seconds.abs() / 2.0)
(0.35 * snr_score +
0.35 * polarization.max(0.0).min(1.0) +
0.2 * waveform.max(0.0).min(1.0) +
0.1 * residual_score).min(1.0)
}
///|
/// Convert confidence into an operator review status.
pub fn phase_pick_status(confidence : Double) -> String {
if confidence >= 0.8 {
"accepted"
} else if confidence >= 0.55 {
"review"
} else {
"rejected"
}
}
///|
/// Build a phase quality record.
pub fn make_phase_pick_quality(
station : String,
phase : String,
pick_time : Double,
snr_db : Double,
polarization : Double,
waveform : Double,
residual_seconds : Double,
) -> PhasePickQuality {
let confidence = phase_pick_confidence(
snr_db, polarization, waveform, residual_seconds,
)
{
station,
phase,
pick_time,
snr_db,
polarization_score: polarization,
waveform_score: waveform,
residual_seconds,
confidence,
status: phase_pick_status(confidence),
}
}
///|
/// Return accepted picks from a quality set.
pub fn accepted_phase_picks(
picks : Array[PhasePickQuality],
) -> Array[PhasePickQuality] {
picks.filter(pick => pick.status == "accepted")
}
///|
/// Compute median time difference between two phase sets.
pub fn phase_pick_time_difference(
first : Array[PhasePickQuality],
second : Array[PhasePickQuality],
) -> Double {
let differences = Array::new()
for left in first {
for right in second {
if left.station == right.station {
differences.push(right.pick_time - left.pick_time)
}
}
}
median_samples(differences)
}
///|
/// Export phase-pick quality rows.
pub fn phase_pick_quality_to_csv(picks : Array[PhasePickQuality]) -> String {
let mut output = "station,phase,pick_time,snr_db,polarization,waveform,residual,confidence,status\n"
for pick in picks {
output = output +
pick.station +
"," +
pick.phase +
"," +
pick.pick_time.to_string() +
"," +
pick.snr_db.to_string() +
"," +
pick.polarization_score.to_string() +
"," +
pick.waveform_score.to_string() +
"," +
pick.residual_seconds.to_string() +
"," +
pick.confidence.to_string() +
"," +
pick.status +
"\n"
}
output
}
///|
/// Format phase-pick quality summary.
pub fn phase_pick_quality_text(pick : PhasePickQuality) -> String {
"station=" +
pick.station +
" phase=" +
pick.phase +
" time=" +
pick.pick_time.to_string() +
" snr_db=" +
pick.snr_db.to_string() +
" confidence=" +
pick.confidence.to_string() +
" status=" +
pick.status
}