File size: 8,634 Bytes
ccf69b0
f51d821
bfd7e4a
ccf69b0
f2b1904
ccf69b0
 
f2b1904
 
 
 
 
 
f51d821
ccf69b0
bfd7e4a
 
ccf69b0
 
f2b1904
 
 
 
 
 
 
 
 
 
 
 
bfd7e4a
ccf69b0
bfd7e4a
f2b1904
bfd7e4a
ccf69b0
 
 
 
bfd7e4a
ccf69b0
 
 
 
 
 
 
 
 
bfd7e4a
ccf69b0
bfd7e4a
f2b1904
 
 
 
 
 
 
 
 
 
 
ccf69b0
 
 
 
 
 
 
 
 
f2b1904
 
 
 
 
 
 
 
 
 
 
 
ccf69b0
 
 
bfd7e4a
ccf69b0
bfd7e4a
f2b1904
bfd7e4a
 
ccf69b0
 
f2b1904
 
 
ccf69b0
 
 
 
 
f2b1904
ccf69b0
 
 
 
 
f2b1904
bfd7e4a
ccf69b0
 
bfd7e4a
ccf69b0
f2b1904
 
ccf69b0
bfd7e4a
f2b1904
bfd7e4a
 
 
 
f2b1904
bfd7e4a
 
f2b1904
bfd7e4a
 
f2b1904
 
bfd7e4a
 
f2b1904
 
 
bfd7e4a
 
f2b1904
bfd7e4a
 
f2b1904
bfd7e4a
 
f2b1904
bfd7e4a
 
 
f2b1904
bfd7e4a
 
 
 
 
 
 
f2b1904
 
bfd7e4a
 
f2b1904
bfd7e4a
 
 
 
f2b1904
bfd7e4a
 
f2b1904
ccf69b0
 
 
 
 
 
 
 
 
 
 
 
f2b1904
 
ccf69b0
f2b1904
 
ccf69b0
f2b1904
ccf69b0
f2b1904
ccf69b0
 
 
 
 
 
 
 
 
 
bfd7e4a
 
 
ccf69b0
bfd7e4a
ccf69b0
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# app.py
import gradio as gr
from ultralytics import YOLO
from PIL import Image, ImageDraw, ImageFont
from huggingface_hub import hf_hub_download
import torch
import os
import requests # Keep for potential future use, though hf_hub_download handles model download

# --- Configuration ---
MODEL_REPO_ID = "biglam/historic-newspaper-illustrations-yolov11"
# Choose 'yolo11n.pt' (nano) or 'yolo11s.pt' (small)
MODEL_FILENAME = "yolo11n.pt" # Defaulting to the smaller nano model

# --- Model Loading ---
model = None # Initialize model variable

# Use try-except block for robust model loading
try:
    # Step 1: Download the specific model weights file from Hugging Face Hub
    print(f"Downloading model weights '{MODEL_FILENAME}' from '{MODEL_REPO_ID}'...")
    model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
    print(f"Model weights downloaded to: {model_path}")

    # Step 2: Load the YOLO model using the downloaded weights file path
    model = YOLO(model_path)
    print(f"Ultralytics YOLO model loaded successfully from '{model_path}'.")

    # You can check the device it's running on (YOLO usually auto-detects)
    device = next(model.parameters()).device
    print(f"Model is running on device: {device}")

except Exception as e:
    print(f"Error loading Ultralytics YOLO model: {e}")
    # Ensure model remains None if loading fails


