Skip to content

Commit fc96bb9

Browse files
author
Jan Buethe
committedJul 26, 2024
added osce testing related scripts (ietf120)
1 parent 2554a89 commit fc96bb9

File tree

2 files changed

+316
-0
lines changed

2 files changed

+316
-0
lines changed
 
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import argparse
2+
import os
3+
import yaml
4+
import subprocess
5+
6+
import numpy as np
7+
8+
9+
10+
parser = argparse.ArgumentParser()
11+
parser.add_argument('commonvoice_base_dir')
12+
parser.add_argument('output_dir')
13+
parser.add_argument('--clips-per-language', required=False, type=int, default=10)
14+
parser.add_argument('--seed', required=False, type=int, default=2024)
15+
16+
17+
def select_clips(dir, num_clips=10):
18+
19+
if num_clips % 2:
20+
print(f"warning: number of clips will be reduced to {num_clips - 1}")
21+
female = dict()
22+
male = dict()
23+
24+
clips = np.genfromtxt(os.path.join(dir, 'validated.tsv'), delimiter='\t', dtype=str, invalid_raise=False)
25+
clips_by_client = dict()
26+
27+
if len(clips.shape) < 2 or len(clips) < num_clips:
28+
# not enough data to proceed
29+
return None
30+
31+
for client in set(clips[1:,0]):
32+
client_clips = clips[clips[:, 0] == client]
33+
f, m = False, False
34+
if 'female_feminine' in client_clips[:, 8]:
35+
female[client] = client_clips[client_clips[:, 8] == 'female_feminine']
36+
f = True
37+
if 'male_masculine' in client_clips[:, 8]:
38+
male[client] = client_clips[client_clips[:, 8] == 'male_masculine']
39+
m = True
40+
41+
if f and m:
42+
print(f"both male and female clips under client {client}")
43+
44+
45+
if min(len(female), len(male)) < num_clips // 2:
46+
return None
47+
48+
# select num_clips // 2 random female clients
49+
female_client_selection = np.array(list(female.keys()), dtype=str)[np.random.choice(len(female), num_clips//2, replace=False)]
50+
female_clip_selection = []
51+
for c in female_client_selection:
52+
s_idx = np.random.randint(0, len(female[c]))
53+
female_clip_selection.append(os.path.join(dir, 'clips', female[c][s_idx, 1].item()))
54+
55+
# select num_clips // 2 random female clients
56+
male_client_selection = np.array(list(male.keys()), dtype=str)[np.random.choice(len(male), num_clips//2, replace=False)]
57+
male_clip_selection = []
58+
for c in male_client_selection:
59+
s_idx = np.random.randint(0, len(male[c]))
60+
male_clip_selection.append(os.path.join(dir, 'clips', male[c][s_idx, 1].item()))
61+
62+
return female_clip_selection + male_clip_selection
63+
64+
def ffmpeg_available():
65+
try:
66+
x = subprocess.run(['ffmpeg', '-h'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
67+
return x.returncode == 0
68+
except:
69+
return False
70+
71+
72+
def convert_clips(selection, outdir):
73+
if not ffmpeg_available():
74+
raise RuntimeError("ffmpeg not available")
75+
76+
clipdir = os.path.join(outdir, 'clips')
77+
os.makedirs(clipdir, exist_ok=True)
78+
79+
clipdict = dict()
80+
81+
for lang, clips in selection.items():
82+
clipdict[lang] = []
83+
for clip in clips:
84+
clipname = os.path.splitext(os.path.split(clip)[-1])[0]
85+
target_name = os.path.join('clips', clipname + '.wav')
86+
call_args = ['ffmpeg', '-i', clip, '-ar', '16000', os.path.join(outdir, target_name)]
87+
print(call_args)
88+
r = subprocess.run(call_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
89+
if r.returncode != 0:
90+
raise RuntimeError(f'could not execute {call_args}')
91+
clipdict[lang].append(target_name)
92+
93+
return clipdict
94+
95+
96+
if __name__ == "__main__":
97+
if not ffmpeg_available():
98+
raise RuntimeError("ffmpeg not available")
99+
100+
args = parser.parse_args()
101+
102+
base_dir = args.commonvoice_base_dir
103+
output_dir = args.output_dir
104+
seed = args.seed
105+
106+
np.random.seed(seed)
107+
108+
langs = os.listdir(base_dir)
109+
selection = dict()
110+
111+
for lang in langs:
112+
print(f"processing {lang}...")
113+
clips = select_clips(os.path.join(base_dir, lang))
114+
if clips is not None:
115+
selection[lang] = clips
116+
117+
118+
os.makedirs(output_dir, exist_ok=True)
119+
120+
clips = convert_clips(selection, output_dir)
121+
122+
with open(os.path.join(output_dir, 'clips.yml'), 'w') as f:
123+
yaml.dump(clips, f)
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import os
2+
import argparse
3+
import yaml
4+
import subprocess
5+
6+
import numpy as np
7+
8+
from moc2 import compare as moc
9+
10+
DEBUG=False
11+
12+
parser = argparse.ArgumentParser()
13+
14+
parser.add_argument('inputdir', type=str, help='Input folder with test items')
15+
parser.add_argument('outputdir', type=str, help='Output folder')
16+
parser.add_argument('bitrate', type=int, help='bitrate to test')
17+
parser.add_argument('--reference_opus_demo', type=str, default='./opus_demo', help='reference opus_demo binary for generating bitstreams and reference output')
18+
parser.add_argument('--test_opus_demo', type=str, default='./opus_demo', help='opus_demo binary under test')
19+
parser.add_argument('--test_opus_demo_options', type=str, default='-dec_complexity 7', help='options for test opus_demo (e.g. "-dec_complexity 7")')
20+
parser.add_argument('--verbose', type=int, default=0, help='verbosity level: 0 for quiet (default), 1 for reporting individual test results, 2 for reporting per-item scores in failed tests')
21+
22+
def run_opus_encoder(opus_demo_path, input_pcm_path, bitstream_path, application, fs, num_channels, bitrate, options=[], verbose=False):
23+
24+
call_args = [
25+
opus_demo_path,
26+
"-e",
27+
application,
28+
str(fs),
29+
str(num_channels),
30+
str(bitrate),
31+
"-bandwidth",
32+
"WB"
33+
]
34+
35+
call_args += options
36+
37+
call_args += [
38+
input_pcm_path,
39+
bitstream_path
40+
]
41+
42+
try:
43+
if verbose:
44+
print(f"running {call_args}...")
45+
subprocess.run(call_args)
46+
else:
47+
subprocess.run(call_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
48+
except:
49+
return 1
50+
51+
return 0
52+
53+
54+
def run_opus_decoder(opus_demo_path, bitstream_path, output_pcm_path, fs, num_channels, options=[], verbose=False):
55+
56+
call_args = [
57+
opus_demo_path,
58+
"-d",
59+
str(fs),
60+
str(num_channels)
61+
]
62+
63+
call_args += options
64+
65+
call_args += [
66+
bitstream_path,
67+
output_pcm_path
68+
]
69+
70+
try:
71+
if verbose:
72+
print(f"running {call_args}...")
73+
subprocess.run(call_args)
74+
else:
75+
subprocess.run(call_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
76+
except:
77+
return 1
78+
79+
return 0
80+
81+
def compute_moc_score(reference_pcm, test_pcm, delay=91):
82+
x_ref = np.fromfile(reference_pcm, dtype=np.int16).astype(np.float32) / (2 ** 15)
83+
x_cut = np.fromfile(test_pcm, dtype=np.int16).astype(np.float32) / (2 ** 15)
84+
85+
moc_score = moc(x_ref, x_cut[delay:])
86+
87+
return moc_score
88+
89+
def sox(*call_args):
90+
try:
91+
call_args = ["sox"] + list(call_args)
92+
subprocess.run(call_args)
93+
return 0
94+
except:
95+
return 1
96+
97+
def process_clip_factory(ref_opus_demo, test_opus_demo, test_options):
98+
def process_clip(clip_path, processdir, bitrate):
99+
# derive paths
100+
clipname = os.path.splitext(os.path.split(clip_path)[1])[0]
101+
pcm_path = os.path.join(processdir, clipname + ".raw")
102+
bitstream_path = os.path.join(processdir, clipname + ".bin")
103+
ref_path = os.path.join(processdir, clipname + "_ref.raw")
104+
test_path = os.path.join(processdir, clipname + "_test.raw")
105+
106+
# run sox
107+
sox(clip_path, pcm_path)
108+
109+
# run encoder
110+
run_opus_encoder(ref_opus_demo, pcm_path, bitstream_path, "voip", 16000, 1, bitrate)
111+
112+
# run decoder
113+
run_opus_decoder(ref_opus_demo, bitstream_path, ref_path, 16000, 1)
114+
run_opus_decoder(test_opus_demo, bitstream_path, test_path, 16000, 1, options=test_options)
115+
116+
d_ref = compute_moc_score(pcm_path, ref_path)
117+
d_test = compute_moc_score(pcm_path, test_path)
118+
119+
return d_ref, d_test
120+
121+
122+
return process_clip
123+
124+
def main(inputdir, outputdir, bitrate, reference_opus_demo, test_opus_demo, test_option_string, verbose):
125+
126+
# load clips list
127+
with open(os.path.join(inputdir, 'clips.yml'), "r") as f:
128+
clips = yaml.safe_load(f)
129+
130+
# parse test options
131+
test_options = test_option_string.split()
132+
133+
process_clip = process_clip_factory(reference_opus_demo, test_opus_demo, test_options)
134+
135+
os.makedirs(outputdir, exist_ok=True)
136+
processdir = os.path.join(outputdir, 'process')
137+
os.makedirs(processdir, exist_ok=True)
138+
139+
num_passed = 0
140+
results = dict()
141+
min_rel_diff = 1000
142+
min_mean = 1000
143+
worst_clip = None
144+
worst_lang = None
145+
for lang, lang_clips in clips.items():
146+
if verbose > 0: print(f"processing language {lang}...")
147+
results[lang] = np.zeros((len(lang_clips), 2))
148+
for i, clip in enumerate(lang_clips):
149+
clip_path = os.path.join(inputdir, clip)
150+
d_ref, d_test = process_clip(clip_path, processdir, bitrate)
151+
results[lang][i, 0] = d_ref
152+
results[lang][i, 1] = d_test
153+
154+
alpha = 0.5
155+
rel_diff = ((results[lang][:, 0] ** alpha - results[lang][:, 1] ** alpha) /(results[lang][:, 0] ** alpha))
156+
157+
min_idx = np.argmin(rel_diff).item()
158+
if rel_diff[min_idx] < min_rel_diff:
159+
min_rel_diff = rel_diff[min_idx]
160+
worst_clip = lang_clips[min_idx]
161+
162+
if np.mean(rel_diff) < min_mean:
163+
min_mean = np.mean(rel_diff).item()
164+
worst_lang = lang
165+
166+
if np.min(rel_diff) < -0.1 or np.mean(rel_diff) < -0.025:
167+
if verbose > 0: print(f"FAIL ({np.mean(results[lang], axis=0)} {np.mean(rel_diff)} {np.min(rel_diff)})")
168+
if verbose > 1:
169+
for i, c in enumerate(lang_clips):
170+
print(f" {c:50s} {results[lang][i]} {rel_diff[i]}")
171+
else:
172+
if verbose > 0: print(f"PASS ({np.mean(results[lang], axis=0)} {np.mean(rel_diff)} {np.min(rel_diff)})")
173+
num_passed += 1
174+
175+
print(f"{num_passed}/{len(clips)} tests passed!")
176+
177+
print(f"worst case occured at clip {worst_clip} with relative difference of {min_rel_diff}")
178+
print(f"worst mean relative difference was {min_mean} for test {worst_lang}")
179+
180+
np.save(os.path.join(outputdir, f'results_' + "_".join(test_options) + f"_{bitrate}.npy"), results, allow_pickle=True)
181+
182+
183+
184+
if __name__ == "__main__":
185+
args = parser.parse_args()
186+
187+
main(args.inputdir,
188+
args.outputdir,
189+
args.bitrate,
190+
args.reference_opus_demo,
191+
args.test_opus_demo,
192+
args.test_opus_demo_options,
193+
args.verbose)

0 commit comments

Comments
 (0)
Failed to load comments.