import streamlit as st import torch import torch.nn as nn import torch.nn.functional as F from torchvision import transforms, models from PIL import Image import json, os, time # ── Page config ────────────────────────────────────────────── st.set_page_config( page_title="Animal Classifier", page_icon="🐾", layout="centered", ) # ── Custom CSS ─────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ── Constants ───────────────────────────────────────────────── NUM_CLASSES = 10 DEVICE = torch.device("cpu") # Streamlit Cloud has no GPU IMG_SIZE = 224 CLASS_NAMES = { 0: "Dog", 1: "Horse", 2: "Elephant", 3: "Butterfly",4: "Chicken", 5: "Cat", 6: "Cow", 7: "Sheep", 8: "Spider", 9: "Squirrel", } EMOJIS = { "Dog":"🐶","Horse":"🐴","Elephant":"🐘","Butterfly":"🦋", "Chicken":"🐔","Cat":"🐱","Cow":"🐄","Sheep":"🐑", "Spider":"🕷️","Squirrel":"🐿️", } FUN_FACTS = { "Dog": "Dogs have a sense of smell 40× stronger than humans.", "Horse": "Horses can sleep both standing up and lying down.", "Elephant": "Elephants are the only animals that can't jump.", "Butterfly": "Butterflies taste with their feet.", "Chicken": "Chickens have better color vision than humans.", "Cat": "Cats spend 70% of their lives sleeping.", "Cow": "Cows have best friends and get stressed when separated.", "Sheep": "Sheep can recognize up to 50 other sheep faces.", "Spider": "Spiders recycle their webs by eating them.", "Squirrel": "Squirrels forget where they bury 50% of their nuts.", } infer_tf = transforms.Compose([ transforms.Resize((IMG_SIZE, IMG_SIZE)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) # ── Model loader (cached) ───────────────────────────────────── # Replace the load_model() function in app.py with this: @st.cache_resource(show_spinner=False) def load_model(): from huggingface_hub import hf_hub_download weights_path = hf_hub_download( repo_id="YOUR_HF_USERNAME/animal-classifier", # ← change this filename="best_model.pth", cache_dir="/tmp" ) model = models.efficientnet_b2(weights=None) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(p=0.4), nn.Linear(in_features, 512), nn.SiLU(), nn.Dropout(p=0.3), nn.Linear(512, NUM_CLASSES), ) model.load_state_dict(torch.load(weights_path, map_location=DEVICE)) model.eval() return model # ── Load class mapping if exists, else use default ──────────── def get_class_names(): mapping_path = os.path.join(os.path.dirname(__file__), "class_mapping.json") if os.path.exists(mapping_path): with open(mapping_path) as f: raw = json.load(f) return {int(k): v for k, v in raw.items()} return CLASS_NAMES # ── Predict ─────────────────────────────────────────────────── def predict(img: Image.Image, model, class_names: dict, top_k=3): tensor = infer_tf(img.convert("RGB")).unsqueeze(0).to(DEVICE) with torch.no_grad(): probs = F.softmax(model(tensor), dim=1)[0] top_probs, top_idxs = probs.topk(top_k) return [ { "label": class_names.get(i.item(), f"class_{i}"), "confidence": float(p) * 100, "emoji": EMOJIS.get(class_names.get(i.item(), ""), "🐾"), } for p, i in zip(top_probs.cpu(), top_idxs.cpu()) ] # ── UI ──────────────────────────────────────────────────────── st.markdown('