| |
| """ |
| 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" |
|
|
| |
| |
| |
|
|
| 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() |
|
|
| |
| try: |
| data = json.loads(raw) |
| except json.JSONDecodeError: |
| |
| 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: |
| |
| 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 |
|
|
| |
| 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) |
|
|
| |
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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): |
| |
| |
| 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) |
|
|
| |
| 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 = {} |
|
|
| |
| |
| 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)) |
|
|
| |
| 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)) |
|
|
| |
| 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 |
|
|
| |
| 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 |
| |
| 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 |
|
|
| |
| if "id" in item: |
| ann["id"] = item["id"] |
|
|
| |
| 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 |
| |
| 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 |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
| |
| all_data = [] |
| category_names = set() |
|
|
| for mf in mark_files: |
| tablet_dir = Path(mf).parent |
| tablet_id = tablet_dir.name |
|
|
| |
| 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] |
|
|
| |
| 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 |
|
|
| |
| 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") |
|
|
| |
| 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_DIR.mkdir(exist_ok=True) |
| img_dir = OUTPUT_DIR / "images" |
| img_dir.mkdir(exist_ok=True) |
|
|
| |
| 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}") |
|
|
| |
| 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): |
| |
| w = image_info.get("width", 0) |
| h = image_info.get("height", 0) |
|
|
| |
| 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}") |
|
|
| |
| 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: |
| |
| import shutil |
| shutil.copy2(image_path, link_path) |
|
|
| images.append({ |
| "id": img_id, |
| "file_name": new_name, |
| "height": h, |
| "width": w, |
| }) |
|
|
| |
| 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 |
|
|
|
|
| |
| |
| |
|
|
| 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_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) |
|
|
| |
| 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)} |
|
|
| |
| 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"]] |
|
|
| |
| 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 |
|
|
| |
| 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}") |
|
|
|
|
| |
| |
| |
|
|
| 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]): |
| |
| 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() |
|
|