Files

113 lines
4.5 KiB
Python

"""Write the segment VAD again at a different frame rate.
`00_prep_fisher.py` rasterizes the word annotations at `VAD_HZ` and stores one
`.npy` per segment beside the audio. A projection window running at another rate
needs its labels on that grid: resampling the 25 Hz array would inherit its
quantisation, so this rasterizes the annotations again from their original
continuous times.
Only the VAD is rewritten. The audio is rate-independent and stays where it is,
so this touches ~1 GB rather than the 1.12 TB of segments, and it writes to
`seg/vad{rate}/` rather than over `seg/vad/`.
python preprocess/vap/03_prep_vad_rate.py --frame-hz 12.5 \
--fisher-path /path/to/fisher --splits train val test
"""
import argparse
from collections import defaultdict
from pathlib import Path
import numpy as np
from preprocess.vap.utils import SAMPLE_RATE
SEG_SEC = 30
CONTEXT_SEC = 2.0
def rasterize(words_dir, conversation, frames, frame_hz):
"""`[2, frames]` activity for a whole conversation, from the word times."""
vad = np.zeros((2, frames), dtype=np.float32)
paths = sorted(words_dir.glob(f"{conversation}_*_words.txt"))
if len(paths) != 2:
raise FileNotFoundError(
f"expected two *_words.txt for {conversation} in {words_dir}, found {len(paths)}"
)
for speaker, path in enumerate(paths):
with open(path) as handle:
for line in handle:
if not line.strip():
continue
begin, end, *_ = line.split()
first = max(0, int(round(float(begin) * frame_hz)))
last = min(frames, int(round(float(end) * frame_hz)))
if last > first:
vad[speaker, first:last] = 1.0
return vad
def segment_window(vad, start_sec, frame_hz, context_frames):
"""The stored slice for one segment: the segment plus context, zero-padded.
Mirrors `extract_and_save_chunks`, so a reader can treat these files exactly
as it treats the 25 Hz ones.
"""
seg_frames = int(round(SEG_SEC * frame_hz))
start = int(round(start_sec * frame_hz))
end = start + seg_frames
lo, hi = max(0, start - context_frames), min(vad.shape[-1], end + context_frames)
chunk = vad[:, lo:hi]
pad_left = max(0, context_frames - start)
pad_right = max(0, (end + context_frames) - vad.shape[-1])
if pad_left or pad_right:
chunk = np.pad(chunk, ((0, 0), (pad_left, pad_right)))
return chunk
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--fisher-path", required=True)
parser.add_argument("--frame-hz", type=float, required=True)
parser.add_argument("--splits", nargs="+", default=["train", "val", "test"])
parser.add_argument("--overwrite", action="store_true")
args = parser.parse_args()
root = Path(args.fisher_path)
label = f"{args.frame_hz:g}".replace(".", "_")
context_frames = int(round(CONTEXT_SEC * args.frame_hz))
# One rasterization per conversation, then slice every segment out of it.
by_conversation = defaultdict(list)
for split in args.splits:
for line in open(root / f"{split}.txt"):
if not line.strip():
continue
part, group, conversation, seg_id, start, *_ = line.split()
by_conversation[(part, group, conversation)].append((int(seg_id), float(start)))
written = skipped = 0
for index, ((part, group, conversation), segments) in enumerate(sorted(by_conversation.items()), 1):
out = root / part / "seg" / f"vad{label}" / group / conversation
out.mkdir(parents=True, exist_ok=True)
targets = [(s, p) for s, p in ((s, out / f"{s}.npy") for s, _ in segments)
if args.overwrite or not p.exists()]
if not targets:
skipped += len(segments)
continue
last_start = max(start for _, start in segments)
frames = int(round((last_start + SEG_SEC + CONTEXT_SEC) * args.frame_hz)) + 1
vad = rasterize(root / part / "regions_vap" / group, conversation, frames, args.frame_hz)
starts = dict(segments)
for seg_id, path in targets:
np.save(path, segment_window(vad, starts[seg_id], args.frame_hz, context_frames))
written += 1
if index % 500 == 0:
print(f" {index}/{len(by_conversation)} conversations, {written} written", flush=True)
print(f"done: {written} written, {skipped} already present, "
f"{len(by_conversation)} conversations -> seg/vad{label}/")
if __name__ == "__main__":
main()