-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathapt.py
More file actions
99 lines (78 loc) · 3.15 KB
/
apt.py
File metadata and controls
99 lines (78 loc) · 3.15 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
import numpy
import scipy.io.wavfile
import scipy.signal
import sys
import PIL
class APT(object):
RATE = 20800
NOAA_LINE_LENGTH = 2080
def __init__(self, filename):
(rate, self.signal) = scipy.io.wavfile.read(filename)
if rate != self.RATE:
raise Exception("Resample audio file to {}".format(self.RATE))
# Keep only one channel if audio is stereo
if self.signal.ndim > 1:
self.signal = self.signal[:, 0]
truncate = self.RATE * int(len(self.signal) // self.RATE)
self.signal = self.signal[:truncate]
def decode(self, outfile=None):
hilbert = scipy.signal.hilbert(self.signal)
filtered = scipy.signal.medfilt(numpy.abs(hilbert), 5)
reshaped = filtered.reshape(len(filtered) // 5, 5)
digitized = self._digitize(reshaped[:, 2])
matrix = self._reshape(digitized)
image = PIL.Image.fromarray(matrix)
if not outfile is None:
image.save(outfile)
image.show()
return matrix
def _digitize(self, signal, plow=0.5, phigh=99.5):
'''
Convert signal to numbers between 0 and 255.
'''
(low, high) = numpy.percentile(signal, (plow, phigh))
delta = high - low
data = numpy.round(255 * (signal - low) / delta)
data[data < 0] = 0
data[data > 255] = 255
return data.astype(numpy.uint8)
def _reshape(self, signal):
'''
Find sync frames and reshape the 1D signal into a 2D image.
Finds the sync A frame by looking at the maximum values of the cross
correlation between the signal and a hardcoded sync A frame.
The expected distance between sync A frames is 2080 samples, but with
small variations because of Doppler effect.
'''
# sync frame to find: seven impulses and some black pixels (some lines
# have something like 8 black pixels and then white ones)
syncA = [0, 128, 255, 128]*7 + [0]*7
# list of maximum correlations found: (index, value)
peaks = [(0, 0)]
# minimum distance between peaks
mindistance = 2000
# need to shift the values down to get meaningful correlation values
signalshifted = [x-128 for x in signal]
syncA = [x-128 for x in syncA]
for i in range(len(signal)-len(syncA)):
corr = numpy.dot(syncA, signalshifted[i : i+len(syncA)])
# if previous peak is too far, keep it and add this value to the
# list as a new peak
if i - peaks[-1][0] > mindistance:
peaks.append((i, corr))
# else if this value is bigger than the previous maximum, set this
# one
elif corr > peaks[-1][1]:
peaks[-1] = (i, corr)
# create image matrix starting each line on the peaks found
matrix = []
for i in range(len(peaks) - 1):
matrix.append(signal[peaks[i][0] : peaks[i][0] + 2080])
return numpy.array(matrix)
if __name__ == '__main__':
apt = APT(sys.argv[1])
if len(sys.argv) > 2:
outfile = sys.argv[2]
else:
outfile = None
apt.decode(outfile)