selcal/scripts/selcal-detect.py

116 lines
3.3 KiB
Python
Executable File

#! /usr/bin/env python3
import csv
import sys
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
from scipy.io import wavfile
from scipy.signal import butter, lfilter
from math import log10
import pyqtgraph as pg
from PyQt5 import QtWidgets
tones = {}
with open('tones.csv', newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
tones[row['designator']] = float(row['frequency'])
# Shamelessly lifted from
# https://scipy.github.io/old-wiki/pages/Cookbook/ButterworthBandpass
def butter_bandpass(lowcut, highcut, fs, order=5):
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
b, a = butter(order, [low, high], btype='band')
return b, a
def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
b, a = butter_bandpass(lowcut, highcut, fs, order=order)
y = lfilter(b, a, data)
return y
# tone synthesis
def note(freq, cycles, amp=32767.0, rate=44100):
len = cycles * (1.0/rate)
t = np.linspace(0, len, int(len * rate))
if freq == 0:
data = np.zeros(int(len * rate))
else:
data = np.sin(2 * np.pi * freq * t) * amp
return data.astype(int)
def decimate_from_sample_rate(sample_rate):
if sample_rate == 44100:
return 4 # rate = 11025, Fmax = 5512.5 Hz
elif sample_rate == 48000:
return 5 # rate = 9600, Fmax = 4800 Hz
elif sample_rate == 22050:
return 2 # rate = 11025, Fmax = 5512.5 Hz
elif sample_rate == 11025:
return 1 # rate = 11025, Fmax = 5512.5 Hz
else:
raise ValueError("Sample rate not supported")
# analyze wav file
if __name__ == '__main__':
FLT_LEN = 2000 # Samples
file_name = sys.argv[1]
sample_rate, data = wavfile.read(file_name)
print(f"{file_name}: {len(data)} samples @ {sample_rate} Hz")
decimate = decimate_from_sample_rate(sample_rate)
if decimate > 1:
data = signal.decimate(data, decimate)
sample_rate = sample_rate / decimate
print(f'Length after decimation: {len(data)} samples')
data = butter_bandpass_filter(data, 270, 1700, sample_rate, order=8)
pure_signals = {tone:note(freq, FLT_LEN, rate=sample_rate) for tone,freq in tones.items()}
correlations = {tone:np.abs(signal.correlate(data, pure, mode='same')) for tone,pure in pure_signals.items()}
app = QtWidgets.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="SELCAL Tone Correlation")
plot = win.addPlot(title=file_name)
plot.addLegend()
color_map = pg.colormap.get('CET-C6s')
colors = color_map.getLookupTable(nPts=len(tones))
for (tone, correlation), color in zip(correlations.items(), colors):
plot.plot(correlation, pen=pg.mkPen(color=color), fillLevel=0, name=tone)
plot.setLabel('left', 'Signal Correlation')
plot.setLabel('bottom', 'Time (samples)')
plot.showGrid(x=True, y=True)
app.exec_()
# matplotlib is too slow
'''
fig, ax = plt.subplots()
# Step 4: Plot the data
for tone,correlation in correlations.items():
ax.plot(correlation, label=tone)
# Step 5: Customize the plot
ax.set_title('Multiple Lines Sharing the Same X Data')
ax.set_xlabel('Time (samples)')
ax.set_ylabel('Signal Correlation')
ax.legend() # Add a legend
ax.grid(True) # Add a grid
# Step 6: Display the plot
plt.show()
'''