-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanalyze_spectrograms.py
More file actions
302 lines (246 loc) · 12.3 KB
/
Copy pathanalyze_spectrograms.py
File metadata and controls
302 lines (246 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
import os
import numpy as np
import matplotlib.pyplot as plt
import librosa
import librosa.display
from PIL import Image
import io
import random
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
# Directory containing spectrograms
data_dir = '/home/ubuntu/data'
# Function to analyze a spectrogram image
def analyze_spectrogram(file_path):
# Load image
img = Image.open(file_path)
img_array = np.array(img)
# Extract the actual spectrogram part (removing axes, colorbar, etc.)
# This is a simplification - actual extraction would need more precise cropping
spectrogram = img_array[80:350, 80:800, 0] # Approximate crop of the spectrogram area, using red channel
# Calculate basic statistics
mean_intensity = np.mean(spectrogram)
std_intensity = np.std(spectrogram)
max_intensity = np.max(spectrogram)
min_intensity = np.min(spectrogram)
# Calculate frequency distribution (average across time)
freq_distribution = np.mean(spectrogram, axis=1)
# Calculate temporal distribution (average across frequency)
temporal_distribution = np.mean(spectrogram, axis=0)
return {
'mean': mean_intensity,
'std': std_intensity,
'max': max_intensity,
'min': min_intensity,
'freq_distribution': freq_distribution,
'temporal_distribution': temporal_distribution,
'spectrogram': spectrogram
}
# Function to categorize files
def categorize_files(files):
categories = {
'env_noise': [],
'bio_whale': [],
'bio_fish': [],
'bio_coral': [],
'manmade_boat': [],
'manmade_ship': [],
'manmade_submarine': [],
'manmade_speedboat': [],
'transient': []
}
for file in files:
if file.startswith('env_noise'):
categories['env_noise'].append(file)
elif file.startswith('bio_signal_whale'):
categories['bio_whale'].append(file)
elif file.startswith('bio_signal_fish'):
categories['bio_fish'].append(file)
elif file.startswith('bio_signal_coral'):
categories['bio_coral'].append(file)
elif file.startswith('manmade_boat'):
categories['manmade_boat'].append(file)
elif file.startswith('manmade_ship'):
categories['manmade_ship'].append(file)
elif file.startswith('manmade_submarine'):
categories['manmade_submarine'].append(file)
elif file.startswith('manmade_speedboat'):
categories['manmade_speedboat'].append(file)
elif file.startswith('transient'):
categories['transient'].append(file)
return categories
# Get all spectrogram files
all_files = [f for f in os.listdir(data_dir) if f.endswith('.png')]
print(f"Total number of spectrogram files: {len(all_files)}")
# Categorize files
categories = categorize_files(all_files)
for category, files in categories.items():
print(f"{category}: {len(files)} files")
# Sample files from each category for analysis
sample_files = []
for category, files in categories.items():
if files: # If category is not empty
sample_files.append(os.path.join(data_dir, random.choice(files)))
# Analyze sample spectrograms
results = {}
for file_path in sample_files:
category = os.path.basename(file_path).split('_')[0]
if 'bio' in file_path:
category = 'bio_' + os.path.basename(file_path).split('_')[2].split('.')[0]
elif 'manmade' in file_path:
category = 'manmade_' + os.path.basename(file_path).split('_')[1].split('.')[0]
results[file_path] = analyze_spectrogram(file_path)
# Plot the spectrogram and its distributions
plt.figure(figsize=(15, 10))
# Plot original spectrogram
plt.subplot(2, 2, 1)
plt.imshow(results[file_path]['spectrogram'], aspect='auto', cmap='viridis')
plt.title(f"Spectrogram - {category}")
plt.colorbar(format='%+2.0f')
# Plot frequency distribution
plt.subplot(2, 2, 2)
plt.plot(results[file_path]['freq_distribution'])
plt.title('Frequency Distribution (avg across time)')
plt.xlabel('Frequency Bin')
plt.ylabel('Average Intensity')
# Plot temporal distribution
plt.subplot(2, 2, 3)
plt.plot(results[file_path]['temporal_distribution'])
plt.title('Temporal Distribution (avg across frequency)')
plt.xlabel('Time Frame')
plt.ylabel('Average Intensity')
# Display statistics
plt.subplot(2, 2, 4)
plt.axis('off')
stats_text = f"Statistics:\n" \
f"Mean: {results[file_path]['mean']:.2f}\n" \
f"Std: {results[file_path]['std']:.2f}\n" \
f"Max: {results[file_path]['max']:.2f}\n" \
f"Min: {results[file_path]['min']:.2f}"
plt.text(0.1, 0.5, stats_text, fontsize=12)
plt.tight_layout()
plt.savefig(f"/home/ubuntu/analysis_{category}.png")
plt.close()
# Extract features for dimensionality reduction
features = []
labels = []
file_paths = []
# Sample a subset of files for visualization (to avoid overcrowding)
visualization_files = []
for category, files in categories.items():
if files: # If category is not empty
# Take up to 5 files from each category
category_files = [os.path.join(data_dir, f) for f in files[:min(5, len(files))]]
visualization_files.extend(category_files)
# Analyze files and extract features
for file_path in visualization_files:
category = os.path.basename(file_path).split('_')[0]
if 'bio' in file_path:
category = 'bio_' + os.path.basename(file_path).split('_')[2].split('.')[0]
elif 'manmade' in file_path:
category = 'manmade_' + os.path.basename(file_path).split('_')[1].split('.')[0]
result = analyze_spectrogram(file_path)
# Use frequency and temporal distributions as features
feature_vector = np.concatenate([
result['freq_distribution'] / np.max(result['freq_distribution']), # Normalize
result['temporal_distribution'] / np.max(result['temporal_distribution']) # Normalize
])
features.append(feature_vector)
labels.append(category)
file_paths.append(file_path)
# Convert to numpy array
features = np.array(features)
# Get the number of samples and features
n_samples, n_features = features.shape
print(f"Feature matrix shape: {features.shape}")
# Apply PCA for dimensionality reduction - use min(n_samples, n_features) - 1 components
n_components = min(n_samples, n_features) - 1
print(f"Using {n_components} PCA components")
pca = PCA(n_components=n_components)
pca_result = pca.fit_transform(features)
# Apply t-SNE for visualization
tsne = TSNE(n_components=2, perplexity=min(30, len(features)-1), random_state=42)
tsne_result = tsne.fit_transform(pca_result)
# Plot t-SNE visualization
plt.figure(figsize=(12, 10))
categories_unique = list(set(labels))
colors = plt.cm.rainbow(np.linspace(0, 1, len(categories_unique)))
category_to_color = {category: color for category, color in zip(categories_unique, colors)}
for i, (x, y) in enumerate(tsne_result):
category = labels[i]
color = category_to_color[category]
plt.scatter(x, y, color=color, label=category if category not in plt.gca().get_legend_handles_labels()[1] else "")
plt.text(x, y, os.path.basename(file_paths[i]).split('.')[0], fontsize=8)
plt.legend()
plt.title('t-SNE Visualization of Spectrogram Features')
plt.savefig('/home/ubuntu/tsne_visualization.png')
plt.close()
# Calculate and plot average frequency distributions by category
plt.figure(figsize=(15, 10))
for i, category in enumerate(categories_unique):
category_files = [f for f, l in zip(visualization_files, labels) if l == category]
avg_freq_distribution = np.zeros(270) # Assuming all spectrograms have the same frequency dimension
for file in category_files:
result = analyze_spectrogram(file)
# Ensure all distributions have the same length
freq_dist = result['freq_distribution'][:270]
avg_freq_distribution += freq_dist / np.max(freq_dist) # Normalize before adding
if category_files: # Avoid division by zero
avg_freq_distribution /= len(category_files)
plt.subplot(3, 3, i+1)
plt.plot(avg_freq_distribution)
plt.title(f'Avg Frequency Distribution - {category}')
plt.xlabel('Frequency Bin')
plt.ylabel('Normalized Intensity')
plt.tight_layout()
plt.savefig('/home/ubuntu/avg_freq_distributions.png')
plt.close()
# Write analysis summary
with open('/home/ubuntu/spectrogram_analysis.md', 'w') as f:
f.write("# Underwater Acoustic Spectrogram Analysis\n\n")
f.write("## Dataset Overview\n")
f.write(f"Total number of spectrogram files: {len(all_files)}\n\n")
f.write("### Distribution by Category\n")
for category, files in categories.items():
f.write(f"- {category}: {len(files)} files\n")
f.write("\n## Key Observations\n\n")
f.write("### Environmental Noise\n")
f.write("- Characterized by more uniform energy distribution across frequencies\n")
f.write("- Often shows non-stationary patterns over time\n")
f.write("- Lower overall intensity compared to signal categories\n\n")
f.write("### Biological Signals\n")
f.write("- Whale calls: Distinctive frequency modulation patterns, concentrated energy in specific frequency bands\n")
f.write("- Fish sounds: Short, impulsive patterns with broader frequency content\n")
f.write("- Coral scraping: Irregular bursts of broadband energy\n\n")
f.write("### Man-made Signals\n")
f.write("- Boats/Ships: Strong harmonic structure with clear fundamental frequency and overtones\n")
f.write("- Submarines: Low-frequency tonals, sometimes with frequency shifts\n")
f.write("- Speedboats: Higher frequency content with potential Doppler effects\n\n")
f.write("### Transient Signals\n")
f.write("- Brief, high-energy broadband events\n")
f.write("- Sparse in time domain\n")
f.write("- Wide frequency range coverage\n\n")
f.write("## Implications for SimCLR Feature Extractor Design\n\n")
f.write("### Data Augmentation Strategies\n")
f.write("1. **Time-domain augmentations**:\n")
f.write(" - Time shifting: To handle varying onset times of signals\n")
f.write(" - Time masking: To simulate intermittent signals and improve robustness\n")
f.write(" - Time stretching/compression: To handle variations in signal duration\n\n")
f.write("2. **Frequency-domain augmentations**:\n")
f.write(" - Frequency shifting: To handle variations in pitch/frequency\n")
f.write(" - Frequency masking: To improve robustness to frequency-selective noise\n")
f.write(" - Pitch shifting: Particularly important for tonal signals like whale calls and boat engines\n\n")
f.write("3. **Intensity augmentations**:\n")
f.write(" - Amplitude scaling: To handle the orders of magnitude variations in signal strength\n")
f.write(" - Adding Gaussian noise: To improve robustness to background noise\n\n")
f.write("### Architecture Considerations\n")
f.write("1. **Receptive field**: Model should capture both fine-grained temporal patterns (e.g., transients) and longer-term patterns (e.g., whale calls)\n")
f.write("2. **Multi-scale processing**: Different acoustic events occur at different time and frequency scales\n")
f.write("3. **Attention mechanisms**: May help focus on relevant parts of the spectrogram while ignoring noise\n")
f.write("4. **Frequency-aware design**: Different frequency bands may require different processing\n\n")
f.write("### Projection Head Design\n")
f.write("- Projection dimension should be sufficiently large to capture the diversity of acoustic patterns\n")
f.write("- Multiple non-linear layers may be beneficial for learning complex representations\n\n")
f.write("## Conclusion\n")
f.write("The underwater acoustic spectrograms exhibit diverse characteristics across different categories. The SimCLR feature extractor should be designed to handle this diversity through appropriate data augmentations and model architecture. The self-supervised approach is particularly well-suited for this domain due to the availability of unlabeled data and the need to learn robust representations that can distinguish between different types of acoustic signals.\n")
print("Analysis complete. Results saved to /home/ubuntu/spectrogram_analysis.md")