From 8f44cf56d45ea634b429ee8456672c2c05fa65f2 Mon Sep 17 00:00:00 2001 From: pober Date: Tue, 21 Apr 2026 16:03:46 +0200 Subject: [PATCH] Deviation hist. --- config.yaml | 2 +- src/audio_tests.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index 99a66f1..1c9f13e 100644 --- a/config.yaml +++ b/config.yaml @@ -31,7 +31,7 @@ artifact_detection: threshold_db: -60 # Detect unexpected frequencies above noise floor + this threshold (more negative = less sensitive) amplitude_spikes: enabled: true - threshold_factor: 7.0 # MAD-based outlier detection on envelope (detects clicks, pops, dropouts). Lower = more sensitive. + threshold_factor: 10.0 # MAD-based outlier detection on envelope (detects clicks, pops, dropouts). Lower = more sensitive. zero_crossing: enabled: false threshold_factor: 2.0 # Number of standard deviations for zero-crossing anomalies (detects distortion) diff --git a/src/audio_tests.py b/src/audio_tests.py index a4ec4d8..8aaa324 100644 --- a/src/audio_tests.py +++ b/src/audio_tests.py @@ -722,6 +722,60 @@ def plot_artifact_detection(channel_1: np.ndarray, channel_2: np.ndarray, plt.close() +def plot_deviation_histogram(artifacts_ch1: Dict, artifacts_ch2: Dict, output_dir: Path): + def get_deviations(artifacts_dict): + values = [] + for a in artifacts_dict.get('artifacts', []): + if 'deviation_factor' in a: + values.append(a['deviation_factor']) + return values + + dev_ch1 = get_deviations(artifacts_ch1) + dev_ch2 = get_deviations(artifacts_ch2) + + all_devs = dev_ch1 + dev_ch2 + if not all_devs: + return + + bin_min = int(np.floor(min(all_devs))) + bin_max = int(np.ceil(max(all_devs))) + 1 + bins = np.arange(bin_min, bin_max + 1) + + counts_ch1, _ = np.histogram(dev_ch1, bins=bins) + counts_ch2, _ = np.histogram(dev_ch2, bins=bins) + + bin_labels = [f"{bins[i]}-{bins[i+1]}" for i in range(len(bins) - 1)] + x = np.arange(len(bin_labels)) + width = 0.4 + + fig, ax = plt.subplots(figsize=(max(10, len(bin_labels) * 0.7), 6)) + bars1 = ax.bar(x - width / 2, counts_ch1, width, label='Ch1 Loopback', color='steelblue', alpha=0.85) + bars2 = ax.bar(x + width / 2, counts_ch2, width, label='Ch2 DUT/Radio', color='tomato', alpha=0.85) + + ax.set_xlabel('Deviation Factor (σ)') + ax.set_ylabel('Count') + ax.set_title('Artifact Deviation Factor Distribution') + ax.set_xticks(x) + ax.set_xticklabels(bin_labels, rotation=45, ha='right') + ax.yaxis.get_major_locator().set_params(integer=True) + ax.legend() + ax.grid(True, axis='y', alpha=0.3) + + for bar in bars1: + if bar.get_height() > 0: + ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.05, + str(int(bar.get_height())), ha='center', va='bottom', fontsize=8) + for bar in bars2: + if bar.get_height() > 0: + ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.05, + str(int(bar.get_height())), ha='center', va='bottom', fontsize=8) + + plt.tight_layout() + plot_file = output_dir / 'artifact_deviation_histogram.png' + plt.savefig(plot_file, dpi=150, bbox_inches='tight') + plt.close() + + def run_artifact_detection_test(config: Dict, save_plots: bool = False, output_dir: Path = None) -> Dict: import time @@ -765,6 +819,7 @@ def run_artifact_detection_test(config: Dict, save_plots: bool = False, output_d if save_plots and output_dir: plot_artifact_detection(channel_1, channel_2, artifacts_ch1, artifacts_ch2, frequency, sample_rate, output_dir) + plot_deviation_histogram(artifacts_ch1, artifacts_ch2, output_dir) anomalies_dir = output_dir / 'individual_anomalies' anomalies_dir.mkdir(exist_ok=True)