Instructions to use kesha-humonen/runic-reader-2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use kesha-humonen/runic-reader-2 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Download code/gen_diffusion.py from kesha-humonen/runic-reader-2: direct link, hf CLI and curl.
- Browser
- Download file 5.81 kB
-
https://huggingface.co/kesha-humonen/runic-reader-2/resolve/main/code/gen_diffusion.py
- Command line
-
hf download hf://kesha-humonen/runic-reader-2/code/gen_diffusion.py
-
curl -L -o gen_diffusion.py https://huggingface.co/kesha-humonen/runic-reader-2/resolve/main/code/gen_diffusion.py
5.81 kB
| """Diffusion-based synthetic photographs of runic inscriptions (latent diffusion + ControlNet-Canny). | |
| Follows the recipe of the recognition thesis: the transliteration is mapped to runes, rendered | |
| black on white on a 1024x1024 canvas with automatic font size and line wrapping, converted to a | |
| Canny edge map (thresholds 50/150, one-pixel dilation) and used as the ControlNet condition with | |
| a fixed positive/negative prompt pair, 28 steps and guidance 7.0. Adapted for SDXL: a 1216x832 canvas instead of | |
| 1024x1024 and conditioning scale 0.8 instead of 0.65, with extra negative terms, because SDXL fills empty | |
| stone with invented signs at the thesis settings. | |
| SDXL-base with the SDXL Canny ControlNet replaces SD3-Medium, whose weights are gated. | |
| Resumable per shard; labels are appended after every image. | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import random | |
| import sys | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from synth import FONT_DIR, RUNE, lexicon, sample_phrase # noqa: E402 | |
| POSITIVE = ("deeply carved runes on a dark grey granite runestone, weathered eroded stone surface, " | |
| "plain stone around the inscription, even diffuse daylight, archaeological macro photograph, sharp focus") | |
| NEGATIVE = ("polished, smooth, printed font, fantasy, illustration, painting, cartoon, glowing, " | |
| "text overlay, watermark, signature, blurry, extra runes, additional inscriptions, symbols, ornaments, " | |
| "patterns, tiles, bricks, frame") | |
| def render_condition(translit, rng, width=1216, height=832): | |
| runes = " ".join("".join(RUNE.get(c, "") for c in w) for w in translit.split()) | |
| runes = runes.replace(" ", " ᛫ ") # runic single punctuation as word divider | |
| font_path = os.path.join(FONT_DIR, "NotoSansRunic-Regular.ttf") | |
| size = width | |
| canvas = Image.new("L", (width, height), 255) | |
| draw = ImageDraw.Draw(canvas) | |
| fsize = 200 | |
| while fsize > 24: | |
| font = ImageFont.truetype(font_path, fsize) | |
| words, lines, cur = runes.split(" "), [], "" | |
| for w in words: | |
| trial = (cur + " " + w).strip() | |
| if draw.textlength(trial, font=font) <= size * 0.86: | |
| cur = trial | |
| else: | |
| lines.append(cur) | |
| cur = w | |
| lines.append(cur) | |
| line_h = int(fsize * 1.35) | |
| if len(lines) * line_h <= height * 0.8 and all(draw.textlength(l, font=font) <= size * 0.86 for l in lines): | |
| break | |
| fsize -= 8 | |
| y0 = (height - len(lines) * line_h) // 2 | |
| for i, l in enumerate(lines): | |
| w = draw.textlength(l, font=font) | |
| x = (size - w) / 2 + rng.randint(-20, 20) | |
| draw.text((x, y0 + i * line_h), l, fill=0, font=font) | |
| edges = cv2.Canny(np.array(canvas), 50, 150) | |
| edges = cv2.dilate(edges, np.ones((3, 3), np.uint8), iterations=1) | |
| return Image.fromarray(edges).convert("RGB") | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--corpus", required=True) | |
| ap.add_argument("--out_dir", required=True) | |
| ap.add_argument("--shard", type=int, default=0) | |
| ap.add_argument("--n", type=int, default=1000) | |
| ap.add_argument("--scale", type=float, default=0.8) | |
| args = ap.parse_args() | |
| import torch | |
| from diffusers import AutoencoderKL, ControlNetModel, StableDiffusionXLControlNetPipeline | |
| hub = "/home/jovyan/.cache/huggingface/hub" # job pods have a different $HOME | |
| snap = lambda repo: sorted(os.listdir(f"{hub}/models--{repo.replace('/', '--')}/snapshots"))[0] # noqa: E731 | |
| base = f"{hub}/models--stabilityai--stable-diffusion-xl-base-1.0/snapshots/{snap('stabilityai/stable-diffusion-xl-base-1.0')}" | |
| cn = f"{hub}/models--diffusers--controlnet-canny-sdxl-1.0/snapshots/{snap('diffusers/controlnet-canny-sdxl-1.0')}" | |
| vae = f"{hub}/models--madebyollin--sdxl-vae-fp16-fix/snapshots/{snap('madebyollin/sdxl-vae-fp16-fix')}" | |
| controlnet = ControlNetModel.from_pretrained(cn, torch_dtype=torch.float16, variant="fp16") | |
| pipe = StableDiffusionXLControlNetPipeline.from_pretrained( | |
| base, controlnet=controlnet, vae=AutoencoderKL.from_pretrained(vae, torch_dtype=torch.float16), | |
| torch_dtype=torch.float16, variant="fp16").to("cuda") | |
| pipe.set_progress_bar_config(disable=True) | |
| rng = random.Random(1000 + args.shard) | |
| lex = lexicon(args.corpus) | |
| words = sorted(lex) | |
| weights = [lex[w] ** 0.7 for w in words] | |
| os.makedirs(os.path.join(args.out_dir, "img"), exist_ok=True) | |
| label_path = os.path.join(args.out_dir, f"labels_{args.shard}.jsonl") | |
| done = set() | |
| if os.path.exists(label_path): | |
| done = {json.loads(l)["id"] for l in open(label_path)} | |
| phrases = [sample_phrase(rng, words, weights) for _ in range(args.n)] | |
| with open(label_path, "a") as f: | |
| for i, t in enumerate(phrases): | |
| uid = f"dif{args.shard}_{i:05d}" # v2 parameters (1216x832 canvas, scale 0.8) | |
| if uid in done: | |
| continue | |
| cond = render_condition(t, rng) | |
| g = torch.Generator("cuda").manual_seed(args.shard * 100000 + i) | |
| img = pipe(POSITIVE, negative_prompt=NEGATIVE, image=cond, width=cond.width, height=cond.height, num_inference_steps=28, guidance_scale=7.0, | |
| controlnet_conditioning_scale=args.scale, generator=g).images[0] | |
| img.thumbnail((768, 768)) | |
| name = f"img/{uid}.jpg" | |
| img.save(os.path.join(args.out_dir, name), quality=90) | |
| f.write(json.dumps({"id": uid, "image": name, "target": t}, ensure_ascii=False) + "\n") | |
| f.flush() | |
| if i % 50 == 0: | |
| print("generated", i, flush=True) | |
| print("DONE", args.shard, flush=True) | |
| if __name__ == "__main__": | |
| main() | |