mamba-cot-tts / code /scripts /artifact_metrics.py
maxpicy's picture
Add files using upload-large-folder tool
6e802a4 verified
Raw History Blame Contribute Delete
3.95 kB
# artifact_metrics.py: Completion and prefix WER (truncation kept apart from articulation).
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO))
sys.path.insert(0, str(REPO / "scripts"))
def _ops(ref, hyp):
n, m = len(ref), len(hyp)
if n == 0:
return ["I"] * m
if m == 0:
return ["D"] * n
dp = np.zeros((n + 1, m + 1), dtype=np.int32)
op = np.zeros((n + 1, m + 1), dtype=np.int8)
dp[:, 0] = np.arange(n + 1)
dp[0, :] = np.arange(m + 1)
op[:, 0] = 2
op[0, :] = 3
op[0, 0] = 0
for i in range(1, n + 1):
for j in range(1, m + 1):
if ref[i - 1] == hyp[j - 1]:
dp[i, j], op[i, j] = dp[i - 1, j - 1], 0
else:
c = (dp[i - 1, j - 1] + 1, dp[i - 1, j] + 1, dp[i, j - 1] + 1)
best = min(c)
dp[i, j] = best
op[i, j] = 1 if best == c[0] else 2 if best == c[1] else 3
out, i, j = [], n, m
while i > 0 or j > 0:
o = op[i, j]
if o == 0:
out.append("="); i -= 1; j -= 1
elif o == 1:
out.append("S"); i -= 1; j -= 1
elif o == 2:
out.append("D"); i -= 1
else:
out.append("I"); j -= 1
return out[::-1]
def truncation_metrics(reference: str, hypothesis: str) -> dict:
from eval.intelligibility import _normalize, word_error_rate
ref, hyp = _normalize(reference), _normalize(hypothesis)
n = len(ref)
if n == 0:
return {"completion": float("nan"), "prefix_wer": float("nan"),
"trailing_del": 0, "wer": float("nan")}
ops = _ops(ref, hyp)
trailing = 0
for o in reversed(ops):
if o == "D":
trailing += 1
elif o == "I":
continue
else:
break
covered = max(0, n - trailing)
prefix_ref = " ".join(ref[:covered])
pw = (word_error_rate(prefix_ref, " ".join(hyp))["wer"] if covered
else float("nan"))
return {"completion": covered / n, "prefix_wer": pw,
"trailing_del": trailing,
"wer": word_error_rate(" ".join(ref), " ".join(hyp))["wer"]}
def autotune_metrics(wav, sr, flat_cents=5.0, jump_cents=50.0) -> dict:
from score_naturalness_panel import f0_track
f0, _fs = f0_track(wav, sr)
f0 = np.asarray(f0, dtype=float)
v = f0[np.isfinite(f0) & (f0 > 0)]
if v.size < 8:
return {"flat_f0": float("nan"), "jump_f0": float("nan"),
"stair": float("nan")}
cents = 1200.0 * np.log2(v / v[0])
d = np.abs(np.diff(cents))
if d.size == 0:
return {"flat_f0": float("nan"), "jump_f0": float("nan"),
"stair": float("nan")}
flat = float((d < flat_cents).mean() * 100.0)
jump = float((d > jump_cents).mean() * 100.0)
return {"flat_f0": flat, "jump_f0": jump, "stair": flat + jump}
def prosody_metrics(wav, sr) -> dict:
from score_naturalness_panel import f0_track
f0, _fs = f0_track(wav, sr)
f0 = np.asarray(f0, dtype=float)
ok = np.isfinite(f0) & (f0 > 0)
v = f0[ok]
if v.size < 8:
return {"f0_sd_st": float("nan"), "f0_rng_st": float("nan"),
"voiced_pct": float("nan")}
med = float(np.median(v))
st = 12.0 * np.log2(v / med)
return {"f0_sd_st": float(np.std(st)),
"f0_rng_st": float(np.percentile(st, 95) - np.percentile(st, 5)),
"voiced_pct": float(ok.mean() * 100.0)}
if __name__ == "__main__":
full = "good morning i hope you slept well and feel rested today"
print("exact ", truncation_metrics(full, full))
print("truncated ", truncation_metrics(full, "good morning"))
print("garbled ", truncation_metrics(full, "good morning i hope you slipped "
"wall and fill rusted today"))