# Plant Species Classifier - Hugging Face Space # Developed with PyTorch + Gradio import gradio as gr import torch import torch.nn as nn from torchvision import models, transforms from PIL import Image import wikipediaapi import random from io import BytesIO import requests # Load model and mappings print("Loading model...") device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') checkpoint = torch.load('best_model.pt', map_location=device) num_classes = checkpoint['num_classes'] idx_to_label = checkpoint['idx_to_label'] # Build model model = models.resnet18(weights=None) model.fc = nn.Linear(model.fc.in_features, num_classes) model.load_state_dict(checkpoint['model_state_dict']) model = model.to(device) model.eval() # Transforms val_test_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # Wikipedia API wiki = wikipediaapi.Wikipedia( language='en', user_agent='PlantClassifier/1.0 (https://huggingface.co/spaces)' ) def get_species_info(species_name): try: page = wiki.page(species_name) if page.exists(): summary = page.summary[:500] if len(page.summary) > 500: summary += "..." return { 'title': page.title, 'summary': summary, 'url': page.fullurl, 'found': True } else: genus = species_name.split()[0] page = wiki.page(genus) if page.exists(): summary = page.summary[:500] if len(page.summary) > 500: summary += "..." return { 'title': f"{page.title} (genus)", 'summary': summary, 'url': page.fullurl, 'found': True } return { 'title': species_name, 'summary': f"No Wikipedia information found for {species_name}.", 'url': '', 'found': False } except Exception as e: return { 'title': species_name, 'summary': f"Error fetching information: {str(e)}", 'url': '', 'found': False } def predict_and_display(input_image, image_url): img = None if image_url and image_url.strip(): try: response = requests.get(image_url.strip(), timeout=10, headers={"User-Agent": "Mozilla/5.0"}) response.raise_for_status() img = Image.open(BytesIO(response.content)).convert('RGB') except Exception as e: return f"❌ Error downloading image from URL: {str(e)}", None, [] elif input_image is not None: img = input_image else: return "⚠️ Please upload an image or provide a URL", None, [] model.eval() img_tensor = val_test_transform(img).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(img_tensor) probs = torch.nn.functional.softmax(outputs, dim=1) topk_probs, topk_indices = probs.topk(5, dim=1) predictions = [] for prob, idx in zip(topk_probs[0], topk_indices[0]): label = idx_to_label[str(idx.item())] predictions.append((label, prob.item())) html = "
Confidence: {confidence*100:.2f}%
{wiki_info['summary']}
{f"📖 Read more on Wikipedia →" if wiki_info['url'] else ""}Predictions will appear here...
" ) gr.Markdown("---") gr.Markdown("### 🔬 Species Details") gr.Markdown("Select a prediction number (1-5) to see detailed information:") with gr.Row(): species_selector = gr.Slider( minimum=1, maximum=5, step=1, value=1, label="Select Prediction #", info="Choose which prediction to explore" ) details_btn = gr.Button("📊 Show Details", variant="secondary", size="lg") species_info = gr.HTML() def predict_wrapper(img, url): html, display_img, preds = predict_and_display(img, url) return html, preds predict_btn.click( fn=predict_wrapper, inputs=[input_image, image_url], outputs=[predictions_display, predictions_state] ) details_btn.click( fn=show_species_details, inputs=[predictions_state, species_selector], outputs=[species_info] ) gr.Markdown(""" ---🌱 Built with PyTorch + Gradio | Data from GBIF