medical-schedule-ocr / create_test_pdf.py
JoselinMaria's picture
Add test PDF generator, validator, and recommendations
d8a8666 verified
Raw
History Blame Contribute Delete
3.89 kB
"""
create_test_pdf.py β€” Generate a synthetic scanned medical-schedule PDF
with known ground-truth for accuracy measurement.
"""
import json, os, random
import numpy as np
from PIL import Image, ImageDraw, ImageFont
GROUND_TRUTH = [
("1", "07:30", "09:15", "105", "SMITH, J", "JONES, R", "DOE, JOHN A", "45/M", "03/15/1979"),
("2", "08:00", "10:30", "150", "WILLIAMS, K", "BROWN, S", "MARTINEZ, MARIA", "67/F", "11/22/1957"),
("3", "09:00", "11:00", "120", "JOHNSON, M", "DAVIS, T", "CHEN, DAVID L", "32/M", "06/08/1992"),
("1", "10:00", "12:30", "150", "TAYLOR, P", "WILSON, A", "O'BRIEN, SARAH", "55/F", "09/04/1969"),
("4", "11:30", "13:00", "90", "ANDERSON, R", "MOORE, J", "KIM, JAMES H", "28/M", "12/01/1996"),
("2", "12:00", "14:45", "165", "THOMAS, D", "CLARK, M", "PATEL, PRIYA", "41/F", "07/19/1983"),
("3", "13:30", "15:00", "90", "JACKSON, L", "HALL, N", "RODRIGUEZ, CARLOS", "73/M", "02/28/1951"),
("5", "14:00", "16:30", "150", "WHITE, B", "LEWIS, E", "NGUYEN, THI L", "59/F", "04/10/1965"),
]
COL_SPEC = [ # (header, x_start, x_end)
("ROOM", 50, 180),
("BEGIN", 180, 340),
("END", 340, 490),
("MIN", 490, 580),
("SURGEON", 580, 900),
("ANES", 900, 1180),
("PATIENT'S NAME",1180,1750),
("AGE/SEX", 1750,1900),
("DOB", 1900,2150),
]
def _font(size, bold=False):
name = "DejaVuSansMono-Bold.ttf" if bold else "DejaVuSansMono.ttf"
for d in ["/usr/share/fonts/truetype/dejavu", "/usr/share/fonts/dejavu",
"C:/Windows/Fonts"]:
p = os.path.join(d, name)
if os.path.isfile(p):
return ImageFont.truetype(p, size)
return ImageFont.load_default()
def make_page(rows, W=2550, H=3300):
img = Image.new("RGB", (W, H), "white")
dr = ImageDraw.Draw(img)
fh = _font(26, bold=True)
fd = _font(20)
dr.text((900, 50), "OPERATING ROOM SCHEDULE", fill="black", font=fh)
dr.text((1000, 90), "DATE: 01/15/2025", fill="black", font=fd)
y0, rh = 160, 55
# header
for hdr, xs, xe in COL_SPEC:
dr.rectangle([(xs, y0), (xe, y0 + rh)], outline="black", width=2)
dr.text((xs + 8, y0 + 12), hdr, fill="black", font=fd)
# data
for i, vals in enumerate(rows):
y = y0 + rh + i * rh
for j, (_, xs, xe) in enumerate(COL_SPEC):
dr.rectangle([(xs, y), (xe, y + rh)], outline="black", width=2)
if j < len(vals):
dr.text((xs + 8, y + 14), vals[j], fill="black", font=fd)
return img
def add_noise(img, level=8):
a = np.array(img, dtype=np.float32)
a += np.random.normal(0, level, a.shape)
a = np.clip(a, 0, 255).astype(np.uint8)
pil = Image.fromarray(a)
return pil.rotate(random.uniform(-0.5, 0.5), fillcolor=(245,245,245))
def create_test_pdf(out_dir="./test_pdfs", pages=2):
os.makedirs(out_dir, exist_ok=True)
pils, gt = [], []
for pg in range(pages):
pil = add_noise(make_page(GROUND_TRUTH))
pils.append(pil)
for r in GROUND_TRUTH:
gt.append(dict(page=pg+1,
room=r[0], begin_time=r[1], end_time=r[2],
minutes=r[3], surgeon=r[4], anes=r[5],
patient_name=r[6], age_sex=r[7], dob=r[8]))
pdf = os.path.join(out_dir, "test_schedule.pdf")
pils[0].save(pdf, "PDF", resolution=300, save_all=True, append_images=pils[1:])
gt_path = os.path.join(out_dir, "ground_truth.json")
with open(gt_path, "w") as f:
json.dump(gt, f, indent=2)
print(f"βœ… PDF β†’ {pdf} ({pages} pages Γ— {len(GROUND_TRUTH)} rows)")
print(f"βœ… GT β†’ {gt_path}")
return pdf, gt_path
if __name__ == "__main__":
create_test_pdf()