Bina-0.2-RizehPizeh / training-kit /prepare_ppocrv6_pilot.py
Reza2kn's picture
Add training-kit/prepare_ppocrv6_pilot.py
054400d verified
Raw
History Blame Contribute Delete
4.89 kB
#!/usr/bin/env python3
"""Prepare a small Persian PP-OCRv6 recognition pilot on a remote GPU."""
from __future__ import annotations
import argparse
import io
import json
import unicodedata
from collections import Counter
from pathlib import Path
import pyarrow.parquet as pq
from huggingface_hub import HfApi, hf_hub_download
from PIL import Image
def clean_text(value: str) -> str:
value = unicodedata.normalize("NFC", value).replace("\t", " ")
return " ".join(value.split())
def image_bytes(value: object) -> bytes:
if isinstance(value, dict) and value.get("bytes") is not None:
return value["bytes"]
raise ValueError("Expected an embedded Hugging Face image payload")
def read_gt(path: Path) -> list[tuple[Path, str]]:
rows = []
for raw in path.read_text(encoding="utf-8").splitlines():
key, text = raw.split(maxsplit=1)
rows.append((path.parent / f"{key}.png", clean_text(text)))
return rows
def first_parquet(repo: str) -> str:
files = HfApi().list_repo_files(repo, repo_type="dataset")
candidates = sorted(x for x in files if x.endswith(".parquet") and x.startswith("data/"))
if not candidates:
raise RuntimeError(f"No data parquet found in {repo}")
return candidates[0]
def extract_printed(repo: str, output: Path, train_count: int, eval_count: int) -> tuple[list, list]:
source = hf_hub_download(
repo, first_parquet(repo), repo_type="dataset", local_dir=output / "hf"
)
table = pq.read_table(source, columns=["image", "label"]).slice(0, train_count + eval_count)
rows = table.to_pylist()
if len(rows) < train_count + eval_count:
raise RuntimeError("Printed pilot shard does not contain enough rows")
destination = output / "images" / "printed"
destination.mkdir(parents=True, exist_ok=True)
converted = []
for index, row in enumerate(rows):
path = destination / f"{index:07d}.jpg"
with Image.open(io.BytesIO(image_bytes(row["image"]))) as image:
image.convert("RGB").save(path, "JPEG", quality=95)
converted.append((path, clean_text(row["label"])))
return converted[:train_count], converted[train_count:]
def load_hardword_text(repo: str, output: Path) -> list[str]:
source = hf_hub_download(
repo, first_parquet(repo), repo_type="dataset", local_dir=output / "hf"
)
return [clean_text(x) for x in pq.read_table(source, columns=["sentence"])["sentence"].to_pylist()]
def write_list(path: Path, rows: list[tuple[Path, str]], root: Path) -> None:
with path.open("w", encoding="utf-8") as handle:
for image, text in rows:
handle.write(f"{image.relative_to(root)}\t{text}\n")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--phtd", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--printed-train", type=int, default=4000)
parser.add_argument("--printed-eval", type=int, default=500)
args = parser.parse_args()
args.output.mkdir(parents=True, exist_ok=True)
handwriting_train = read_gt(args.phtd / "train" / "gt.txt")
handwriting_eval = read_gt(args.phtd / "val" / "gt.txt")
printed_train, printed_eval = extract_printed(
"Reza2kn/persian-printed-ocr-3.5m",
args.output,
args.printed_train,
args.printed_eval,
)
hardwords = load_hardword_text("Reza2kn/visualears-hardword-sentences", args.output)
train = handwriting_train + printed_train
evaluation = handwriting_eval + printed_eval
data_root = args.output.parent
write_list(args.output / "train_list.txt", train, data_root)
write_list(args.output / "val_list.txt", evaluation, data_root)
frequencies = Counter("".join(text for _, text in train + evaluation) + "".join(hardwords))
characters = sorted(
char for char in frequencies
if char != " " and char.isprintable() and char not in "\r\n\t"
)
(args.output / "persian_dict.txt").write_text(
"".join(f"{char}\n" for char in characters), encoding="utf-8"
)
summary = {
"train_rows": len(train),
"eval_rows": len(evaluation),
"handwriting_train": len(handwriting_train),
"handwriting_eval": len(handwriting_eval),
"printed_train": len(printed_train),
"printed_eval": len(printed_eval),
"hardword_sentences_for_charset": len(hardwords),
"characters": len(characters),
"max_train_characters": max(map(lambda row: len(row[1]), train)),
"max_eval_characters": max(map(lambda row: len(row[1]), evaluation)),
}
(args.output / "summary.json").write_text(
json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
print(json.dumps(summary, ensure_ascii=False))
if __name__ == "__main__":
main()