""" 🌳 Tree Classification App Multi-stage hierarchical AI pipeline for tree species and growth stage detection. Uses 4 trained models + Grad-CAM heatmaps. """ import streamlit as st from PIL import Image import torch import numpy as np from utils.model_loader import load_all_models from utils.predictor import predict_tree, predict_species, predict_stage from utils.gradcam import generate_gradcam # ────────────────────────────────────────────── # PAGE CONFIGURATION # ────────────────────────────────────────────── st.set_page_config( page_title="🌳 Tree Classifier", page_icon="🌳", layout="wide", initial_sidebar_state="expanded" ) # ────────────────────────────────────────────── # CUSTOM CSS STYLING # ────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ────────────────────────────────────────────── # HEADER # ────────────────────────────────────────────── st.markdown('
🌳 Tree Species & Growth Stage Classifier
', unsafe_allow_html=True) st.markdown('
Upload a tree photo to identify its species and growth stage using AI
', unsafe_allow_html=True) # ────────────────────────────────────────────── # SIDEBAR — INFO PANEL # ────────────────────────────────────────────── with st.sidebar: st.header("📖 How It Works") st.markdown(""" This app uses **4 AI models** in sequence: 1. 🔍 **Tree Detection** — Is it a tree? 2. 🌿 **Species Detection** — Mango or White Gum? 3. 📏 **Stage Classification** — What growth stage? Each step also shows a **Grad-CAM heatmap** — a visualization of which part of the image the AI focused on. """) st.divider() st.header("⚠️ System Limitations") st.markdown(""" This system is currently trained only on: - 🥭 **Mango Trees** - 🌿 **White Gum / Eucalyptus Trees** Upload images of these species for accurate results. """) st.divider() st.header("📊 Growth Stages") st.markdown(""" - 🌱 **Seedling** — Very young, just sprouted - 🌿 **Sapling** — Young, growing - 🌳 **Mature** — Fully grown - 🍂 **Overmature** — Past peak, aging """) # ────────────────────────────────────────────── # MODEL LOADING (cached so it only runs once) # ────────────────────────────────────────────── @st.cache_resource def get_models(): """Load all 4 models once and cache them in memory.""" with st.spinner("Loading AI models... (this only happens once)"): models = load_all_models() return models try: models = get_models() st.sidebar.success("✅ All models loaded successfully") except Exception as e: st.error(f"❌ Failed to load models: {e}") st.info("Make sure your model files are in the `models/` folder.") st.stop() # ────────────────────────────────────────────── # IMAGE UPLOAD # ────────────────────────────────────────────── st.divider() uploaded_file = st.file_uploader( "📤 Upload a tree image (JPG, PNG, JPEG)", type=["jpg", "jpeg", "png"], help="For best results, use a clear photo of a single tree." ) if uploaded_file is None: # Show a friendly placeholder when no image is uploaded st.info("👆 Upload an image above to start the classification pipeline.") st.markdown(""" ### 🧭 What happens after you upload: | Step | What the AI does | |------|-----------------| | Step 1 | Checks if the image contains a tree | | Step 2 | Identifies if it's a Mango or White Gum tree | | Step 3 | Predicts the tree's growth stage | | All steps | Shows a Grad-CAM heatmap highlighting AI focus areas | """) st.stop() # ────────────────────────────────────────────── # IMAGE DISPLAY # ────────────────────────────────────────────── image = Image.open(uploaded_file).convert("RGB") col_img, col_info = st.columns([1, 1]) with col_img: st.subheader("📷 Uploaded Image") st.image(image, caption="Your uploaded image", use_container_width=True) with col_info: st.subheader("🔬 Analysis Pipeline") st.markdown(""" The image will be processed through the following stages: ``` Image │ ▼ [1] Tree vs Non-Tree │ (if tree detected) ▼ [2] Species: Mango or White Gum? │ ▼ [3] Growth Stage Classification ``` Each step outputs a **confidence score** and a **Grad-CAM heatmap**. """) # ────────────────────────────────────────────── # PIPELINE EXECUTION # ────────────────────────────────────────────── st.divider() st.header("🤖 AI Classification Results") with st.spinner("🔍 Running analysis..."): # ── PHASE 1: Tree vs Non-Tree ────────────────── st.subheader("Phase 1 — Tree Detection") try: tree_label, tree_conf, tree_tensor = predict_tree(image, models["tree_vs_nontree"]) tree_heatmap = generate_gradcam(image, models["tree_vs_nontree"], tree_tensor) except Exception as e: st.error(f"Error in tree detection: {e}") st.stop() col1, col2 = st.columns(2) with col1: st.image(image, caption="Original Image", use_container_width=True) with col2: st.image(tree_heatmap, caption="Grad-CAM: Where the model looked", use_container_width=True) if tree_label == "Non-Tree": st.markdown(f"""
Result: Non-Tree detected ({tree_conf:.1%} confidence)
The pipeline has stopped. Please upload an image containing a tree.
""", unsafe_allow_html=True) st.stop() else: st.markdown(f"""
Result: Tree detected ({tree_conf:.1%} confidence) — Proceeding to species detection.
""", unsafe_allow_html=True) # ── PHASE 2: Species Detection ────────────────── st.divider() st.subheader("Phase 2 — Species Detection") st.markdown("""
⚠️ Note: This system is trained only on Mango and White Gum (Eucalyptus) trees. Please upload images of these species for accurate results.
""", unsafe_allow_html=True) try: species_label, species_conf, species_tensor = predict_species(image, models["species"]) species_heatmap = generate_gradcam(image, models["species"], species_tensor) except Exception as e: st.error(f"Error in species detection: {e}") st.stop() col3, col4 = st.columns(2) with col3: st.image(image, caption="Original Image", use_container_width=True) with col4: st.image(species_heatmap, caption="Grad-CAM: Species focus area", use_container_width=True) species_icon = "🥭" if species_label == "Mango" else "🌿" st.markdown(f"""
{species_icon} Detected Species: {species_label} ({species_conf:.1%} confidence)
""", unsafe_allow_html=True) # ── PHASE 3: Growth Stage Classification ──────── st.divider() st.subheader("Phase 3 — Growth Stage Classification") # Route to the correct stage model based on species if species_label == "Mango": stage_model = models["mango_stage"] model_name = "Mango Stage Model" else: stage_model = models["gum_stage"] model_name = "White Gum Stage Model" st.info(f"🔀 Routing to: **{model_name}**") try: stage_label, stage_conf, stage_tensor = predict_stage(image, stage_model, species_label) stage_heatmap = generate_gradcam(image, stage_model, stage_tensor) except Exception as e: st.error(f"Error in stage classification: {e}") st.stop() col5, col6 = st.columns(2) with col5: st.image(image, caption="Original Image", use_container_width=True) with col6: st.image(stage_heatmap, caption="Grad-CAM: Stage focus area", use_container_width=True) stage_icons = {"Seedling": "🌱", "Sapling": "🌿", "Mature": "🌳", "Overmature": "🍂"} stage_icon = stage_icons.get(stage_label, "🌲") st.markdown(f"""
{stage_icon} Growth Stage: {stage_label}   ({stage_conf:.1%} confidence)
""", unsafe_allow_html=True) # ── FINAL SUMMARY ──────────────────────────────── st.divider() st.subheader("📋 Summary") summary_col1, summary_col2, summary_col3 = st.columns(3) with summary_col1: st.metric("🔍 Detection", "Tree", f"{tree_conf:.1%}") with summary_col2: st.metric(f"{species_icon} Species", species_label, f"{species_conf:.1%}") with summary_col3: st.metric(f"{stage_icon} Growth Stage", stage_label, f"{stage_conf:.1%}")