clustaudio 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clustaudio/__init__.py +87 -0
- clustaudio/atc_example.py +378 -0
- clustaudio/clustaudio.py +2915 -0
- clustaudio/examples.py +779 -0
- clustaudio/fingerprint.py +353 -0
- clustaudio/metadata.py +364 -0
- clustaudio/separation.py +617 -0
- clustaudio/tests/audio/atc/ACCU-0agmXf.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-1DTI8r.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-1ZvQqb.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-2nUNxX.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-3qIKv5.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-4BTi3A.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-4U0PXR.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-5n3Cmr.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-6ENnqU.wav +0 -0
- clustaudio/tests/audio/atc/ACCU-6y7b3L.wav +0 -0
- clustaudio/tests/audio/various/Gunshot_and_gunfire_1.wav +0 -0
- clustaudio/tests/audio/various/Gunshot_and_gunfire_2.wav +0 -0
- clustaudio/tests/audio/various/Gunshot_and_gunfire_3.wav +0 -0
- clustaudio/tests/audio/various/Knock_2.wav +0 -0
- clustaudio/tests/audio/various/Knock_3.wav +0 -0
- clustaudio/tests/audio/various/Knock_4.wav +0 -0
- clustaudio/tests/audio/various/Splash_and_splatter_1.wav +0 -0
- clustaudio/tests/audio/various/Splash_and_splatter_2.wav +0 -0
- clustaudio/tests/audio/various/Splash_and_splatter_3.wav +0 -0
- clustaudio/tests/audio/various/dog_barking_1.wav +0 -0
- clustaudio/tests/audio/various/dog_barking_2.wav +0 -0
- clustaudio/tests/audio/various/dog_barking_3.wav +0 -0
- clustaudio/tests/audio/various/drilling_1.wav +0 -0
- clustaudio/tests/audio/various/drilling_2.wav +0 -0
- clustaudio/tests/audio/various/drilling_3.wav +0 -0
- clustaudio/tests/audio/various/siren_1.wav +0 -0
- clustaudio/tests/audio/various/siren_2.wav +0 -0
- clustaudio/tests/audio/various/siren_3.wav +0 -0
- clustaudio/vad.py +380 -0
- clustaudio/visualization.py +592 -0
- clustaudio-0.1.0.dist-info/METADATA +607 -0
- clustaudio-0.1.0.dist-info/RECORD +42 -0
- clustaudio-0.1.0.dist-info/WHEEL +5 -0
- clustaudio-0.1.0.dist-info/licenses/LICENSE +22 -0
- clustaudio-0.1.0.dist-info/top_level.txt +1 -0
clustaudio/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from clustaudio.clustaudio import Clustaudio
|
|
4
|
+
|
|
5
|
+
import clustaudio.vad as vad
|
|
6
|
+
import clustaudio.separation as separation
|
|
7
|
+
import clustaudio.visualization as visualization
|
|
8
|
+
import clustaudio.visualization as visualization
|
|
9
|
+
import clustaudio.fingerprint as fingerprint
|
|
10
|
+
import clustaudio.metadata as metadata
|
|
11
|
+
|
|
12
|
+
from clustaudio.clustaudio import (
|
|
13
|
+
import_example,
|
|
14
|
+
listdir,
|
|
15
|
+
set_logger,
|
|
16
|
+
move_files,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__author__ = 'Erdogan Tasksen'
|
|
21
|
+
__email__ = 'erdogant@gmail.com'
|
|
22
|
+
__version__ = '0.1.0'
|
|
23
|
+
|
|
24
|
+
# Setup root logger
|
|
25
|
+
_logger = logging.getLogger('clustaudio')
|
|
26
|
+
_log_handler = logging.StreamHandler()
|
|
27
|
+
_fmt = '[{asctime}] [{name}] [{levelname}] {msg}'
|
|
28
|
+
_formatter = logging.Formatter(fmt=_fmt, style='{', datefmt='%d-%m-%Y %H:%M:%S')
|
|
29
|
+
_log_handler.setFormatter(_formatter)
|
|
30
|
+
_log_handler.setLevel(logging.DEBUG)
|
|
31
|
+
_logger.addHandler(_log_handler)
|
|
32
|
+
_logger.propagate = False
|
|
33
|
+
|
|
34
|
+
# module level doc-string
|
|
35
|
+
__doc__ = """
|
|
36
|
+
clustaudio
|
|
37
|
+
=====================================================================
|
|
38
|
+
|
|
39
|
+
Python package clustaudio is to detect natural groups or clusters of images.
|
|
40
|
+
|
|
41
|
+
The aim of ``clustaudio`` is to detect natural groups or clusters of images. It works using a multi-step proces of carefully pre-processing the images, extracting the features, and evaluating the optimal number of clusters across the feature space.
|
|
42
|
+
The optimal number of clusters are determined using well known methods suchs as *silhouette, dbindex, and derivatives* in combination with clustering methods, such as *agglomerative, kmeans, dbscan and hdbscan*.
|
|
43
|
+
With ``clustaudio`` we aim to determine the most robust clustering by efficiently searching across the parameter and evaluation the clusters.
|
|
44
|
+
Besides clustering of images, the ``clustaudio`` model can also be used to find the most similar images for a new unseen sample.
|
|
45
|
+
|
|
46
|
+
Example
|
|
47
|
+
-------
|
|
48
|
+
>>> from clustaudio import clustaudio
|
|
49
|
+
>>>
|
|
50
|
+
>>> # Init with default settings
|
|
51
|
+
>>> cl = clustaudio(method='pca')
|
|
52
|
+
>>>
|
|
53
|
+
>>> # load example dataset
|
|
54
|
+
>>> X, y = cl.import_example(data='mnist')
|
|
55
|
+
>>>
|
|
56
|
+
>>> # Cluster digits
|
|
57
|
+
>>> results = cl.fit_transform(X)
|
|
58
|
+
>>>
|
|
59
|
+
>>> # Cluster evaluation
|
|
60
|
+
>>> cl.clustaudio.plot()
|
|
61
|
+
>>> cl.clustaudio.scatter(cl.results['xycoord'])
|
|
62
|
+
>>> cl.pca.plot()
|
|
63
|
+
>>>
|
|
64
|
+
>>> # Unique
|
|
65
|
+
>>> cl.plot_unique(img_mean=False)
|
|
66
|
+
>>> cl.results_unique.keys()
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Scatter
|
|
69
|
+
>>> cl.scatter(img_mean=False, zoom=3)
|
|
70
|
+
>>>
|
|
71
|
+
>>> # Plot clustered images
|
|
72
|
+
>>> cl.plot(labels=8)
|
|
73
|
+
>>>
|
|
74
|
+
>>> # Plot dendrogram
|
|
75
|
+
>>> cl.dendrogram()
|
|
76
|
+
>>>
|
|
77
|
+
>>> # Find images
|
|
78
|
+
>>> results_find = cl.find(X[0,:], k=None, alpha=0.05)
|
|
79
|
+
>>> cl.plot_find()
|
|
80
|
+
>>> cl.scatter()
|
|
81
|
+
>>>
|
|
82
|
+
|
|
83
|
+
References
|
|
84
|
+
----------
|
|
85
|
+
https://github.com/erdogant/clustaudio
|
|
86
|
+
|
|
87
|
+
"""
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Complete ATC Audio Clustering Example
|
|
3
|
+
======================================
|
|
4
|
+
|
|
5
|
+
This script demonstrates the full workflow for processing noisy
|
|
6
|
+
Air Traffic Control communications with clustaudio.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python atc_example.py --input ./ATC_recordings/ --output ./results/
|
|
10
|
+
|
|
11
|
+
Requirements:
|
|
12
|
+
pip install librosa soundfile numpy pandas scikit-learn scipy matplotlib tqdm
|
|
13
|
+
pip install pca distfit clustaudio scatterd colourmap datazets pypickle ismember
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import argparse
|
|
19
|
+
import numpy as np
|
|
20
|
+
import pandas as pd
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Import clustaudio
|
|
24
|
+
from clustaudio import clustaudio, listdir
|
|
25
|
+
import vad
|
|
26
|
+
import separation
|
|
27
|
+
import visualization as viz
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def process_atc_communications(input_dir, output_dir, enhancement_level='aggressive',
|
|
31
|
+
save_plots=True, verbose=True):
|
|
32
|
+
"""
|
|
33
|
+
Process ATC communications with full pipeline.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
input_dir : str
|
|
38
|
+
Directory containing ATC audio files
|
|
39
|
+
output_dir : str
|
|
40
|
+
Directory for output files
|
|
41
|
+
enhancement_level : str
|
|
42
|
+
'light', 'moderate', or 'aggressive'
|
|
43
|
+
save_plots : bool
|
|
44
|
+
Save visualization plots
|
|
45
|
+
verbose : bool
|
|
46
|
+
Print progress messages
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
dict
|
|
51
|
+
Results dictionary with all processing outputs
|
|
52
|
+
"""
|
|
53
|
+
# Create output directories
|
|
54
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
55
|
+
os.makedirs(os.path.join(output_dir, 'enhanced'), exist_ok=True)
|
|
56
|
+
os.makedirs(os.path.join(output_dir, 'separated'), exist_ok=True)
|
|
57
|
+
os.makedirs(os.path.join(output_dir, 'segments_voice'), exist_ok=True)
|
|
58
|
+
os.makedirs(os.path.join(output_dir, 'plots'), exist_ok=True)
|
|
59
|
+
os.makedirs(os.path.join(output_dir, 'organized'), exist_ok=True)
|
|
60
|
+
|
|
61
|
+
if verbose:
|
|
62
|
+
print("=" * 70)
|
|
63
|
+
print("ATC Audio Clustering Pipeline")
|
|
64
|
+
print("=" * 70)
|
|
65
|
+
print(f"Input: {input_dir}")
|
|
66
|
+
print(f"Output: {output_dir}")
|
|
67
|
+
print(f"Enhancement: {enhancement_level}")
|
|
68
|
+
print("=" * 70)
|
|
69
|
+
|
|
70
|
+
# Initialize clustaudio
|
|
71
|
+
if verbose:
|
|
72
|
+
print("\n[1/8] Initializing clustaudio...")
|
|
73
|
+
|
|
74
|
+
cl = clustaudio(
|
|
75
|
+
method='mfcc',
|
|
76
|
+
sr=16000, # Typical for radio communications
|
|
77
|
+
mono=True,
|
|
78
|
+
normalize=True,
|
|
79
|
+
params_mfcc={
|
|
80
|
+
'n_mfcc': 20,
|
|
81
|
+
'n_fft': 2048,
|
|
82
|
+
'hop_length': 512
|
|
83
|
+
},
|
|
84
|
+
verbose='info'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Get audio files
|
|
88
|
+
if verbose:
|
|
89
|
+
print("\n[2/8] Scanning for audio files...")
|
|
90
|
+
|
|
91
|
+
filepaths = listdir(input_dir, ext=['wav', 'mp3', 'flac', 'ogg', 'm4a'])
|
|
92
|
+
|
|
93
|
+
if len(filepaths) == 0:
|
|
94
|
+
print(f"ERROR: No audio files found in {input_dir}")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
if verbose:
|
|
98
|
+
print(f"Found {len(filepaths)} audio files")
|
|
99
|
+
|
|
100
|
+
# Step 1: Enhance ATC audio
|
|
101
|
+
if verbose:
|
|
102
|
+
print(f"\n[3/8] Enhancing audio (method: {enhancement_level})...")
|
|
103
|
+
|
|
104
|
+
enhanced = cl.enhance_atc_audio(
|
|
105
|
+
filepaths,
|
|
106
|
+
method=enhancement_level,
|
|
107
|
+
output_dir=os.path.join(output_dir, 'enhanced')
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
n_enhanced = len([p for p in enhanced['pathnames_enhanced'] if p is not None])
|
|
111
|
+
if verbose:
|
|
112
|
+
print(f"Enhanced {n_enhanced}/{len(filepaths)} files successfully")
|
|
113
|
+
|
|
114
|
+
# Step 2: Separate foreground (voice) from background (noise)
|
|
115
|
+
if verbose:
|
|
116
|
+
print("\n[4/8] Separating voice from noise...")
|
|
117
|
+
|
|
118
|
+
separated = cl.separate_foreground_background(
|
|
119
|
+
enhanced['pathnames_enhanced'],
|
|
120
|
+
method='spectral_masking',
|
|
121
|
+
denoise=False, # Already enhanced
|
|
122
|
+
output_dir=os.path.join(output_dir, 'separated'),
|
|
123
|
+
save_both=True
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
avg_snr = np.mean([s for s in separated['snr_db'] if s is not None])
|
|
127
|
+
if verbose:
|
|
128
|
+
print(f"Separated {len(separated['pathnames_foreground'])} files")
|
|
129
|
+
print(f"Average SNR: {avg_snr:.2f} dB")
|
|
130
|
+
|
|
131
|
+
# Step 3: Extract voice segments
|
|
132
|
+
if verbose:
|
|
133
|
+
print("\n[5/8] Extracting voice segments...")
|
|
134
|
+
|
|
135
|
+
segments_voice = vad.batch_extract_voice_segments(
|
|
136
|
+
separated['pathnames_foreground'],
|
|
137
|
+
output_dir=os.path.join(output_dir, 'segments_voice'),
|
|
138
|
+
sr=16000,
|
|
139
|
+
method='energy',
|
|
140
|
+
energy_threshold=0.015,
|
|
141
|
+
min_silence_duration=0.3,
|
|
142
|
+
min_segment_duration=0.5
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Collect voice segment paths
|
|
146
|
+
pathnames_voice = [
|
|
147
|
+
p
|
|
148
|
+
for seg in segments_voice
|
|
149
|
+
if seg.get("pathnames_segments")
|
|
150
|
+
for p in seg["pathnames_segments"]
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
if verbose:
|
|
154
|
+
print(f"Extracted {len(pathnames_voice)} voice segments from {len(segments_voice)} files")
|
|
155
|
+
avg_segments = len(pathnames_voice) / max(len(segments_voice), 1)
|
|
156
|
+
print(f"Average segments per file: {avg_segments:.1f}")
|
|
157
|
+
|
|
158
|
+
# Step 4: Cluster voice segments
|
|
159
|
+
if verbose:
|
|
160
|
+
print("\n[6/8] Clustering voice segments...")
|
|
161
|
+
|
|
162
|
+
if len(pathnames_voice) < 10:
|
|
163
|
+
print("WARNING: Too few voice segments for reliable clustering")
|
|
164
|
+
print(f"Found only {len(pathnames_voice)} segments (minimum 10 recommended)")
|
|
165
|
+
return {
|
|
166
|
+
'enhanced': enhanced,
|
|
167
|
+
'separated': separated,
|
|
168
|
+
'segments_voice': segments_voice,
|
|
169
|
+
'error': 'Too few segments'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
results_voice = cl.fit_transform(pathnames_voice)
|
|
173
|
+
|
|
174
|
+
labels_voice = cl.cluster(
|
|
175
|
+
min_clust=max(3, min(10, len(pathnames_voice) // 20)),
|
|
176
|
+
max_clust=min(25, len(pathnames_voice) // 10),
|
|
177
|
+
cluster='agglomerative',
|
|
178
|
+
evaluate='silhouette',
|
|
179
|
+
metric='cosine',
|
|
180
|
+
cluster_space='high'
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
n_voice_clusters = len(np.unique(labels_voice))
|
|
184
|
+
if verbose:
|
|
185
|
+
print(f"Found {n_voice_clusters} voice clusters")
|
|
186
|
+
for i in range(n_voice_clusters):
|
|
187
|
+
count = np.sum(labels_voice == i)
|
|
188
|
+
print(f" Cluster {i}: {count} segments")
|
|
189
|
+
|
|
190
|
+
# Step 5: Cluster background noise (optional but useful)
|
|
191
|
+
if verbose:
|
|
192
|
+
print("\n[7/8] Clustering background noise...")
|
|
193
|
+
|
|
194
|
+
results_noise = cl.fit_transform(separated['pathnames_background'])
|
|
195
|
+
|
|
196
|
+
labels_noise = cl.cluster(
|
|
197
|
+
min_clust=2,
|
|
198
|
+
max_clust=8,
|
|
199
|
+
cluster='dbscan',
|
|
200
|
+
metric='euclidean'
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
n_noise_clusters = len(np.unique(labels_noise))
|
|
204
|
+
if verbose:
|
|
205
|
+
print(f"Found {n_noise_clusters} noise types")
|
|
206
|
+
|
|
207
|
+
# Step 6: Generate visualizations
|
|
208
|
+
if save_plots and verbose:
|
|
209
|
+
print("\n[8/8] Generating visualizations...")
|
|
210
|
+
|
|
211
|
+
plots_dir = os.path.join(output_dir, 'plots')
|
|
212
|
+
|
|
213
|
+
if save_plots:
|
|
214
|
+
# Plot cluster statistics
|
|
215
|
+
try:
|
|
216
|
+
fig = cl.plot_statistics()
|
|
217
|
+
if fig:
|
|
218
|
+
viz.save_figure(fig, os.path.join(plots_dir, 'cluster_statistics.png'))
|
|
219
|
+
viz.close_all()
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f"Warning: Could not create statistics plot: {e}")
|
|
222
|
+
|
|
223
|
+
# Plot separation results (first 3 files)
|
|
224
|
+
for i in range(min(3, len(separated['results']))):
|
|
225
|
+
try:
|
|
226
|
+
fig = cl.plot_separation(index=i)
|
|
227
|
+
if fig:
|
|
228
|
+
viz.save_figure(fig, os.path.join(plots_dir, f'separation_{i}.png'))
|
|
229
|
+
viz.close_all()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"Warning: Could not create separation plot {i}: {e}")
|
|
232
|
+
|
|
233
|
+
if verbose:
|
|
234
|
+
print(f"Saved plots to {plots_dir}")
|
|
235
|
+
|
|
236
|
+
# Create reports
|
|
237
|
+
if verbose:
|
|
238
|
+
print("\nGenerating reports...")
|
|
239
|
+
|
|
240
|
+
# Voice cluster report
|
|
241
|
+
voice_report = pd.DataFrame({
|
|
242
|
+
'segment_path': pathnames_voice,
|
|
243
|
+
'voice_cluster': labels_voice
|
|
244
|
+
})
|
|
245
|
+
voice_report.to_csv(os.path.join(output_dir, 'voice_clusters.csv'), index=False)
|
|
246
|
+
|
|
247
|
+
# Combined report
|
|
248
|
+
combined_report = pd.DataFrame({
|
|
249
|
+
'original_file': [os.path.basename(r['pathname']) for r in separated['results']],
|
|
250
|
+
'enhanced_file': [os.path.basename(p) for p in enhanced['pathnames_enhanced']],
|
|
251
|
+
'foreground_file': [os.path.basename(p) if p else None for p in separated['pathnames_foreground']],
|
|
252
|
+
'background_file': [os.path.basename(p) if p else None for p in separated['pathnames_background']],
|
|
253
|
+
'snr_db': separated['snr_db'],
|
|
254
|
+
'noise_cluster': labels_noise
|
|
255
|
+
})
|
|
256
|
+
combined_report.to_csv(os.path.join(output_dir, 'processing_report.csv'), index=False)
|
|
257
|
+
|
|
258
|
+
# Summary statistics
|
|
259
|
+
summary = {
|
|
260
|
+
'total_files': len(filepaths),
|
|
261
|
+
'enhanced_files': n_enhanced,
|
|
262
|
+
'separated_files': len(separated['pathnames_foreground']),
|
|
263
|
+
'voice_segments': len(pathnames_voice),
|
|
264
|
+
'voice_clusters': n_voice_clusters,
|
|
265
|
+
'noise_clusters': n_noise_clusters,
|
|
266
|
+
'avg_snr_db': float(avg_snr),
|
|
267
|
+
'avg_segments_per_file': float(len(pathnames_voice) / max(len(segments_voice), 1))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# Save summary
|
|
271
|
+
summary_df = pd.DataFrame([summary])
|
|
272
|
+
summary_df.to_csv(os.path.join(output_dir, 'summary.csv'), index=False)
|
|
273
|
+
|
|
274
|
+
# Save model
|
|
275
|
+
model_path = os.path.join(output_dir, 'atc_model.pkl')
|
|
276
|
+
cl.save(model_path)
|
|
277
|
+
|
|
278
|
+
if verbose:
|
|
279
|
+
print("\n" + "=" * 70)
|
|
280
|
+
print("PROCESSING COMPLETE!")
|
|
281
|
+
print("=" * 70)
|
|
282
|
+
print(f"Total files processed: {len(filepaths)}")
|
|
283
|
+
print(f"Voice segments extracted: {len(pathnames_voice)}")
|
|
284
|
+
print(f"Voice clusters found: {n_voice_clusters}")
|
|
285
|
+
print(f"Noise types found: {n_noise_clusters}")
|
|
286
|
+
print(f"Average SNR improvement: {avg_snr:.2f} dB")
|
|
287
|
+
print(f"\nResults saved to: {output_dir}")
|
|
288
|
+
print(f"Model saved to: {model_path}")
|
|
289
|
+
print("=" * 70)
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
'enhanced': enhanced,
|
|
293
|
+
'separated': separated,
|
|
294
|
+
'segments_voice': segments_voice,
|
|
295
|
+
'pathnames_voice': pathnames_voice,
|
|
296
|
+
'results_voice': results_voice,
|
|
297
|
+
'labels_voice': labels_voice,
|
|
298
|
+
'results_noise': results_noise,
|
|
299
|
+
'labels_noise': labels_noise,
|
|
300
|
+
'summary': summary,
|
|
301
|
+
'model_path': model_path,
|
|
302
|
+
'clustaudio': cl
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def main():
|
|
307
|
+
"""Main entry point for command-line usage."""
|
|
308
|
+
parser = argparse.ArgumentParser(
|
|
309
|
+
description='Process ATC communications with clustaudio',
|
|
310
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
311
|
+
epilog="""
|
|
312
|
+
Examples:
|
|
313
|
+
# Basic usage
|
|
314
|
+
python atc_example.py --input ./ATC_recordings/ --output ./results/
|
|
315
|
+
|
|
316
|
+
# With custom enhancement
|
|
317
|
+
python atc_example.py --input ./ATC/ --output ./out/ --enhancement aggressive
|
|
318
|
+
|
|
319
|
+
# Without plots
|
|
320
|
+
python atc_example.py --input ./ATC/ --output ./out/ --no-plots
|
|
321
|
+
"""
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
'--input', '-i',
|
|
326
|
+
required=True,
|
|
327
|
+
help='Input directory containing ATC audio files'
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
parser.add_argument(
|
|
331
|
+
'--output', '-o',
|
|
332
|
+
required=True,
|
|
333
|
+
help='Output directory for results'
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
parser.add_argument(
|
|
337
|
+
'--enhancement', '-e',
|
|
338
|
+
default='aggressive',
|
|
339
|
+
choices=['light', 'moderate', 'aggressive'],
|
|
340
|
+
help='Enhancement level (default: aggressive)'
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
parser.add_argument(
|
|
344
|
+
'--no-plots',
|
|
345
|
+
action='store_true',
|
|
346
|
+
help='Skip generating visualization plots'
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
parser.add_argument(
|
|
350
|
+
'--quiet', '-q',
|
|
351
|
+
action='store_true',
|
|
352
|
+
help='Minimal output'
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
args = parser.parse_args()
|
|
356
|
+
|
|
357
|
+
# Validate input directory
|
|
358
|
+
if not os.path.isdir(args.input):
|
|
359
|
+
print(f"ERROR: Input directory does not exist: {args.input}")
|
|
360
|
+
sys.exit(1)
|
|
361
|
+
|
|
362
|
+
# Process
|
|
363
|
+
results = process_atc_communications(
|
|
364
|
+
input_dir=args.input,
|
|
365
|
+
output_dir=args.output,
|
|
366
|
+
enhancement_level=args.enhancement,
|
|
367
|
+
save_plots=not args.no_plots,
|
|
368
|
+
verbose=not args.quiet
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if results is None or results.get('error'):
|
|
372
|
+
sys.exit(1)
|
|
373
|
+
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == '__main__':
|
|
378
|
+
sys.exit(main())
|