Doosik's picture
Upload folder using huggingface_hub
2c7a090 verified
Raw
History Blame Contribute Delete
3.58 kB
"""Recognize the Hangul syllable and font of a single glyph image using the
trained `FontRecognitionModel` checkpoint (model/hfc.pt).
Usage:
python example/demo.py
python example/demo.py --image path/to/glyph.png --topk 10
"""
from __future__ import annotations
import argparse
import csv
import sys
from pathlib import Path
import torch
from PIL import Image
from model import CHAR_SIZE, FontRecognitionModel, decode_open, decode_restricted
EXAMPLE_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = EXAMPLE_DIR.parent
DEFAULT_CHECKPOINT = PROJECT_ROOT / "model" / "hfc.pt"
DEFAULT_INDEX = PROJECT_ROOT / "model" / "index.csv"
DEFAULT_IMAGE = EXAMPLE_DIR / "0408-0000.png"
def load_font_index(index_csv: Path) -> dict[int, str]:
"""Load the font-id -> font-name mapping produced from model/index.json."""
with index_csv.open(encoding="utf-8-sig", newline="") as f:
return {int(row["id"]): row["font_name"] for row in csv.DictReader(f)}
def load_model(checkpoint_path: Path, num_font_classes: int, device: torch.device) -> FontRecognitionModel:
checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False)
model = FontRecognitionModel(num_font_classes)
model.load_state_dict(checkpoint["model"])
model.to(device)
model.eval()
return model
def load_glyph_tensor(image_path: Path, device: torch.device) -> torch.Tensor:
"""Load a glyph image as a (1, 1, CHAR_SIZE, CHAR_SIZE) float tensor in
[0, 1], matching the training data's black-glyph-on-white-background,
unnormalized convention."""
image = Image.open(image_path).convert("L")
if image.size != (CHAR_SIZE, CHAR_SIZE):
image = image.resize((CHAR_SIZE, CHAR_SIZE), Image.BILINEAR)
tensor = torch.frombuffer(bytearray(image.tobytes()), dtype=torch.uint8)
tensor = tensor.reshape(1, 1, CHAR_SIZE, CHAR_SIZE).to(torch.float32) / 255.0
return tensor.to(device)
def main() -> None:
sys.stdout.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--checkpoint", type=Path, default=DEFAULT_CHECKPOINT)
parser.add_argument("--index", type=Path, default=DEFAULT_INDEX)
parser.add_argument("--image", type=Path, default=DEFAULT_IMAGE)
parser.add_argument("--topk", type=int, default=5)
args = parser.parse_args()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
font_index = load_font_index(args.index)
model = load_model(args.checkpoint, len(font_index), device)
x = load_glyph_tensor(args.image, device)
with torch.no_grad():
output = model.encode(x)
restricted_char = decode_restricted(output.cho_logits, output.jung_logits, output.jong_logits)[0]
open_char = decode_open(output.cho_logits, output.jung_logits, output.jong_logits)[0]
font_probs = torch.softmax(output.font_logits, dim=-1)[0]
topk_probs, topk_ids = font_probs.topk(min(args.topk, font_probs.numel()))
print(f"Image: {args.image}")
print()
print("=== Hangul character recognition ===")
print(f"Restricted decode (KS X 1001, 2,350 chars): {restricted_char} (U+{ord(restricted_char):04X})")
print(f"Open decode (all 11,172 syllables): {open_char} (U+{ord(open_char):04X})")
print()
print("=== Font recognition ===")
for rank, (font_id, prob) in enumerate(zip(topk_ids.tolist(), topk_probs.tolist()), start=1):
print(f"{rank}. id={font_id:<5} prob={prob * 100:6.2f}% font_name={font_index[font_id]}")
if __name__ == "__main__":
main()