EchoLoc / data_construction /prepare_iter006.py
zsy814's picture
Initial EchoLoc code release
d8bfe4a verified
Raw
History Blame Contribute Delete
15.9 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Prepare and assemble iter006 data from iter005 Chinese assets plus new English assets."""
from __future__ import annotations
import argparse
import json
import shutil
from pathlib import Path
from typing import Any, Dict, Iterable, List, Set
DEFAULT_ITER005 = Path("/workspace/echoloc/Dataset/Novel/query_data/supervisor_iterations/iter_005")
DEFAULT_ITER006 = Path("/workspace/echoloc/Dataset/Novel/query_data/supervisor_iterations/iter_006")
def read_jsonl(path: Path) -> List[Dict[str, Any]]:
rows: List[Dict[str, Any]] = []
if not path.exists():
return rows
with path.open("r", encoding="utf-8") as handle:
for line in handle:
line = line.strip()
if line:
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
rows.append(json.loads(line.lstrip("\ufeff;;")))
return rows
def write_jsonl(path: Path, rows: Iterable[Dict[str, Any]]) -> int:
path.parent.mkdir(parents=True, exist_ok=True)
n = 0
with path.open("w", encoding="utf-8") as handle:
for row in rows:
handle.write(json.dumps(row, ensure_ascii=False) + "\n")
n += 1
return n
def qid(row: Dict[str, Any]) -> str:
return str(row.get("qid") or row.get("id") or "")
def is_vstyle_en(row: Dict[str, Any]) -> bool:
cur = qid(row)
if cur.startswith("vstyle_en_"):
return True
language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "").lower()
return language == "en" and cur.startswith("vstyle_")
def is_vstyle_zh(row: Dict[str, Any]) -> bool:
cur = qid(row)
if cur.startswith("vstyle_en_"):
return False
if cur.startswith("vstyle_zh_"):
return True
language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "").lower()
return language == "zh" and cur.startswith("vstyle_")
def is_story_zh(row: Dict[str, Any]) -> bool:
cur = qid(row)
if cur.startswith("vstyle_") or cur.startswith("story_en_"):
return False
if "vstyle_en_" in cur:
return False
language = str(row.get("language") or ((row.get("source") or {}).get("language")) or "zh").lower()
return language == "zh"
def is_zh_reuse(row: Dict[str, Any]) -> bool:
return is_story_zh(row) or is_vstyle_zh(row)
def mark_language(row: Dict[str, Any], language: str) -> Dict[str, Any]:
row = dict(row)
row["language"] = language
source = row.get("source")
if isinstance(source, dict):
source = dict(source)
source["language"] = language
row["source"] = source
source_candidate = row.get("source_query_candidate")
if isinstance(source_candidate, dict):
source_candidate = dict(source_candidate)
source_candidate["language"] = language
row["source_query_candidate"] = source_candidate
return row
def by_qid(rows: Iterable[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
out: Dict[str, Dict[str, Any]] = {}
for row in rows:
cur = qid(row)
if cur:
out[cur] = row
return out
def read_first_jsonl(paths: Iterable[Path]) -> List[Dict[str, Any]]:
for path in paths:
rows = read_jsonl(path)
if rows:
return rows
return []
def copy_if_exists(src: Path, dst: Path) -> bool:
if not src.exists():
return False
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
return True
def extract_vstyle_en(iter005: Path, iter006: Path) -> Dict[str, Any]:
out_dir = iter006 / "english_rebuild" / "vstyle_en"
out_dir.mkdir(parents=True, exist_ok=True)
candidates = [mark_language(row, "en") for row in read_jsonl(iter005 / "filtered/query_candidates_keep.jsonl") if is_vstyle_en(row)]
controls = [mark_language(row, "en") for row in read_jsonl(iter005 / "filtered/query_tts_qwen3_input_structured.jsonl") if is_vstyle_en(row)]
eval_rows = [dict(row, language="en") for row in read_jsonl(iter005 / "eval_inputs/e2e_input_aligned.jsonl") if is_vstyle_en(row)]
counts = {
"vstyle_en_candidates": write_jsonl(out_dir / "query_candidates.jsonl", candidates),
"vstyle_en_candidates_keep": write_jsonl(out_dir / "filtered/query_candidates_keep.jsonl", candidates),
"vstyle_en_query_tts_controls": write_jsonl(out_dir / "filtered/query_tts_qwen3_input_structured.jsonl", controls),
"vstyle_en_existing_eval_rows": write_jsonl(out_dir / "existing_eval_audio_rows.jsonl", eval_rows),
}
(out_dir / "README_iter006_vstyle_en.txt").write_text(
"Use filtered/query_candidates_keep.jsonl as RESPONSE_IN for English response/thinker target regeneration. "
"existing_eval_audio_rows.jsonl preserves iter005 query_audio_path for vstyle_en.\n",
encoding="utf-8",
)
return {"out_dir": str(out_dir), "counts": counts}
def copy_zh_reuse(iter005: Path, iter006: Path) -> Dict[str, Any]:
out_dir = iter006 / "zh_reuse_from_iter005"
out_dir.mkdir(parents=True, exist_ok=True)
specs = [
("query_candidates.jsonl", "query_candidates.jsonl"),
("filtered/query_candidates_keep.jsonl", "filtered/query_candidates_keep.jsonl"),
("filtered/query_tts_qwen3_input_structured.jsonl", "filtered/query_tts_qwen3_input_structured.jsonl"),
("thinker_targets/response_controls.jsonl", "thinker_targets/response_controls.jsonl"),
("thinker_targets/thinker_targets.jsonl", "thinker_targets/thinker_targets.jsonl"),
("eval_inputs/e2e_input_aligned.jsonl", "eval_inputs/e2e_input_aligned.jsonl"),
("eval_inputs/talker_input_all.jsonl", "eval_inputs/talker_input_all.jsonl"),
("eval_inputs/response_tts_input_aligned.jsonl", "eval_inputs/response_tts_input_aligned.jsonl"),
]
counts: Dict[str, Any] = {}
for rel_src, rel_dst in specs:
rows = [row for row in read_jsonl(iter005 / rel_src) if is_zh_reuse(row)]
counts[rel_dst] = write_jsonl(out_dir / rel_dst, rows)
for rel in ["sft/thinker_emochange/train.jsonl", "sft/thinker_emochange/val.jsonl", "sft/thinker_emochange/manifest.json"]:
counts[rel] = "not_copied_final_sft_rebuilt_from_merged_eval"
return {"out_dir": str(out_dir), "counts": counts}
def merge_unique(parts: Iterable[Iterable[Dict[str, Any]]]) -> List[Dict[str, Any]]:
out: Dict[str, Dict[str, Any]] = {}
for rows in parts:
for row in rows:
cur = qid(row)
if cur:
out[cur] = row
return [out[key] for key in sorted(out)]
def merge_eval_with_existing_audio(new_eval_rows: List[Dict[str, Any]], existing_audio_rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
audio_by_qid = by_qid(existing_audio_rows)
merged = []
for row in new_eval_rows:
cur = qid(row)
audio = audio_by_qid.get(cur)
if audio:
row = dict(row)
row["query_audio_path"] = audio.get("query_audio_path", row.get("query_audio_path", ""))
row["query_audio_control_path"] = audio.get("query_audio_control_path", row.get("query_audio_control_path", ""))
merged.append(row)
return merged
def merge_final(iter005: Path, iter006: Path, story_en_root: Path, vstyle_en_root: Path) -> Dict[str, Any]:
zh_root = iter006 / "zh_reuse_from_iter005"
final_eval = iter006 / "eval_inputs"
final_targets = iter006 / "thinker_targets"
final_filtered = iter006 / "filtered"
if not zh_root.exists():
copy_zh_reuse(iter005, iter006)
zh_candidates = read_jsonl(zh_root / "query_candidates.jsonl")
zh_keep = read_jsonl(zh_root / "filtered/query_candidates_keep.jsonl")
zh_controls = read_jsonl(zh_root / "filtered/query_tts_qwen3_input_structured.jsonl")
zh_responses = read_jsonl(zh_root / "thinker_targets/response_controls.jsonl")
zh_targets = read_jsonl(zh_root / "thinker_targets/thinker_targets.jsonl")
zh_e2e = read_jsonl(zh_root / "eval_inputs/e2e_input_aligned.jsonl")
zh_talker = read_jsonl(zh_root / "eval_inputs/talker_input_all.jsonl")
zh_response_aligned = read_jsonl(zh_root / "eval_inputs/response_tts_input_aligned.jsonl")
story_candidates = read_jsonl(story_en_root / "query_candidates.jsonl")
story_keep = read_jsonl(story_en_root / "filtered/query_candidates_keep.jsonl")
story_controls = read_jsonl(story_en_root / "filtered/query_tts_qwen3_input_structured.jsonl")
story_responses = read_jsonl(story_en_root / "thinker_targets/response_controls.jsonl")
story_targets = read_jsonl(story_en_root / "thinker_targets/thinker_targets.jsonl")
story_e2e = read_jsonl(story_en_root / "eval_inputs/e2e_input_aligned.jsonl")
story_talker = read_jsonl(story_en_root / "eval_inputs/talker_input_all.jsonl")
story_response_aligned = read_jsonl(story_en_root / "eval_inputs/response_tts_input_aligned.jsonl")
vstyle_candidates = read_first_jsonl([vstyle_en_root / "filtered/query_candidates_keep.jsonl", vstyle_en_root / "query_candidates_keep.jsonl"])
vstyle_controls = read_first_jsonl([vstyle_en_root / "filtered/query_tts_qwen3_input_structured.jsonl", vstyle_en_root / "query_tts_qwen3_input_structured.jsonl"])
vstyle_responses = read_jsonl(vstyle_en_root / "thinker_targets/response_controls.jsonl")
vstyle_targets = read_jsonl(vstyle_en_root / "thinker_targets/thinker_targets.jsonl")
vstyle_export = read_jsonl(vstyle_en_root / "eval_inputs/e2e_input_aligned.jsonl")
vstyle_existing_audio = read_jsonl(vstyle_en_root / "existing_eval_audio_rows.jsonl")
if not vstyle_export and vstyle_responses and vstyle_targets and vstyle_existing_audio:
response_by_qid = by_qid(vstyle_responses)
audio_by_qid = by_qid(vstyle_existing_audio)
vstyle_export = []
for target in vstyle_targets:
cur = qid(target)
response = response_by_qid.get(cur, {})
audio = audio_by_qid.get(cur, {})
style = str((target.get("combined") or {}).get("instruct") or "").strip()
text = str((target.get("combined") or {}).get("txt") or "").strip()
visible_query = response.get("visible_query") or (response.get("source_query_candidate") or {}).get("visible_query") or target.get("source_query") or {}
vstyle_export.append(
{
"id": cur,
"qid": cur,
"query_type": target.get("query_type") or response.get("query_type"),
"language": "en",
"query_text": visible_query.get("text", audio.get("query_text", "")) if isinstance(visible_query, dict) else "",
"query_audio_path": audio.get("query_audio_path", ""),
"query_audio_control_path": audio.get("query_audio_control_path", ""),
"oracle_thinker_style": style,
"oracle_thinker_text": text,
"oracle_response_text": response.get("audio_content", text.replace(" <|EMO_CHANGE|> ", " ")),
"response_control": response.get("final_generated_control") or response.get("response_generated_control"),
"source_event": response.get("source_event"),
}
)
vstyle_e2e = merge_eval_with_existing_audio(vstyle_export, vstyle_existing_audio)
vstyle_talker = read_jsonl(vstyle_en_root / "eval_inputs/talker_input_all.jsonl")
vstyle_response_aligned = read_jsonl(vstyle_en_root / "eval_inputs/response_tts_input_aligned.jsonl")
if not vstyle_talker and vstyle_targets:
vstyle_talker = []
for target in vstyle_targets:
cur = qid(target)
combined = target.get("combined") or {}
vstyle_talker.append(
{
"id": cur,
"qid": cur,
"language": "en",
"ability": "constructed_thinker_to_talker",
"query_type": target.get("query_type"),
"thinker_style": str(combined.get("instruct") or "").strip(),
"thinker_text": str(combined.get("txt") or "").strip(),
"need_emochange": target.get("need_emochange"),
"source_query": target.get("source_query"),
"aligned_with_query_audio": cur in {qid(row) for row in vstyle_e2e},
}
)
if not vstyle_response_aligned and vstyle_responses and vstyle_e2e:
e2e_qids = {qid(row) for row in vstyle_e2e}
vstyle_response_aligned = [row for row in vstyle_responses if qid(row) in e2e_qids]
counts = {
"query_candidates": write_jsonl(iter006 / "query_candidates.jsonl", merge_unique([zh_candidates, story_candidates, vstyle_candidates])),
"query_candidates_keep": write_jsonl(final_filtered / "query_candidates_keep.jsonl", merge_unique([zh_keep, story_keep, vstyle_candidates])),
"query_tts_controls": write_jsonl(final_filtered / "query_tts_qwen3_input_structured.jsonl", merge_unique([zh_controls, story_controls, vstyle_controls])),
"response_controls": write_jsonl(final_targets / "response_controls.jsonl", merge_unique([zh_responses, story_responses, vstyle_responses])),
"thinker_targets": write_jsonl(final_targets / "thinker_targets.jsonl", merge_unique([zh_targets, story_targets, vstyle_targets])),
"e2e_input_aligned": write_jsonl(final_eval / "e2e_input_aligned.jsonl", merge_unique([zh_e2e, story_e2e, vstyle_e2e])),
"talker_input_all": write_jsonl(final_eval / "talker_input_all.jsonl", merge_unique([zh_talker, story_talker, vstyle_talker])),
"response_tts_input_aligned": write_jsonl(final_eval / "response_tts_input_aligned.jsonl", merge_unique([zh_response_aligned, story_response_aligned, vstyle_response_aligned])),
}
(iter006 / "iter006_merge_summary.json").write_text(json.dumps(counts, ensure_ascii=False, indent=2), encoding="utf-8")
return {"iter006": str(iter006), "counts": counts}
def counts(iter006: Path) -> Dict[str, Any]:
paths = [
"query_candidates.jsonl",
"filtered/query_candidates_keep.jsonl",
"filtered/query_tts_qwen3_input_structured.jsonl",
"thinker_targets/response_controls.jsonl",
"thinker_targets/thinker_targets.jsonl",
"eval_inputs/e2e_input_aligned.jsonl",
"eval_inputs/talker_input_all.jsonl",
"eval_inputs/response_tts_input_aligned.jsonl",
"sft/thinker_emochange/train.jsonl",
"sft/thinker_emochange/val.jsonl",
]
return {rel: len(read_jsonl(iter006 / rel)) for rel in paths}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("mode", choices=["copy-zh", "extract-vstyle-en", "merge-final", "counts"])
parser.add_argument("--iter005", type=Path, default=DEFAULT_ITER005)
parser.add_argument("--iter006", type=Path, default=DEFAULT_ITER006)
parser.add_argument("--story_en_root", type=Path, default=DEFAULT_ITER006 / "english_rebuild/story_en")
parser.add_argument("--vstyle_en_root", type=Path, default=DEFAULT_ITER006 / "english_rebuild/vstyle_en")
args = parser.parse_args()
if args.mode == "copy-zh":
result = copy_zh_reuse(args.iter005, args.iter006)
elif args.mode == "extract-vstyle-en":
result = extract_vstyle_en(args.iter005, args.iter006)
elif args.mode == "merge-final":
result = merge_final(args.iter005, args.iter006, args.story_en_root, args.vstyle_en_root)
else:
result = {"iter006": str(args.iter006), "counts": counts(args.iter006)}
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())