hitit-cuneiform-ocr / code /src /preprocessing /restore_ebl_crops.py
savastakan's picture
Initial upload: code + 5 record checkpoints + fuse
f211247 verified
Raw
History Blame Contribute Delete
5.69 kB
#!/usr/bin/env python3
"""
Restore missing ebl crops under hitit_ocr/data/classification/all/<label>/ebl_<image>_<idx>.png.
Legacy prepare_data.py load_ebl_coco parses COCO annotations and writes crops with:
tablet_key = f"ebl_{img.file_name without ext}"
crop_name = f"{tablet_key}_{idx}.png"
where idx = position in anns_by_img[img_id].
We mirror that: parse the same two COCO jsons (train2017, val2017), build per-image annotation
lists in the same order as the legacy code, then for each manifest record whose path matches
ebl_<key>_<idx>.png, recompute the bbox, pad, and save.
"""
import json
import sys
from collections import Counter, defaultdict
from pathlib import Path
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
MIN_CROP_SIZE = 8
CONTEXT_PAD_RATIO = 0.15
PROJECT = Path("/arf/scratch/stakan/hitit-proje")
COCO_BASE = PROJECT / "datasets/sources/ebl_ocr/ready-for-training/coco-recognition/data/coco"
MANIFEST = PROJECT / "datasets/sources/hitit_local/manifest_v13_ultimate.jsonl"
def normalize_sign(s):
if s is None:
return None
s = s.strip().rstrip(".,;: ").strip()
if not s or s in ["/", ".", ",", "-", "(X)", "(x)", "X", "x", "?", ""]:
return None
if s.isdigit():
return None
if len(s) == 1 and not s.isalpha():
return None
return s
def build_coco_tablets():
"""Mirror load_ebl_coco return: {tablet_key: {image_path, width, height, signs[]}}."""
tablets = {}
for split, img_folder in [("instances_train2017.json", "train2017"),
("instances_val2017.json", "val2017")]:
ann_file = COCO_BASE / "annotations" / split
img_dir = COCO_BASE / img_folder
if not ann_file.exists():
continue
with open(ann_file) as f:
coco = json.load(f)
cat_map = {c["id"]: c["name"] for c in coco["categories"]}
img_map = {img["id"]: img for img in coco["images"]}
anns_by_img = defaultdict(list)
for ann in coco["annotations"]:
anns_by_img[ann["image_id"]].append(ann)
for img_id, img_info in img_map.items():
img_file = img_dir / img_info["file_name"]
if not img_file.exists():
continue
signs = []
for ann in anns_by_img.get(img_id, []):
bx, by, bw, bh = ann["bbox"]
cat_name = cat_map.get(ann["category_id"], "")
cat_name = normalize_sign(cat_name) if cat_name else None
if not cat_name:
continue
if bw > 0 and bh > 0:
signs.append((bx, by, bw, bh, cat_name))
if not signs:
continue
tablet_key = f"ebl_{img_info['file_name'].replace('.jpg', '').replace('.png', '')}"
tablets[tablet_key] = {
"image_path": str(img_file),
"width": img_info["width"],
"height": img_info["height"],
"signs": signs,
}
return tablets
def missing_ebl_records():
out = []
with open(MANIFEST) as f:
for line in f:
r = json.loads(line)
if r.get("source") != "ebl":
continue
p = r.get("path", "")
if "/classification/all/" not in p:
continue
if Path(p).exists():
continue
out.append(r)
return out
def parse_key_idx(stem):
"""'ebl_CBS.1515-0_165' -> ('ebl_CBS.1515-0', 165)."""
parts = stem.rsplit("_", 1)
if len(parts) != 2 or not parts[1].isdigit():
return None, None
return parts[0], int(parts[1])
def main():
print("[plan] parsing COCO…")
tablets = build_coco_tablets()
print(f" COCO tablets: {len(tablets)}")
print("[plan] scanning manifest for missing ebl records…")
missing = missing_ebl_records()
print(f" missing: {len(missing)}")
# Group by tablet_key
by_key = defaultdict(list)
for r in missing:
key, idx = parse_key_idx(Path(r["path"]).stem)
if key is None:
continue
by_key[key].append((idx, r))
stats = Counter()
for key, items in by_key.items():
info = tablets.get(key)
if info is None:
stats["no_tablet"] += len(items)
continue
img_w, img_h, signs = info["width"], info["height"], info["signs"]
try:
img = Image.open(info["image_path"]).convert("RGB")
except Exception:
stats["img_fail"] += len(items)
continue
for idx, r in items:
if idx >= len(signs):
stats["idx_oor"] += 1
continue
x, y, bw, bh, _ = signs[idx]
px = bw * CONTEXT_PAD_RATIO
py = bh * CONTEXT_PAD_RATIO
x1 = max(0, int(x - px))
y1 = max(0, int(y - py))
x2 = min(img_w, int(x + bw + px))
y2 = min(img_h, int(y + bh + py))
if x2 - x1 < MIN_CROP_SIZE or y2 - y1 < MIN_CROP_SIZE:
stats["too_small"] += 1
continue
out = Path(r["path"])
out.parent.mkdir(parents=True, exist_ok=True)
try:
img.crop((x1, y1, x2, y2)).save(str(out))
stats["written"] += 1
man_w, man_h = r.get("width"), r.get("height")
if man_w != x2 - x1 or man_h != y2 - y1:
stats["size_mismatch"] += 1
except Exception:
stats["save_fail"] += 1
img.close()
print("DONE:", dict(stats))
if __name__ == "__main__":
main()