File size: 5,692 Bytes
f211247 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | #!/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()
|