# -*- coding: utf-8 -*- """يولّد 3 أشكال احترافية للورقة بـ PIL (تسميات إنجليزية لتفادي تشكيل العربية في الصور).""" from PIL import Image, ImageDraw, ImageFont import os OUT = 'figures' os.makedirs(OUT, exist_ok=True) def font(sz, bold=False): p = 'C:/Windows/Fonts/' + ('arialbd.ttf' if bold else 'arial.ttf') try: return ImageFont.truetype(p, sz) except Exception: return ImageFont.load_default() INK = (28, 33, 51); GOLD = (196, 150, 40); BLUE = (30, 64, 175) EM = (0, 150, 80); GREY = (110, 120, 140); LIGHT = (238, 240, 246) # ═══ Figure 1: Architecture ═══ def fig_arch(): W, H = 1600, 620 im = Image.new('RGB', (W, H), 'white'); d = ImageDraw.Draw(im) d.text((W//2-260, 18), 'Figure 1 | Hybrid Pipeline Architecture', font=font(30, True), fill=INK) stages = ['Preprocess\n(grayscale L)', 'Layout\nRouter', 'Segmentation\n(v4 / logic)', 'HTR Recognition\n(CTC)', 'Gated LM\n(shallow fusion)'] x0, y0, bw, bh, gap = 40, 130, 260, 110, 40 cx = [] for i, s in enumerate(stages): x = x0 + i*(bw+gap) d.rounded_rectangle([x, y0, x+bw, y0+bh], 14, fill=LIGHT, outline=INK, width=3) for j, ln in enumerate(s.split('\n')): fw = d.textlength(ln, font=font(24, j == 0)) d.text((x+bw/2-fw/2, y0+26+j*30), ln, font=font(24, j == 0), fill=INK) cx.append(x+bw) if i < len(stages)-1: ay = y0+bh//2 d.line([x+bw, ay, x+bw+gap, ay], fill=GOLD, width=5) d.polygon([(x+bw+gap, ay), (x+bw+gap-14, ay-8), (x+bw+gap-14, ay+8)], fill=GOLD) # human-in-the-loop branch under router rx = x0 + 1*(bw+gap) d.rounded_rectangle([rx, y0+bh+55, rx+bw, y0+bh+135], 12, fill=(255, 247, 225), outline=GOLD, width=3) d.text((rx+18, y0+bh+72), 'Human-in-the-loop', font=font(22, True), fill=GOLD) d.text((rx+18, y0+bh+100), 'Manual / PAGE-XML crop', font=font(20), fill=INK) d.line([rx+bw//2, y0+bh, rx+bw//2, y0+bh+55], fill=GOLD, width=4) # post-OCR assistance layer (wide box) py = y0+bh+185 d.rounded_rectangle([40, py, W-40, py+150], 16, fill=(232, 245, 238), outline=EM, width=3) d.text((60, py+16), 'Transparent Post-OCR Assistance Layer (original text preserved)', font=font(26, True), fill=EM) items = ['Candidate readings (visual + LLM)', 'Retrieval-augmented correction (Qur\'an / hadith)', 'Term / page explanation', 'Uncertainty flagging', 'Relevance-ranked catalog search'] for i, it in enumerate(items): col = i % 3; row = i // 3 d.ellipse([70+col*510, py+66+row*38, 84+col*510, py+80+row*38], fill=EM) d.text((96+col*510, py+62+row*38), it, font=font(21), fill=INK) d.line([x0+3.5*(bw+gap)+bw//2, y0+bh, x0+3.5*(bw+gap)+bw//2, py], fill=EM, width=4) im.save(f'{OUT}/figure1_architecture.png') print('figure1 OK') def bar_chart(fname, title, labels, values, colors, ylabel, ymax=None, fmt='{:.2f}'): W, H = 1500, 760 im = Image.new('RGB', (W, H), 'white'); d = ImageDraw.Draw(im) d.text((60, 22), title, font=font(30, True), fill=INK) ox, oy, pw, ph = 150, 120, W-220, H-260 d.line([ox, oy, ox, oy+ph], fill=INK, width=3) d.line([ox, oy+ph, ox+pw, oy+ph], fill=INK, width=3) ymax = ymax or (max(values)*1.18) for g in range(0, 6): gy = oy+ph - ph*g/5 d.line([ox, gy, ox+pw, gy], fill=(228, 230, 238), width=1) d.text((ox-96, gy-12), f'{ymax*g/5:.0f}', font=font(20), fill=GREY) d.text((36, oy+ph//2), ylabel, font=font(22, True), fill=INK) n = len(values); slot = pw/n; bw = slot*0.56 for i, (lb, v, c) in enumerate(zip(labels, values, colors)): bx = ox + slot*i + (slot-bw)/2 bh = ph * (v/ymax) d.rounded_rectangle([bx, oy+ph-bh, bx+bw, oy+ph], 8, fill=c) vt = fmt.format(v) fw = d.textlength(vt, font=font(24, True)) d.text((bx+bw/2-fw/2, oy+ph-bh-34), vt, font=font(24, True), fill=c) for j, ln in enumerate(lb.split('\n')): fw = d.textlength(ln, font=font(20)) d.text((bx+bw/2-fw/2, oy+ph+12+j*26), ln, font=font(20), fill=INK) im.save(f'{OUT}/{fname}.png'); print(fname, 'OK') # ═══ Figure 2: CER ═══ bar_chart('figure2_cer', 'Figure 2 | CER across models (lower is better)', ['muharaf\n(baseline)', 'exp4A\n(RASAM)', 'exp6\n(general)', 'exp6+LM\n(RASAM)', 'exp6+LM\n(TariMa)'], [37.91, 7.64, 7.48, 6.76, 8.68], [GREY, BLUE, BLUE, EM, GOLD], 'CER %', ymax=42) # ═══ Figure 3: Self-training perplexity ═══ bar_chart('figure3_selftrain', 'Figure 3 | Logic-domain LM perplexity (self-training, lower is better)', ['General\n(char8)', 'Self-trained\n(logic 418)', 'Mixed\n(gen+self)'], [53.14, 40.70, 20.25], [GREY, BLUE, EM], 'Perplexity', ymax=60) print('ALL FIGURES DONE')