| |
| """Combine all text/transliteration sources into a single sign-sequence corpus |
| for BERT-style Masked Sign Language Model pretraining. |
| |
| Sources aggregated: |
| - tlhdig/corpus.jsonl (Hittite transliteration, primary) |
| - cuneiml/manifest.jsonl (Sumerian/Akkadian, larger but different script era) |
| - transliterated_fragments/manifest.jsonl (mixed period fragments) |
| |
| Output: JSONL of {source, tablet_id, signs: [str,...], lang, period} |
| Records whose sign sequence has no token in label_to_idx are skipped. |
| """ |
| import json, argparse, re |
| from pathlib import Path |
| from collections import Counter |
|
|
| ROOT = Path("/arf/scratch/stakan/hitit-proje") |
|
|
|
|
| def _norm_tlh_token(t): |
| """Strip determinatives/numerics from TLHdig transliteration tokens. |
| 'URUne-ri' → 'ne'; strip leading 'URU/DINGIR/GIŠ/KUR/GÍŠ' + subscripts. |
| Splits on hyphens and returns list of normalized tokens.""" |
| if not t: return [] |
| |
| cleaned = re.sub(r'[₀-₉0-9]', '', t) |
| |
| parts = [p.strip() for p in cleaned.split('-') if p.strip()] |
| |
| parts = [p for p in parts if not re.match(r'^[<\[\]]', p)] |
| |
| out = [] |
| for p in parts: |
| m = re.match(r'^(URU|DINGIR|GIŠ|KUR|GÍŠ|MUNUS|LÚ)(.+)$', p) |
| if m: out.append(m.group(2)) |
| else: out.append(p) |
| return out |
|
|
|
|
| def iter_tlhdig(path, l2i): |
| with open(path) as f: |
| for line in f: |
| try: r = json.loads(line) |
| except: continue |
| words = r.get('words') or [] |
| signs = [] |
| for w in words: |
| if not isinstance(w, dict): continue |
| for tok in _norm_tlh_token(w.get('text') or ''): |
| if tok in l2i: |
| signs.append(tok) |
| if len(signs) < 3: continue |
| yield { |
| 'source': 'tlhdig', |
| 'tablet_id': r.get('tablet') or '', |
| 'signs': signs, |
| 'lang': r.get('lang') or 'hit', |
| 'period': 'Hittite', |
| } |
|
|
|
|
| def iter_cuneiml(path, l2i): |
| """cuneiml stores sign sequences inside extra.text.{obverse,reverse}[*].sign.""" |
| with open(path) as f: |
| for line in f: |
| try: r = json.loads(line) |
| except: continue |
| extra = r.get('extra') or {} |
| text = extra.get('text') or {} |
| all_signs = [] |
| for side in ('obverse', 'reverse'): |
| for row in text.get(side) or []: |
| if not isinstance(row, dict): continue |
| for s in row.get('sign') or []: |
| if not isinstance(s, str): continue |
| for c in s: |
| if c in l2i: |
| all_signs.append(c) |
| if len(all_signs) < 3: continue |
| yield { |
| 'source': 'cuneiml', |
| 'tablet_id': r.get('tablet_id') or '', |
| 'signs': all_signs, |
| 'lang': r.get('language') or 'sum', |
| 'period': r.get('period') or 'Ur-III', |
| } |
|
|
|
|
| def iter_transliterated_fragments(path, l2i): |
| with open(path) as f: |
| for line in f: |
| try: r = json.loads(line) |
| except: continue |
| extra = r.get('extra') or {} |
| |
| sign_seq = extra.get('signs') or extra.get('sign_sequence') or [] |
| if not sign_seq: |
| cun = extra.get('cuneiform') or r.get('cuneiform') or '' |
| sign_seq = [c for c in cun if c in l2i] |
| else: |
| sign_seq = [s for s in sign_seq if s in l2i] |
| if len(sign_seq) < 3: continue |
| yield { |
| 'source': 'transliterated_fragments', |
| 'tablet_id': r.get('tablet_id') or '', |
| 'signs': sign_seq, |
| 'lang': r.get('language') or 'akk', |
| 'period': r.get('period') or 'mixed', |
| } |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument('--label-to-idx', required=True) |
| ap.add_argument('--output', required=True) |
| args = ap.parse_args() |
|
|
| import torch |
| ck = torch.load(args.label_to_idx, map_location='cpu', weights_only=False) |
| l2i = ck['label_to_idx'] |
| print(f"Hittite label vocab: {len(l2i)}") |
|
|
| sources = [ |
| ('tlhdig', ROOT / 'datasets/sources/tlhdig/corpus.jsonl', iter_tlhdig), |
| ('cuneiml', ROOT / 'datasets/sources/cuneiml/manifest.jsonl', iter_cuneiml), |
| ('transliterated_fragments', |
| ROOT / 'datasets/sources/transliterated_fragments/manifest.jsonl', |
| iter_transliterated_fragments), |
| ] |
|
|
| total = 0 |
| stats = Counter() |
| with open(args.output, 'w') as out: |
| for name, path, iterfn in sources: |
| if not path.exists(): |
| print(f" {name}: missing — skip") |
| continue |
| n = 0 |
| for rec in iterfn(path, l2i): |
| out.write(json.dumps(rec, ensure_ascii=False) + '\n') |
| n += 1 |
| stats[name] = n |
| total += n |
| print(f" {name}: {n} records") |
|
|
| print(f"\nTotal text-corpus records: {total}") |
| print(f"Saved → {args.output}") |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|