wjbmattingly's picture
fixed width argument
4384411
import gradio as gr
import torch
from transformers import TrOCRProcessor, VisionEncoderDecoderModel
import subprocess
import json
import spaces
from PIL import Image, ImageDraw
import os
import tempfile
import numpy as np
import requests
# Dictionary of model names and their corresponding HuggingFace model IDs
MODEL_OPTIONS = {
"Microsoft Handwritten": "microsoft/trocr-base-handwritten",
"Medieval Base": "medieval-data/trocr-medieval-base",
"Medieval Latin Caroline": "medieval-data/trocr-medieval-latin-caroline",
"Medieval Castilian Hybrida": "medieval-data/trocr-medieval-castilian-hybrida",
"Medieval Humanistica": "medieval-data/trocr-medieval-humanistica",
"Medieval Textualis": "medieval-data/trocr-medieval-textualis",
"Medieval Cursiva": "medieval-data/trocr-medieval-cursiva",
"Medieval Semitextualis": "medieval-data/trocr-medieval-semitextualis",
"Medieval Praegothica": "medieval-data/trocr-medieval-praegothica",
"Medieval Semihybrida": "medieval-data/trocr-medieval-semihybrida",
"Medieval Print": "medieval-data/trocr-medieval-print"
}
def load_model(model_name):
model_id = MODEL_OPTIONS[model_name]
processor = TrOCRProcessor.from_pretrained(model_id)
model = VisionEncoderDecoderModel.from_pretrained(model_id)
# Move model to GPU if available, else use CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
return processor, model
def detect_lines(image_path):
# API endpoint
url = "https://wjbmattingly-kraken-api.hf.space/detect_lines"
# Run Kraken for line detection
lines_json_path = "lines.json"
# Prepare the file for upload
files = {'file': ('ms.jpg', open(image_path, 'rb'), 'image/jpeg')}
# Specify the model to use
data = {'model_name': 'catmus-medieval.mlmodel'}
# Send the POST request
response = requests.post(url, files=files, data=data)
result = response.json()["result"]["lines"]
return result
def extract_line_images(image, lines):
line_images = []
for line in lines:
polygon = line['boundary']
# Calculate bounding box
x_coords, y_coords = zip(*polygon)
x1, y1, x2, y2 = int(min(x_coords)), int(min(y_coords)), int(max(x_coords)), int(max(y_coords))
# Crop the line from the original image
line_image = image.crop((x1, y1, x2, y2))
# Create a mask for the polygon
mask = Image.new('L', (x2-x1, y2-y1), 0)
adjusted_polygon = [(int(x-x1), int(y-y1)) for x, y in polygon]
ImageDraw.Draw(mask).polygon(adjusted_polygon, outline=255, fill=255)
# Convert images to numpy arrays
line_array = np.array(line_image)
mask_array = np.array(mask)
# Apply the mask
masked_line = np.where(mask_array[:,:,np.newaxis] == 255, line_array, 255)
# Convert back to PIL Image
masked_line_image = Image.fromarray(masked_line.astype('uint8'), 'RGB')
line_images.append(masked_line_image)
return line_images
def visualize_lines(image, lines):
output_image = image.copy()
draw = ImageDraw.Draw(output_image)
for line in lines:
polygon = [(int(x), int(y)) for x, y in line['boundary']]
draw.polygon(polygon, outline="red")
return output_image
@spaces.GPU
def transcribe_lines(line_images, model_name):
processor, model = load_model(model_name)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
transcriptions = []
for line_image in line_images:
# Process the line image
pixel_values = processor(images=line_image, return_tensors="pt").pixel_values
pixel_values = pixel_values.to(device)
# Generate (no beam search)
generated_ids = model.generate(pixel_values)
# Decode
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
transcriptions.append(generated_text)
return transcriptions
def process_document(image, model_name):
# Save the uploaded image temporarily
with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_file:
image.save(temp_file, format="JPEG")
temp_file_path = temp_file.name
# Step 1: Detect lines
lines = detect_lines(temp_file_path)
# Visualize detected lines
output_image = visualize_lines(image, lines)
# Step 2: Extract line images
line_images = extract_line_images(image, lines)
# Step 3: Transcribe lines
transcriptions = transcribe_lines(line_images, model_name)
# Clean up temporary file
os.unlink(temp_file_path)
return output_image, "\n".join(transcriptions)
# Gradio interface
def gradio_process_document(image, model_name):
output_image, transcriptions = process_document(image, model_name)
return output_image, transcriptions
with gr.Blocks() as iface:
gr.Markdown("# Medieval Manuscript Line Detection and HTR App")
gr.Markdown("Upload an image and select a model to detect lines and transcribe the text.")
with gr.Row():
input_image = gr.Image(type="pil", label="Upload Image", height=300, width=300)
with gr.Column():
model_dropdown = gr.Dropdown(choices=list(MODEL_OPTIONS.keys()), value="Medieval Base", label="Select Model")
submit_button = gr.Button("Process")
with gr.Row():
output_image = gr.Image(type="pil", label="Detected Lines", scale=1, width=500)
output_text = gr.Textbox(label="Transcription")
submit_button.click(
fn=gradio_process_document,
inputs=[input_image, model_dropdown],
outputs=[output_image, output_text]
)
iface.launch(debug=True)