| """Parse release files + build the group index + enumerate training pairs. |
| FROZEN — do not modify (these define the task and the CV labels). |
| """ |
| import json |
| import os |
| from collections import defaultdict |
| from typing import Dict, List, Tuple |
|
|
| from .schema import Option, Question, TrainItem |
|
|
|
|
| def group_id(qid: str) -> str: |
| return qid.rsplit("_", 1)[0] |
|
|
|
|
| def build_index(cfg: dict) -> Tuple[List[Question], Dict[str, List[str]]]: |
| qs: List[Question] = [] |
| groups: Dict[str, List[str]] = defaultdict(list) |
| for name, rel in cfg["subsets"].items(): |
| path = os.path.join(cfg["data_root"], rel) |
| base = os.path.dirname(path) |
| for r in json.load(open(path, encoding="utf-8")): |
| opts = [Option(letter=k[-1], wav=os.path.join(base, v)) |
| for k, v in sorted(r["options"].items())] |
| q = Question( |
| subset=name, qid=r["question_id"], group=group_id(r["question_id"]), |
| utterance_text=r["utterance"], |
| utterance_wav=os.path.join(base, r["utterance_audio"]), |
| context=r["context"], reference=r["response"], options=opts) |
| qs.append(q) |
| groups[q.group].append(q.qid) |
| |
| assert len(qs) == 530, f"expected 530 questions, got {len(qs)}" |
| assert len(groups) == 200, f"expected 200 groups, got {len(groups)}" |
| return qs, dict(groups) |
|
|
|
|
| def build_train_items(cfg: dict) -> List[TrainItem]: |
| """Enumerate labelled (utterance, goodPara, badPara) triples for CV. |
| |
| Grouped by dialogue id so the group-aware CV split keeps a dialogue together, |
| mirroring the test structure (group = dialogue, items = context/tone variants). |
| """ |
| root = cfg["data_root"] |
| items: List[TrainItem] = [] |
|
|
| |
| d = os.path.join(root, "empatheticDialogue_t_multi-context") |
| mc = os.path.join(root, cfg["train"]["multi_context"]) |
| if os.path.exists(mc): |
| for e in map(json.loads, open(mc, encoding="utf-8")): |
| emo = e["emotion"] |
| for i, ctx in enumerate(e["contexts"], start=1): |
| utt = f'{d}/user_audio/{e["id"]}_{i}_{emo}.wav' |
| good = f'{d}/response_audio/{e["id"]}_{i}_goodPara.wav' |
| bad = f'{d}/response_audio/{e["id"]}_{i}_badPara.wav' |
| if all(os.path.exists(p) for p in (utt, good, bad)): |
| items.append(TrainItem(e["id"], "multi_context", str(i), |
| utt, good, bad, ctx["context"], |
| ctx["response"], emo)) |
|
|
| |
| d = os.path.join(root, "empatheticDialogue_n_multi-emotion") |
| me = os.path.join(root, cfg["train"]["multi_emotion"]) |
| if os.path.exists(me): |
| for e in map(json.loads, open(me, encoding="utf-8")): |
| ctx = e["contexts"]["Context"] |
| emos = sorted({k[:-len("_response")] for k in e["contexts"] |
| if k.endswith("_response")}) |
| for em in emos: |
| utt = f'{d}/user_audio/{e["id"]}_{em}.wav' |
| good = f'{d}/response_audio/{e["id"]}_{em}_goodPara.wav' |
| bad = f'{d}/response_audio/{e["id"]}_{em}_badPara.wav' |
| if all(os.path.exists(p) for p in (utt, good, bad)): |
| items.append(TrainItem(e["id"], "multi_emotion", em, utt, |
| good, bad, ctx, |
| e["contexts"][f"{em}_response"], em)) |
| return items |
|
|