Skip to content

Commit ee2c2c5

Browse files
committed
add audio read plugin for EPG Systems' AQ8 files
1 parent 731734f commit ee2c2c5

8 files changed

Lines changed: 134 additions & 22 deletions

File tree

src/audio-read-plugin.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
# a function that inputs the full path to a file containing the audio recording,
2-
# an interval of time, and some keyword arguments and returns the sampling
3-
# rate, shape of entire recording (not just the interval), and requested data as int16
1+
#a function that inputs the full path (including possibly a recording letter) to
2+
#a file containing the audio recording, an interval of time, and some keyword
3+
#arguments and returns the sampling rate, shape of entire recording (not just
4+
#the interval), and requested data as int16. if {start,stop}_tic are None, return
5+
#the entire recording
46
def audio_read(fullpath, start_tic, stop_tic, **kw):
57

68
# load data, determine sampling rate and length, and do any special processing
79

810
return sampling_rate, nsamples_nchannels, slice_of_data
11+
12+
# a function that returns a list of file extensions which this plugin can handle
13+
def audio_read_exts(**kw):
14+
return [] # e.g. ['.wav', '.WAV']
15+
16+
# a function that returns a dictionary that maps logical recordings to channels in the file
17+
def audio_read_rec2ch(**kw):
18+
return {} # e.g. {'A':[0], 'B':[1]}, or {'A':[0,1]}
19+
20+
def audio_read_init(**kw):
21+
pass

