File size: 4,092 Bytes
d0687b8
 
 
 
 
 
 
 
 
 
 
 
361ce22
 
 
d0687b8
361ce22
 
d0687b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361ce22
 
 
 
d0687b8
361ce22
 
 
 
 
 
 
d0687b8
 
 
 
 
 
 
361ce22
 
 
 
 
 
 
d0687b8
8b7f73f
361ce22
d0687b8
 
361ce22
d0687b8
 
 
 
8b7f73f
d0687b8
8b7f73f
361ce22
 
d0687b8
361ce22
8b7f73f
 
 
 
361ce22
8b7f73f
 
 
 
 
 
 
 
d0687b8
361ce22
 
 
 
 
 
d0687b8
 
 
361ce22
 
 
 
 
 
d0687b8
 
8b7f73f
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
import os
import gradio as gr
import torch
import torch.nn as nn
import timm
import json
from torchvision import transforms
from PIL import Image

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_PATH = os.path.join(BASE_DIR, "artifacts", "best_model.pt")
LABELS_PATH = os.path.join(BASE_DIR, "data", "style_labels.json")
# second model
ARTIST_MODEL_PATH = os.path.join(BASE_DIR, "artifacts", "best_artist_model.pt")
ARTIST_LABELS_PATH = os.path.join(BASE_DIR, "data", "artist_labels.json")

# define the model architecture (same as training) to load the weights correctly, num_classes will be altered when loading
class ArtClassifier(nn.Module): 
    def __init__(self, num_classes):
        super().__init__()
        self.backbone = timm.create_model("efficientnet_b0", pretrained=False, num_classes=0)
        num_features = self.backbone.num_features
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.3),
            nn.Linear(num_features, 512),
            nn.ReLU(),
            nn.Dropout(p=0.2),
            nn.Linear(512, num_classes)
        )
    def forward(self, x):
        features = self.backbone(x)
        return self.classifier(features)

# helper function to load model and labels, returns model and idx_to_label dict
def load_model(model_path, labels_path):
    with open(labels_path, "r") as f:
        label_to_idx = json.load(f)

    idx_to_label = {int(i): label for label, i in label_to_idx.items()}

    model = ArtClassifier(num_classes=len(label_to_idx))
    model.load_state_dict(torch.load(model_path, map_location=torch.device("cpu")))
    model.eval()

    return model, idx_to_label

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225]),
])

# call the helper function to load both models and their corresponding idx_to_label mappings
style_model, idx_to_style = load_model(MODEL_PATH, LABELS_PATH)
artist_model, idx_to_artist = load_model(ARTIST_MODEL_PATH, ARTIST_LABELS_PATH)


def make_prediction_html(model, idx_to_label, image, title):
    if image is None:
        return "<p>No image provided.</p>"
    
    pil_image = Image.fromarray(image).convert("RGB")
    tensor = transform(pil_image).unsqueeze(0)

    with torch.no_grad():
        outputs = model(tensor)
        probs = torch.softmax(outputs, dim=1)[0]
    top_probs, top_idxs = torch.topk(probs, 3)
    
    medals = ["🥇", "🥈", "🥉"]
    colors = ["#4f46e5", "#7c3aed", "#a855f7"]
    html = f"<div style='font-family:sans-serif;padding:10px'><h3>{title}</h3>"

    for i, (p, idx) in enumerate(zip(top_probs, top_idxs)):
        label = idx_to_label[idx.item()] # rename variable to fit broad use case (style or artist)
        pct = float(p.item()) * 100
        html += f"""
        <div style='margin-bottom:16px'>
            <div style='display:flex;justify-content:space-between;margin-bottom:4px'>
                <span style='font-weight:600'>{medals[i]} {label}</span>
                <span style='color:#666'>{pct:.1f}%</span>
            </div>
            <div style='background:#e5e7eb;border-radius:999px;height:12px'>
                <div style='background:{colors[i]};width:{pct:.1f}%;height:12px;border-radius:999px;transition:width 0.5s'></div>
            </div>
        </div>"""
    html += "</div>"
    return html

def predict(image):
    style_html = make_prediction_html(style_model, idx_to_style, image, "Art Style Predictions")
    artist_html = make_prediction_html(artist_model, idx_to_artist, image, "Artist Predictions")

    return style_html, artist_html

demo = gr.Interface(
    fn=predict,
    inputs=gr.Image(label="Upload your artwork"),
    outputs=[
        gr.HTML(label="Art Style Predictions"),
        gr.HTML(label="Artist Predictions")
    ],
    title="Art Style + Artist Classifier",
    description="Upload a photo of your artwork to identify which art style and artist's work it most resembles.",
)

demo.launch(server_name="0.0.0.0", server_port=7860)