# --- Image Processing Function ---
def detect_illustrations(input_image: Image.Image) -> Image.Image:
    """
    Detects illustrations in the input image using the loaded Ultralytics YOLO model
    and draws bounding boxes around them.

    Args:
        input_image (PIL.Image.Image): The image uploaded by the user.

    Returns:
        PIL.Image.Image: The image with bounding boxes drawn around detected illustrations,
                         or the original image if the model failed to load or no objects are detected.
    """
    if model is None:
        print("Model not loaded. Returning original image.")
        if input_image is None:
            # Handle case where user clicks submit without uploading image
             # Create a placeholder image or return None based on Gradio handling
             placeholder = Image.new('RGB', (300, 100), color = 'white')
             d = ImageDraw.Draw(placeholder)
             try:
                 font = ImageFont.truetype("arial.ttf", 15)
             except IOError:
                 font = ImageFont.load_default()
             d.text((10,10), "Error: Model not loaded & No image provided.", fill="red", font=font)
             return placeholder
        # If image exists but model failed, add error text to image
        draw = ImageDraw.Draw(input_image)
        try:
            font = ImageFont.truetype("arial.ttf", 20) # Adjust font/size as needed
        except IOError:
            font = ImageFont.load_default()
        draw.text((10, 10), "Error: Model could not be loaded.", fill="red", font=font)
        return input_image

    if input_image is None:
         # Handle case where user clicks submit without uploading image after model is loaded
         placeholder = Image.new('RGB', (300, 100), color = 'white')
         d = ImageDraw.Draw(placeholder)
         try:
             font = ImageFont.truetype("arial.ttf", 15)
         except IOError:
             font = ImageFont.load_default()
         d.text((10,10), "Please upload an image.", fill="orange", font=font)
         return placeholder


    # Convert image to RGB if it's not already
    if input_image.mode != "RGB":
        input_image = input_image.convert("RGB")

    # Perform object detection using the Ultralytics model
    try:
        # results is a list of Results objects
        # Set confidence threshold if desired, e.g., model(input_image, conf=0.5)
        results = model(input_image, verbose=False) # Set verbose=True for more detailed logs
        print(f"Detection results obtained.") # Log results for debugging
    except Exception as e:
        print(f"Error during inference: {e}")
        # Handle inference errors
        output_image = input_image.copy()
        draw = ImageDraw.Draw(output_image)
        try:
            font = ImageFont.truetype("arial.ttf", 20)
        except IOError:
            font = ImageFont.load_default()
        draw.text((10, 10), f"Error during detection: {e}", fill="red", font=font)
        return output_image

    # --- Draw Bounding Boxes ---
    output_image = input_image.copy()
    draw = ImageDraw.Draw(output_image)

    # Define colors
    label_colors = {"illustration": "red"}
    default_color = "blue"

    # Load a font for labels
    try:
        # Using a slightly larger font
        font = ImageFont.truetype("arial.ttf", 18)
    except IOError:
        print("Arial font not found. Using Pillow's default font.")
        font = ImageFont.load_default()

    # Process results (Ultralytics returns results per image, here we have one)
    if results and results[0].boxes:
        boxes = results[0].boxes # Access the Boxes object
        print(f"Found {len(boxes)} potential objects.")
        for box in boxes:
            # Extract coordinates (xyxy format)
            x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) # Get coordinates as integers

            # Get confidence score and class ID
            conf = box.conf[0].item() # Confidence score
            cls = int(box.cls[0].item()) # Class ID

            # Get the label name from the model's names dictionary
            class_name = model.names[cls] if cls in model.names else f"Class_{cls}"
            print(f"Detected '{class_name}' (ID: {cls}) with confidence {conf:.2f} at [{x1}, {y1}, {x2}, {y2}]")


            # Choose color based on label
            color = label_colors.get(class_name, default_color)

            # Draw the bounding box
            draw.rectangle([(x1, y1), (x2, y2)], outline=color, width=3)

            # Prepare label text
            label_text = f"{class_name}: {conf:.2f}"

            # Calculate text size and position using textbbox
            try:
                 text_bbox = draw.textbbox((0, 0), label_text, font=font) # Use (0,0) for size calc
                 text_width = text_bbox[2] - text_bbox[0]
                 text_height = text_bbox[3] - text_bbox[1]
            except AttributeError: # Fallback for older Pillow versions
                 text_width, text_height = font.getsize(label_text)


            # Draw background rectangle for text for better visibility
            text_bg_y = y1 - text_height - 4 # Position above box
            if text_bg_y < 0: text_bg_y = y1 + 2 # Adjust if too close to top edge

            draw.rectangle(
                [(x1, text_bg_y), (x1 + text_width + 4, text_bg_y + text_height + 2)],
                fill=color
            )

            # Draw the label text
            draw.text((x1 + 2, text_bg_y + 1), label_text, fill="white", font=font)

    else:
         print("No objects detected in the results.")
         # Optionally add text indicating no detections
         draw.text((10, 10), "No illustrations detected.", fill="orange", font=font)


    return output_image

# --- Gradio Interface ---
# Define the input and output components
image_input = gr.Image(type="pil", label="Upload Newspaper Image")
image_output = gr.Image(type="pil", label="Detected Illustrations")

# Define title and description for the Gradio app
title = f"Historic Newspaper Illustration Detector ({MODEL_FILENAME})"
description = f"""
Upload an image of a historic newspaper page.
This app uses the `{MODEL_REPO_ID}` model ('{MODEL_FILENAME}' weights via Ultralytics YOLO)
to detect illustrations and draw bounding boxes around them.
Processing might take a moment depending on the image size and server load.
Model loading happens once when the Space starts.
"""
article = f"<p style='text-align: center'><a href='https://huggingface.co/{MODEL_REPO_ID}' target='_blank'>Model Card</a> | Powered by <a href='https://github.com/ultralytics/ultralytics' target='_blank'>Ultralytics YOLO</a></p>"


# Create the Gradio interface
iface = gr.Interface(
    fn=detect_illustrations,
    inputs=image_input,
    outputs=image_output,
    title=title,
    description=description,
    article=article,
    examples=[
        # Add relative paths if you include example images in your Space repo
        # e.g., ["example1.jpg"]
    ],
    allow_flagging='never'
)

# --- Launch the App ---
if __name__ == "__main__":
    # Launch the app. share=True creates a public link (useful for testing locally)
    # In Hugging Face Spaces, share=True is not needed.
    iface.launch()