Download code/convert_to_coco.py from savastakan/hitit-cuneiform-ocr: direct link, hf CLI and curl.
- Browser
- Download file 18.2 kB
-
https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/convert_to_coco.py
- Command line
-
hf download hf://savastakan/hitit-cuneiform-ocr/code/convert_to_coco.py
-
curl -L -o convert_to_coco.py https://huggingface.co/savastakan/hitit-cuneiform-ocr/resolve/main/code/convert_to_coco.py
18.2 kB
| #!/usr/bin/env python3 | |
| """ | |
| Hitit civi yazisi tablet anotasyonlarini (mark.txt) COCO formatina donusturur. | |
| Kullanim: | |
| python3 convert_to_coco.py --inspect # Sadece format kesfet, donusturme | |
| python3 convert_to_coco.py --convert # COCO formatina donustur | |
| python3 convert_to_coco.py --convert --merge-ebl # eBL verileriyle birlestir | |
| Cikti: | |
| output/coco_hitit_train.json | |
| output/coco_hitit_val.json | |
| output/images/ (symlink'ler) | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import glob | |
| import argparse | |
| import random | |
| from collections import defaultdict | |
| from pathlib import Path | |
| BASE_DIR = Path(__file__).parent | |
| TABLETS_DIR = BASE_DIR / "datasets" / "sources" / "hitit_local" | |
| EBL_DIR = BASE_DIR / "datasets" / "sources" / "ebl_ocr" / "ready-for-training" | |
| OUTPUT_DIR = BASE_DIR / "output" | |
| # ============================================================ | |
| # ADIM 1: mark.txt formatini kesfet | |
| # ============================================================ | |
| def inspect_mark_file(path): | |
| """Tek bir mark.txt dosyasinin yapisini analiz et.""" | |
| with open(path, "r", encoding="utf-8") as f: | |
| raw = f.read() | |
| # JSON parse | |
| try: | |
| data = json.loads(raw) | |
| except json.JSONDecodeError: | |
| # Belki UTF-16? | |
| try: | |
| with open(path, "r", encoding="utf-16") as f: | |
| raw = f.read() | |
| data = json.loads(raw) | |
| except: | |
| print(f" HATA: {path} JSON olarak parse edilemedi") | |
| print(f" Ilk 500 karakter: {raw[:500]}") | |
| return None | |
| return data | |
| def describe_structure(data, prefix="", depth=0, max_depth=4): | |
| """JSON yapisini ozetler.""" | |
| indent = " " * depth | |
| if depth > max_depth: | |
| print(f"{indent}...") | |
| return | |
| if isinstance(data, dict): | |
| print(f"{indent}{prefix}dict (anahtarlar: {list(data.keys())})") | |
| for key in list(data.keys())[:15]: | |
| val = data[key] | |
| if isinstance(val, (dict, list)): | |
| describe_structure(val, prefix=f'"{key}": ', depth=depth + 1, max_depth=max_depth) | |
| else: | |
| valstr = str(val) | |
| if len(valstr) > 100: | |
| valstr = valstr[:100] + "..." | |
| print(f"{indent} \"{key}\": {type(val).__name__} = {valstr}") | |
| elif isinstance(data, list): | |
| print(f"{indent}{prefix}list (uzunluk: {len(data)})") | |
| if len(data) > 0: | |
| # Ilk elemani goster | |
| print(f"{indent} [0]:") | |
| describe_structure(data[0], depth=depth + 1, max_depth=max_depth) | |
| if len(data) > 1: | |
| print(f"{indent} ... ({len(data) - 1} eleman daha)") | |
| else: | |
| valstr = str(data) | |
| if len(valstr) > 100: | |
| valstr = valstr[:100] + "..." | |
| print(f"{indent}{prefix}{type(data).__name__} = {valstr}") | |
| def inspect_all(): | |
| """Tum mark.txt dosyalarini tara, format ozetini cikar.""" | |
| mark_files = sorted(glob.glob(str(TABLETS_DIR / "*" / "mark.txt"))) | |
| print(f"\n{'='*60}") | |
| print(f"TOPLAM {len(mark_files)} mark.txt dosyasi bulundu") | |
| print(f"{'='*60}\n") | |
| if not mark_files: | |
| print("HATA: Hicbir mark.txt dosyasi bulunamadi!") | |
| return | |
| # Ilk dosyayi detayli incele | |
| first = mark_files[0] | |
| print(f"--- {first} ---") | |
| data = inspect_mark_file(first) | |
| if data is None: | |
| return | |
| print(f"\nDosya boyutu: {os.path.getsize(first) / 1024:.1f} KB") | |
| print(f"\nYAPI:") | |
| describe_structure(data) | |
| # Tum dosyalarin istatistiklerini topla | |
| print(f"\n{'='*60}") | |
| print("TUM DOSYALARIN OZETI") | |
| print(f"{'='*60}") | |
| total_annotations = 0 | |
| all_names = set() | |
| all_types = set() | |
| errors = [] | |
| for mf in mark_files: | |
| try: | |
| d = inspect_mark_file(mf) | |
| if d is None: | |
| errors.append(mf) | |
| continue | |
| annotations = extract_annotations(d) | |
| total_annotations += len(annotations) | |
| for ann in annotations: | |
| if "name" in ann: | |
| all_names.add(ann["name"]) | |
| if "text" in ann: | |
| all_names.add(ann["text"]) | |
| if "type" in ann: | |
| all_types.add(ann["type"]) | |
| except Exception as e: | |
| errors.append(f"{mf}: {e}") | |
| print(f"Toplam anotasyon sayisi: {total_annotations}") | |
| print(f"Benzersiz sinif sayisi: {len(all_names)}") | |
| print(f"Anotasyon tipleri: {all_types}") | |
| print(f"Hatali dosyalar: {len(errors)}") | |
| if errors: | |
| for e in errors[:10]: | |
| print(f" - {e}") | |
| if all_names: | |
| sorted_names = sorted(all_names) | |
| print(f"\nSiniflar (ilk 50):") | |
| for n in sorted_names[:50]: | |
| print(f" - {n}") | |
| if len(sorted_names) > 50: | |
| print(f" ... ve {len(sorted_names) - 50} sinif daha") | |
| # ============================================================ | |
| # ADIM 2: Anotasyonlari cikar (format-agnostik) | |
| # ============================================================ | |
| def extract_annotations(data): | |
| """ | |
| JSON yapisindan anotasyonlari cikar. | |
| Farkli olasi yapilari dener. | |
| Her anotasyon: {x, y, width, height, name/text, ...} | |
| """ | |
| annotations = [] | |
| if isinstance(data, list): | |
| for item in data: | |
| ann = extract_single_annotation(item) | |
| if ann: | |
| annotations.append(ann) | |
| elif isinstance(data, dict): | |
| # Ust seviye dict olabilir, icerideki listelerden cikar | |
| # Olasi anahtarlar: marks, annotations, objects, regions, shapes | |
| for key in data: | |
| val = data[key] | |
| if isinstance(val, list) and len(val) > 0: | |
| for item in val: | |
| ann = extract_single_annotation(item) | |
| if ann: | |
| annotations.append(ann) | |
| # Belki dict'in kendisi tek bir anotasyon? | |
| if not annotations: | |
| ann = extract_single_annotation(data) | |
| if ann: | |
| annotations.append(ann) | |
| return annotations | |
| def extract_single_annotation(item): | |
| """Tek bir anotasyon objesinden x, y, w, h ve sinif bilgisi cikar.""" | |
| if not isinstance(item, dict): | |
| return None | |
| ann = {} | |
| # Bounding box bilgisi | |
| # Durum 1: Dogrudan x, y, width, height | |
| if "x" in item and "y" in item: | |
| ann["x"] = float(item["x"]) | |
| ann["y"] = float(item["y"]) | |
| ann["width"] = float(item.get("width", 0)) | |
| ann["height"] = float(item.get("height", 0)) | |
| # Durum 2: "mark" icinde | |
| elif "mark" in item and isinstance(item["mark"], dict): | |
| mark = item["mark"] | |
| if "x" in mark and "y" in mark: | |
| ann["x"] = float(mark["x"]) | |
| ann["y"] = float(mark["y"]) | |
| ann["width"] = float(mark.get("width", 0)) | |
| ann["height"] = float(mark.get("height", 0)) | |
| # Durum 3: "rect" icinde | |
| elif "rect" in item and isinstance(item["rect"], dict): | |
| r = item["rect"] | |
| if "x" in r and "y" in r: | |
| ann["x"] = float(r["x"]) | |
| ann["y"] = float(r["y"]) | |
| ann["width"] = float(r.get("width", r.get("w", 0))) | |
| ann["height"] = float(r.get("height", r.get("h", 0))) | |
| if "x" not in ann: | |
| return None | |
| # Sinif/etiket bilgisi | |
| for name_key in ["name", "text", "label", "class", "category", "comment"]: | |
| if name_key in item and item[name_key]: | |
| ann["name"] = str(item[name_key]) | |
| break | |
| # "mark" icinde olabilir | |
| if "mark" in item and isinstance(item["mark"], dict): | |
| if name_key in item["mark"] and item["mark"][name_key]: | |
| ann["name"] = str(item["mark"][name_key]) | |
| break | |
| # ID | |
| if "id" in item: | |
| ann["id"] = item["id"] | |
| # Type | |
| if "type" in item: | |
| ann["type"] = item["type"] | |
| return ann | |
| def extract_image_info(data): | |
| """JSON verisinden gorsel boyut bilgisini cikar.""" | |
| info = {} | |
| if isinstance(data, dict): | |
| for key in ("naturalWidth", "imageWidth", "width"): | |
| if key in data: | |
| info["width"] = int(data[key]) | |
| break | |
| for key in ("naturalHeight", "imageHeight", "height"): | |
| if key in data: | |
| info["height"] = int(data[key]) | |
| break | |
| # Ic icte olabilir | |
| if "image" in data and isinstance(data["image"], dict): | |
| img = data["image"] | |
| for key in ("naturalWidth", "width"): | |
| if key in img: | |
| info["width"] = int(img[key]) | |
| break | |
| for key in ("naturalHeight", "height"): | |
| if key in img: | |
| info["height"] = int(img[key]) | |
| break | |
| return info | |
| # ============================================================ | |
| # ADIM 3: COCO formatina donustur | |
| # ============================================================ | |
| def convert_to_coco(val_ratio=0.2, seed=42): | |
| """Tum mark.txt dosyalarini COCO formatina donustur.""" | |
| mark_files = sorted(glob.glob(str(TABLETS_DIR / "*" / "mark.txt"))) | |
| print(f"\n{len(mark_files)} mark.txt dosyasi isleniyor...\n") | |
| # Her tablet klasoru icin gorsel dosyasini bul | |
| all_data = [] # [(tablet_id, image_path, annotations, image_info)] | |
| category_names = set() | |
| for mf in mark_files: | |
| tablet_dir = Path(mf).parent | |
| tablet_id = tablet_dir.name | |
| # Gorseli bul (jpg) | |
| images = list(tablet_dir.glob("*.jpg")) + list(tablet_dir.glob("*.png")) | |
| if not images: | |
| print(f" UYARI: {tablet_id} icin gorsel bulunamadi, atlaniyor") | |
| continue | |
| image_path = images[0] | |
| # Anotasyonlari cikar | |
| data = inspect_mark_file(mf) | |
| if data is None: | |
| continue | |
| annotations = extract_annotations(data) | |
| image_info = extract_image_info(data) | |
| if not annotations: | |
| print(f" UYARI: {tablet_id} icin anotasyon bulunamadi") | |
| continue | |
| for ann in annotations: | |
| if "name" in ann: | |
| category_names.add(ann["name"]) | |
| all_data.append((tablet_id, image_path, annotations, image_info)) | |
| if not all_data: | |
| print("HATA: Hicbir veri islenmedi!") | |
| return | |
| # Kategori mapping olustur | |
| sorted_categories = sorted(category_names) | |
| cat_to_id = {name: i for i, name in enumerate(sorted_categories)} | |
| print(f"\nToplam {len(all_data)} tablet islendi") | |
| print(f"Toplam {sum(len(d[2]) for d in all_data)} anotasyon") | |
| print(f"Toplam {len(sorted_categories)} sinif\n") | |
| # Train/val bolustur | |
| random.seed(seed) | |
| indices = list(range(len(all_data))) | |
| random.shuffle(indices) | |
| val_count = max(1, int(len(indices) * val_ratio)) | |
| val_indices = set(indices[:val_count]) | |
| train_data = [all_data[i] for i in range(len(all_data)) if i not in val_indices] | |
| val_data = [all_data[i] for i in range(len(all_data)) if i in val_indices] | |
| print(f"Train: {len(train_data)} gorsel") | |
| print(f"Val: {len(val_data)} gorsel\n") | |
| # Output klasorlerini olustur | |
| OUTPUT_DIR.mkdir(exist_ok=True) | |
| img_dir = OUTPUT_DIR / "images" | |
| img_dir.mkdir(exist_ok=True) | |
| # COCO JSON olustur | |
| for split_name, split_data in [("train", train_data), ("val", val_data)]: | |
| coco = build_coco_json(split_data, cat_to_id, sorted_categories, img_dir) | |
| out_path = OUTPUT_DIR / f"coco_hitit_{split_name}.json" | |
| with open(out_path, "w", encoding="utf-8") as f: | |
| json.dump(coco, f, ensure_ascii=False, indent=2) | |
| print(f"Kaydedildi: {out_path}") | |
| # Kategori listesini ayri kaydet | |
| cat_path = OUTPUT_DIR / "categories.json" | |
| with open(cat_path, "w", encoding="utf-8") as f: | |
| json.dump( | |
| [{"id": cat_to_id[n], "name": n} for n in sorted_categories], | |
| f, ensure_ascii=False, indent=2 | |
| ) | |
| print(f"Kaydedildi: {cat_path}") | |
| def build_coco_json(split_data, cat_to_id, sorted_categories, img_dir): | |
| """COCO formatinda JSON olustur.""" | |
| images = [] | |
| annotations = [] | |
| ann_id = 0 | |
| for img_id, (tablet_id, image_path, anns, image_info) in enumerate(split_data): | |
| # Gorsel bilgisi | |
| w = image_info.get("width", 0) | |
| h = image_info.get("height", 0) | |
| # Boyut bilinmiyorsa dosyadan okumaya calis | |
| if w == 0 or h == 0: | |
| try: | |
| from PIL import Image | |
| with Image.open(image_path) as im: | |
| w, h = im.size | |
| except ImportError: | |
| print(f" UYARI: PIL yuklu degil, {tablet_id} boyutu bilinmiyor") | |
| except Exception as e: | |
| print(f" UYARI: {tablet_id} gorsel okunamadi: {e}") | |
| # Gorseli output/images altina sembolik link yap | |
| ext = image_path.suffix | |
| new_name = f"tablet_{tablet_id}{ext}" | |
| link_path = img_dir / new_name | |
| if not link_path.exists(): | |
| try: | |
| os.symlink(image_path.resolve(), link_path) | |
| except OSError: | |
| # Symlink desteklenmiyorsa kopyala | |
| import shutil | |
| shutil.copy2(image_path, link_path) | |
| images.append({ | |
| "id": img_id, | |
| "file_name": new_name, | |
| "height": h, | |
| "width": w, | |
| }) | |
| # Anotasyonlar | |
| for ann in anns: | |
| cat_name = ann.get("name", "unknown") | |
| if cat_name not in cat_to_id: | |
| continue | |
| x, y = ann["x"], ann["y"] | |
| bw, bh = ann["width"], ann["height"] | |
| area = bw * bh | |
| annotations.append({ | |
| "id": ann_id, | |
| "image_id": img_id, | |
| "category_id": cat_to_id[cat_name], | |
| "bbox": [x, y, bw, bh], | |
| "area": area, | |
| "segmentation": [], | |
| "iscrowd": 0, | |
| }) | |
| ann_id += 1 | |
| coco = { | |
| "images": images, | |
| "annotations": annotations, | |
| "categories": [{"id": cat_to_id[n], "name": n} for n in sorted_categories], | |
| } | |
| return coco | |
| # ============================================================ | |
| # ADIM 4: eBL verileriyle birlestir | |
| # ============================================================ | |
| def merge_with_ebl(): | |
| """Hitit COCO verisini eBL COCO verisiyle birlestir.""" | |
| hitit_train = OUTPUT_DIR / "coco_hitit_train.json" | |
| if not hitit_train.exists(): | |
| print("HATA: Once --convert ile Hitit verisini donusturun!") | |
| return | |
| # eBL COCO dosyasini bul | |
| ebl_coco_paths = [ | |
| EBL_DIR / "coco-recognition" / "data" / "coco" / "annotations" / "instances_train2017.json", | |
| EBL_DIR / "coco-two-stage" / "data" / "coco" / "annotations" / "instances_val2017.json", | |
| ] | |
| for ebl_path in ebl_coco_paths: | |
| if not ebl_path.exists(): | |
| print(f" eBL dosyasi bulunamadi: {ebl_path}") | |
| continue | |
| print(f"\n eBL dosyasi: {ebl_path}") | |
| with open(ebl_path, "r") as f: | |
| ebl_coco = json.load(f) | |
| with open(hitit_train, "r") as f: | |
| hitit_coco = json.load(f) | |
| # Kategori birlesimi | |
| hitit_cats = {c["name"]: c["id"] for c in hitit_coco["categories"]} | |
| ebl_cats = {c["name"]: c["id"] for c in ebl_coco["categories"]} | |
| all_cat_names = sorted(set(hitit_cats.keys()) | set(ebl_cats.keys())) | |
| merged_cat_to_id = {name: i for i, name in enumerate(all_cat_names)} | |
| # Hitit anotasyonlarini yeni ID'lerle guncelle | |
| old_to_new_hitit = {hitit_cats[n]: merged_cat_to_id[n] for n in hitit_cats} | |
| for ann in hitit_coco["annotations"]: | |
| ann["category_id"] = old_to_new_hitit[ann["category_id"]] | |
| # eBL anotasyonlarini yeni ID'lerle guncelle ve ID offset ekle | |
| img_offset = max(im["id"] for im in hitit_coco["images"]) + 1 | |
| ann_offset = max(a["id"] for a in hitit_coco["annotations"]) + 1 if hitit_coco["annotations"] else 0 | |
| old_to_new_ebl = {ebl_cats[n]: merged_cat_to_id[n] for n in ebl_cats} | |
| for ann in ebl_coco["annotations"]: | |
| ann["id"] += ann_offset | |
| ann["image_id"] += img_offset | |
| ann["category_id"] = old_to_new_ebl[ann["category_id"]] | |
| for im in ebl_coco["images"]: | |
| im["id"] += img_offset | |
| # Birlestir | |
| merged = { | |
| "images": hitit_coco["images"] + ebl_coco["images"], | |
| "annotations": hitit_coco["annotations"] + ebl_coco["annotations"], | |
| "categories": [{"id": merged_cat_to_id[n], "name": n} for n in all_cat_names], | |
| } | |
| out_name = f"coco_merged_{ebl_path.parent.parent.parent.name}.json" | |
| out_path = OUTPUT_DIR / out_name | |
| with open(out_path, "w", encoding="utf-8") as f: | |
| json.dump(merged, f, ensure_ascii=False, indent=2) | |
| print(f" Birlesik: {len(merged['images'])} gorsel, " | |
| f"{len(merged['annotations'])} anotasyon, " | |
| f"{len(merged['categories'])} sinif") | |
| print(f" Kaydedildi: {out_path}") | |
| # ============================================================ | |
| # MAIN | |
| # ============================================================ | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Hitit tablet anotasyonlarini COCO formatina donustur") | |
| parser.add_argument("--inspect", action="store_true", help="Sadece mark.txt formatini kesfet") | |
| parser.add_argument("--convert", action="store_true", help="COCO formatina donustur") | |
| parser.add_argument("--merge-ebl", action="store_true", help="eBL verileriyle birlestir") | |
| parser.add_argument("--val-ratio", type=float, default=0.2, help="Validasyon orani (varsayilan: 0.2)") | |
| parser.add_argument("--seed", type=int, default=42, help="Random seed") | |
| args = parser.parse_args() | |
| if not any([args.inspect, args.convert, args.merge_ebl]): | |
| # Hicbir flag verilmezse inspect yap | |
| args.inspect = True | |
| if args.inspect: | |
| inspect_all() | |
| if args.convert: | |
| convert_to_coco(val_ratio=args.val_ratio, seed=args.seed) | |
| if args.merge_ebl: | |
| merge_with_ebl() | |
| if __name__ == "__main__": | |
| main() | |