#!/usr/bin/env python3 """Letterbox 224 preprocessed crop'ları WebDataset shard'larına yaz. Config-driven preprocessing.yaml'dan okur. Pipeline: read → CLAHE (conditional) → gamma → letterbox 224 → JPEG MSII proxy optional 2. shard olarak. """ import json, os, io, time, argparse from pathlib import Path from concurrent.futures import ProcessPoolExecutor import sys ROOT = Path("/arf/scratch/stakan/hitit-proje") SOURCES = ROOT / "datasets" / "sources" sys.path.insert(0, str(ROOT / "hitit_ocr" / "src")) def process_one(item): """Tek image: preprocess et, JPEG bytes döndür.""" rid, path, label, source, fold = item try: import numpy as np from PIL import Image import cv2 from preprocessing.pipeline import load_config, apply_clahe, apply_gamma, letterbox, is_low_quality, msii_proxy cfg = load_config() img = np.array(Image.open(path).convert('RGB')) enh = cfg.get('enhancement', {}) if enh.get('clahe', {}).get('enabled') and (not enh['clahe'].get('conditional') or is_low_quality(img)): img = apply_clahe(img, clip_limit=enh['clahe'].get('clip_limit', 2.5)) if enh.get('gamma_correction', {}).get('enabled') and (not enh['gamma_correction'].get('conditional') or is_low_quality(img)): img = apply_gamma(img, gamma=enh['gamma_correction'].get('gamma', 1.2)) lb = cfg.get('geometric', {}).get('letterbox', {}) img = letterbox(img, target=lb.get('target_size', 224), margin_ratio=lb.get('margin_ratio', 0.1)) # Encode JPEG buf = io.BytesIO() Image.fromarray(img).save(buf, format='JPEG', quality=90) jpeg_bytes = buf.getvalue() return (rid, jpeg_bytes, label, source, fold) except Exception as e: return (rid, None, None, None, None) def main(): import webdataset as wds ap = argparse.ArgumentParser() ap.add_argument('--subset', default='curated', help='curated|all|hitit') ap.add_argument('--workers', type=int, default=64) ap.add_argument('--shard-size', type=int, default=2000) ap.add_argument('--out', default=str(ROOT / "datasets" / "streaming" / "preprocessed_224")) args = ap.parse_args() # Topla items = [] for d in sorted(SOURCES.iterdir()): if not d.is_dir(): continue mp = d / "manifest_classification.jsonl" if not mp.exists(): continue with open(mp) as f: for line in f: r = json.loads(line) if r.get('storage') != 'fs' or not r.get('path') or not os.path.exists(r['path']): continue if r.get('quality_gate_pass') is False: continue if args.subset == 'curated' and not r.get('in_curated_pretrain'): continue if args.subset == 'hitit' and r.get('source') != 'hitit': continue items.append((r['id'], r['path'], r.get('unified_label',''), r.get('source',''), r.get('fold', 0))) print(f"İşlenecek: {len(items):,}", flush=True) out_dir = Path(args.out) out_dir.mkdir(parents=True, exist_ok=True) pattern = str(out_dir / "preprocessed-%06d.tar") sink = wds.ShardWriter(pattern, maxcount=args.shard_size, encoder=False) t0 = time.time() done = 0 try: with ProcessPoolExecutor(max_workers=args.workers) as ex: for res in ex.map(process_one, items, chunksize=50): rid, jpeg_bytes, label, source, fold = res if jpeg_bytes: sink.write({ '__key__': rid, 'jpeg': jpeg_bytes, 'json': json.dumps({ 'id': rid, 'unified_label': label, 'source': source, 'fold': fold, }, ensure_ascii=False).encode('utf-8'), }) done += 1 if done % 5000 == 0 and done > 0: rate = done / max(time.time() - t0, 1) print(f" {done:,}/{len(items):,} ({rate:.0f} img/s)", flush=True) finally: sink.close() # Summary shards = sorted(out_dir.glob("preprocessed-*.tar")) total_size = sum(s.stat().st_size for s in shards) print(f"\n{len(shards)} shard, {done:,} kayıt, {total_size/1024/1024:.1f} MB") meta = { "format": "WebDataset (preprocessed letterbox 224, CLAHE+gamma conditional)", "n_shards": len(shards), "n_records": done, "total_size_mb": round(total_size/1024/1024, 1), "pattern": f"preprocessed-{{000000..{len(shards)-1:06d}}}.tar", "config_version": "1.0", "preprocessing_steps": ["CLAHE (conditional)", "Gamma (conditional)", "Letterbox 224 median-fill"], } with open(out_dir / "shards.json", 'w') as f: json.dump(meta, f, indent=2, ensure_ascii=False) if __name__ == '__main__': main()