src/data.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,15 @@ def __init__(self, data_dir,
118118
self.np_rng = np.random.default_rng(None if random_seed_batch==-1 else random_seed_batch)
119119

120120
sys.path.append(os.path.dirname(audio_read_plugin))
121-
self.audio_read_plugin = os.path.basename(audio_read_plugin)
121+
audio_read_plugin = os.path.basename(audio_read_plugin)
122122
self.audio_read_plugin_kwargs = audio_read_plugin_kwargs
123+
self.audio_read_module = importlib.import_module(audio_read_plugin)
124+
self.audio_read_module.audio_read_init(**self.audio_read_plugin_kwargs)
123125

124126
sys.path.append(os.path.dirname(video_read_plugin))
125-
self.video_read_plugin = os.path.basename(video_read_plugin)
127+
video_read_plugin = os.path.basename(video_read_plugin)
126128
self.video_read_plugin_kwargs = video_read_plugin_kwargs
129+
self.video_read_module = importlib.import_module(video_read_plugin)
127130

128131
self.prepare_data_index(shiftby,
129132
labels_touse, kinds_touse,
@@ -139,14 +142,12 @@ def __init__(self, data_dir,
139142
signal.signal(signal.SIGTERM, term)
140143

141144
def audio_read(self, fullpath, start_tic=None, stop_tic=None):
142-
audio_read_module = importlib.import_module(self.audio_read_plugin)
143-
return audio_read_module.audio_read(fullpath, start_tic, stop_tic,
144-
**self.audio_read_plugin_kwargs)
145+
return self.audio_read_module.audio_read(fullpath, start_tic, stop_tic,
146+
**self.audio_read_plugin_kwargs)
145147

146148
def video_read(self, fullpath, start_frame=None, stop_frame=None):
147-
video_read_module = importlib.import_module(self.video_read_plugin)
148-
return video_read_module.video_read(fullpath, start_frame, stop_frame,
149-
**self.video_read_plugin_kwargs)
149+
return self.video_read_module.video_read(fullpath, start_frame, stop_frame,
150+
**self.video_read_plugin_kwargs)
150151

151152
def catalog_overlaps(self, data):
152153
data.sort(key=lambda x: x['ticks'][0])

src/gui/controller.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,16 +2094,27 @@ def model_callback():
20942094
else:
20952095
bokehlog.info('ERROR: a directory or file must be selected in the file browser')
20962096

2097+
def _wavcsv_files_callback(filename, files):
2098+
if os.path.splitext(filename)[1] in M.audio_read_exts():
2099+
if len(M.audio_read_rec2ch()) == 1:
2100+
files.append(os.path.join(M.file_dialog_root, filename))
2101+
else:
2102+
files.extend([os.path.join(M.file_dialog_root, filename)+'-'+k
2103+
for k in M.audio_read_rec2ch().keys()])
2104+
else:
2105+
files.append(os.path.join(M.file_dialog_root, filename))
2106+
20972107
def wavcsv_files_callback():
20982108
if len(V.file_dialog_source.selected.indices)==0:
20992109
bokehlog.info('ERROR: a file(s) must be selected in the file browser')
21002110
return
2111+
files = []
21012112
filename = V.file_dialog_source.data['names'][V.file_dialog_source.selected.indices[0]]
2102-
files = os.path.join(M.file_dialog_root, filename)
2113+
_wavcsv_files_callback(filename, files)
21032114
for i in range(1, len(V.file_dialog_source.selected.indices)):
21042115
filename = V.file_dialog_source.data['names'][V.file_dialog_source.selected.indices[i]]
2105-
files += ','+os.path.join(M.file_dialog_root, filename)
2106-
V.wavcsv_files.value = files
2116+
_wavcsv_files_callback(filename, files)
2117+
V.wavcsv_files.value = ','.join(files)
21072118

21082119
def groundtruth_callback():
21092120
if len(V.file_dialog_source.selected.indices)>=2:

src/gui/model.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,14 @@ def save_annotations():
8484
fids = {}
8585
csvwriters = {}
8686
csvfiles_current = set([])
87-
for wavfile in set([os.path.join(*x['file']) for x in annotated_sounds if x["label"]!=""]):
88-
csvfile = wavfile[:-4]+"-annotated-"+songexplorer_starttime+".csv"
87+
wavfiles = set()
88+
for sound in annotated_sounds:
89+
if not sound["label"]: continue
90+
wavfile = os.path.join(*sound["file"])
91+
wavfile_norec = ''.join(wavfile.split('-')[:-1]) if len(audio_read_rec2ch())>1 else wavfile
92+
wavfiles |= set([wavfile_norec])
93+
for wavfile in wavfiles:
94+
csvfile = os.path.splitext(wavfile)[0]+"-annotated-"+songexplorer_starttime+".csv"
8995
annotated_csvfiles_all.add(csvfile)
9096
csvfiles_current.add(csvfile)
9197
fids[wavfile] = open(os.path.join(V.groundtruth_folder.value, csvfile),
@@ -98,7 +104,9 @@ def save_annotations():
98104
corrected_sounds=[]
99105
for annotation in annotated_sounds:
100106
if annotation['label']!="" and not annotation['label'].isspace():
101-
csvwriters[os.path.join(*annotation['file'])].writerow(
107+
wavfile = os.path.join(*annotation['file'])
108+
wavfile_norec = ''.join(wavfile.split('-')[:-1]) if len(audio_read_rec2ch())>1 else wavfile
109+
csvwriters[wavfile_norec].writerow(
102110
[annotation['file'][1],
103111
annotation['ticks'][0], annotation['ticks'][1],
104112
'annotated', annotation['label']])
@@ -110,10 +118,15 @@ def save_annotations():
110118
x['ticks'][1], 'annotated', x['label']] \
111119
for x in corrected_sounds], \
112120
columns=['file','start','stop','kind','label'])
113-
for wavfile in set([os.path.join(*x['file']) for x in corrected_sounds]):
121+
wavfiles = set()
122+
for sound in corrected_sounds:
123+
wavfile = os.path.join(*sound["file"])
124+
wavfile_norec = ''.join(wavfile.split('-')[:-1]) if len(audio_read_rec2ch())>1 else wavfile
125+
wavfiles |= set([wavfile_norec])
126+
for wavfile in wavfiles:
114127
wavdir, wavbase = os.path.split(wavfile)
115128
wavpath = os.path.join(V.groundtruth_folder.value, wavdir)
116-
for csvbase in filter(lambda x: x.startswith(wavbase[:-4]) and
129+
for csvbase in filter(lambda x: x.startswith(os.path.splitext(wavbase)[0]) and
117130
x.endswith(".csv") and
118131
"-annotated" in x and
119132
songexplorer_starttime not in x,
@@ -235,7 +248,8 @@ def init(_bokeh_document, _configuration_file, _use_aitch):
235248
global context_width_sec0, context_offset_sec0
236249
global xcluster, ycluster, zcluster, ndcluster, tic2pix_max, snippet_width_pix, ilayer, ispecies, iword, inohyphen, ikind, nlayers, layers, species, words, nohyphens, kinds, used_labels, snippets_gap_sec, snippets_tic, snippets_gap_tic, snippets_decimate_by, snippets_pix, snippets_gap_pix, context_decimate_by, context_width_tic, context_offset_tic, context_sound, isnippet, xsnippet, ysnippet, file_nframes, context_midpoint_tic, ilabel, used_sounds, used_starts_sorted, used_stops, iused_stops_sorted, annotated_sounds, annotated_starts_sorted, annotated_stops, iannotated_stops_sorted, annotated_csvfiles_all, nrecent_annotations, clustered_sounds, clustered_activations, used_recording2firstsound, clustered_starts_sorted, clustered_stops, iclustered_stops_sorted, songexplorer_starttime, history_stack, history_idx, wizard, action, function, statepath, state, file_dialog_root, file_dialog_filter, nearest_sounds, status_ticker_queue, waitfor_job, dfs, remaining_isounds
237250
global user_changed_recording, user_copied_parameters
238-
global audio_read, video_read, detect_labels, doubleclick_annotation, context_data, context_data_istart, model, video_findfile
251+
global audio_read, audio_read_exts, audio_read_rec2ch
252+
global video_read, detect_labels, doubleclick_annotation, context_data, context_data_istart, model, video_findfile
239253
global detect_parameters, doubleclick_parameters, model_parameters, cluster_parameters
240254

241255
bokeh_document = _bokeh_document
@@ -253,9 +267,11 @@ def init(_bokeh_document, _configuration_file, _use_aitch):
253267

254268
sys.path.insert(0,os.path.dirname(audio_read_plugin))
255269
audio_read_module = importlib.import_module(os.path.basename(audio_read_plugin))
270+
audio_read_module.audio_read_init(**audio_read_plugin_kwargs)
256271
def audio_read(wav_path, start_tic=None, stop_tic=None):
257-
return audio_read_module.audio_read(wav_path, start_tic, stop_tic,
258-
**audio_read_plugin_kwargs)
272+
return audio_read_module.audio_read(wav_path, start_tic, stop_tic, **audio_read_plugin_kwargs)
273+
def audio_read_exts(): return audio_read_module.audio_read_exts(**audio_read_plugin_kwargs)
274+
def audio_read_rec2ch(): return audio_read_module.audio_read_rec2ch(**audio_read_plugin_kwargs)
259275

260276
sys.path.insert(0,os.path.dirname(video_read_plugin))
261277
video_read_module = importlib.import_module(os.path.basename(video_read_plugin))

src/highpass-filter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,12 @@ def audio_read(wav_path, start_tic, stop_tic, cutoff=1, order=2):
3333
data_unpadded = data_filtered[padlenL:-padlenR or None, :]
3434

3535
return sampling_rate, data.shape, data_unpadded
36+
37+
def audio_read_exts(**kw):
38+
return ['.wav', '.WAV']
39+
40+
def audio_read_rec2ch(**kw):
41+
return {'A':[0]}
42+
43+
def audio_read_init(**kw):
44+
pass

src/load-epg-lut.npy

189 KB
Binary file not shown.

src/load-epg.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#audio_read_plugin="load-epg"
2+
#audio_read_plugin_kwargs={"nchan":8, "lut_file":"load-epg-lut.npy",
3+
# "ncomments":3, "Fs":"smpl.frq= ([0-9.]+)Hz"}
4+
5+
import re
6+
import numpy as np
7+
import os
8+
9+
def audio_read(fullpath_aq8_rec, start_tic, stop_tic,
10+
nchan=8, ncomments=3, Fs="smpl.frq= ([0-9.]+)Hz", **kw):
11+
fullpath_aq8, rec = fullpath_aq8_rec[:-2], fullpath_aq8_rec[-1]
12+
13+
if not start_tic: start_tic=0
14+
15+
with open(fullpath_aq8, 'rb') as fid:
16+
for _ in range(ncomments):
17+
line = fid.readline().decode()
18+
m = re.search(Fs, line)
19+
if m: sampling_rate = float(m.group(1))
20+
n0 = fid.tell()
21+
n1 = fid.seek(0,2)
22+
nsamples = (n1-n0)//nchan
23+
fid.seek(n0)
24+
if not stop_tic: stop_tic=nsamples
25+
fid.seek(nchan*start_tic, 1)
26+
b = fid.read(4*nchan*(stop_tic-start_tic))
27+
28+
v = np.frombuffer(b, dtype=np.uint32)
29+
a = np.reshape(v, (-1,nchan))
30+
31+
rec2ch = audio_read_rec2ch()
32+
chs = [0] if len(rec2ch)==1 else rec2ch[rec]
33+
s = a[:, chs]
34+
35+
i = np.searchsorted(lut[:,0], s)
36+
m = np.take(lut[:,1], i)
37+
c = (m / 10 * np.iinfo(np.int16).max).astype(np.int16)
38+
39+
return sampling_rate, (nsamples,len(chs)), c
40+
41+
def audio_read_exts(nchan=8, **kw):
42+
return ['.aq'+str(nchan)]
43+
44+
def audio_read_rec2ch(nchan=8, **kw):
45+
return {chr(65+i):[i] for i in range(nchan)}
46+
47+
def audio_read_init(lut_file="load-epg-lut.npy", **kw):
48+
script_dir = os.path.abspath(os.path.dirname(__file__))
49+
npyfile = np.load(os.path.join(script_dir, lut_file))
50+
global lut
51+
lut = npyfile[next(iter(npyfile.keys()))]
52+
isort = np.argsort(lut[:,0])
53+
lut = lut[isort,:]

src/load-wav.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,12 @@ def audio_read(wav_path, start_tic, stop_tic, mmap=True):
1919
data_sliced = data[start_tic_clamped : stop_tic_clamped, :]
2020

2121
return sampling_rate, data.shape, data_sliced
22+
23+
def audio_read_exts(**kw):
24+
return ['.wav', '.WAV']
25+
26+
def audio_read_rec2ch(**kw):
27+
return {'A':[0]}
28+
29+
def audio_read_init(**kw):
30+
pass

0 commit comments

Comments
 (0)