File size: 4,941 Bytes
b36a0df
 
 
 
 
 
7bb277b
e6f939a
b36a0df
 
e6f939a
b36a0df
7bb277b
e6f939a
b36a0df
 
 
 
 
 
 
 
 
 
 
 
7bb277b
b36a0df
e6f939a
7bb277b
e6f939a
 
 
7bb277b
 
 
 
 
 
e6f939a
 
 
 
 
 
 
 
 
 
b36a0df
 
e6f939a
b36a0df
 
 
e6f939a
 
 
 
 
 
b36a0df
 
e6f939a
b36a0df
e6f939a
 
 
 
b36a0df
 
 
 
 
 
7bb277b
1b1ae89
b36a0df
 
7bb277b
1b1ae89
e6f939a
1b1ae89
7bb277b
1b1ae89
7bb277b
1b1ae89
 
 
 
 
 
 
 
 
b36a0df
1b1ae89
b36a0df
 
1b1ae89
 
 
 
7bb277b
 
1b1ae89
e6f939a
 
b36a0df
7bb277b
 
1b1ae89
 
7bb277b
b36a0df
7bb277b
b36a0df
 
e6f939a
b36a0df
 
 
 
c75e55c
e6f939a
7bb277b
b36a0df
 
 
 
 
e6f939a
 
 
 
 
2447702
e6f939a
 
 
b36a0df
7bb277b
 
e6f939a
b36a0df
7bb277b
e6f939a
b36a0df
 
 
 
 
 
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
import gradio as gr
import torch
import timm
import torchvision.transforms as T
from PIL import Image
import numpy as np
import pandas as pd
import json, os

# ------------------------------
# CONFIGURATION
# ------------------------------
DEVICE = "cpu"
NUM_CLASSES = 200

MODEL_PATHS = {
    "EfficientNet-B3": "models/BestEfficientNetB3_model.pth",
    "EfficientNetV2-S (in21k)": "models/BestEfficientNetV2S_model.pth"
}

MODEL_NAMES = {
    "EfficientNet-B3": "tf_efficientnet_b3_ns",
    "EfficientNetV2-S (in21k)": "tf_efficientnetv2_s.in21k"
}

# ------------------------------
# LOAD CLASS NAMES (supports JSON or TXT)
# ------------------------------
CLASS_NAMES = []

if os.path.exists("class_names.json"):
    try:
        with open("class_names.json", "r") as f:
            data = json.load(f)
            if isinstance(data, dict):
                # some JSONs store indices as keys
                CLASS_NAMES = [v for _, v in sorted(data.items(), key=lambda x: int(x[0]))]
            elif isinstance(data, list):
                CLASS_NAMES = data
        print(f"✅ Loaded {len(CLASS_NAMES)} class names from class_names.json")
    except Exception as e:
        print(f"⚠️ Error reading class_names.json: {e}")
elif os.path.exists("class_names.txt"):
    with open("class_names.txt", "r") as f:
        CLASS_NAMES = [line.strip() for line in f.readlines()]
    print(f"✅ Loaded {len(CLASS_NAMES)} class names from class_names.txt")
else:
    CLASS_NAMES = [f"Class {i}" for i in range(NUM_CLASSES)]
    print("⚠️ No class_names file found — using generic labels")

# ------------------------------
# MODEL LOADING + CACHING
# ------------------------------
@torch.inference_mode()
def load_model(model_name):
    model = timm.create_model(MODEL_NAMES[model_name],
                              pretrained=False,
                              num_classes=NUM_CLASSES)
    model.load_state_dict(torch.load(MODEL_PATHS[model_name],
                                     map_location=DEVICE))
    model.to(DEVICE).eval()
    return model

_loaded_models = {}
def get_model(model_name):
    if model_name not in _loaded_models:
        print(f"🔄 Loading {model_name} ...")
        _loaded_models[model_name] = load_model(model_name)
    return _loaded_models[model_name]

# ------------------------------
# PREDICTION FUNCTION
# ------------------------------
def predict(image, model_choice):
    if image is None:
        return pd.DataFrame(), "Please upload an image."

    model = get_model(model_choice)

    # Ensure RGB
    try:
        image = image.convert("RGB")
    except Exception as e:
        return pd.DataFrame(), f"❌ Error reading image: {e}"

    # Resize dynamically
    size = 300 if "b3" in model_choice.lower() else 384
    preprocess = T.Compose([
        T.Resize((size, size)),
        T.ToTensor(),
        T.Normalize(mean=[0.485, 0.456, 0.406],
                    std=[0.229, 0.224, 0.225])
    ])
    img_tensor = preprocess(image).unsqueeze(0).to(DEVICE)

    with torch.no_grad():
        logits = model(img_tensor)
        probs = torch.nn.functional.softmax(logits, dim=1)[0]

    top5 = torch.topk(probs, 5)
    indices = top5.indices.cpu().numpy().tolist()
    scores = top5.values.cpu().numpy().tolist()

    # Map to readable names
    if CLASS_NAMES and len(CLASS_NAMES) > max(indices):
        names = [CLASS_NAMES[i] for i in indices]
    else:
        names = [f"Class {i}" for i in indices]

    # Make a DataFrame (this avoids 'undefined' issue)
    df = pd.DataFrame({
        "Class": names,
        "Confidence (%)": [round(s * 100, 2) for s in scores]
    })

    return df, f"✅ Prediction complete using {model_choice}"

# ------------------------------
# GRADIO INTERFACE
# ------------------------------
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
    gr.Markdown(
        """
        #Bird Species Classifier  
        Upload a bird image or use your camera to identify its species.  
        Supports multiple EfficientNet models and displays top-5 predictions.
        """
    )

    with gr.Row():
        with gr.Column(scale=1):
            img_in = gr.Image(type="pil",
                              label="Upload or Capture Image",
                              sources=["upload", "webcam"])
            model_sel = gr.Dropdown(
                choices=list(MODEL_PATHS.keys()),
                value="EfficientNetV2-S",
                label="Select Model")
            btn = gr.Button("🔍 Classify")

        with gr.Column(scale=1):
            df_out = gr.Dataframe(label="Top-5 Predictions",
                                  interactive=False)
            status = gr.Textbox(label="Status", interactive=False)

    btn.click(fn=predict, inputs=[img_in, model_sel],
              outputs=[df_out, status])

    gr.Markdown(
    )

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)