Spaces:
Sleeping
Sleeping
File size: 6,521 Bytes
834c379 e04e193 08319ea 834c379 f1a5287 36b44ac e04e193 36b44ac a42482b 36b44ac a42482b f1a5287 36b44ac f1a5287 36b44ac 32ab2b9 f1a5287 a42482b e04e193 a798df7 36b44ac a42482b 36b44ac a798df7 a42482b a798df7 36b44ac a798df7 36b44ac a798df7 36b44ac e04e193 36b44ac e04e193 a5aa4b2 e04e193 36b44ac a42482b 36b44ac a42482b 36b44ac a42482b 36b44ac f1a5287 36b44ac a42482b 36b44ac a42482b 36b44ac a798df7 36b44ac a42482b 36b44ac a42482b a798df7 f1a5287 36b44ac a42482b 36b44ac a42482b 834c379 e04e193 36b44ac a798df7 36b44ac a798df7 36b44ac a798df7 | 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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | import os
import json
import numpy as np
import streamlit as st
from PIL import Image
import tensorflow as tf
# --------------------------------------------------
# Page config
# --------------------------------------------------
st.set_page_config(
page_title="ResNet50 Image Predictor",
page_icon="π§ ",
layout="centered"
)
st.title("π§ ResNet50 Image Predictor")
st.write("Classifies mushroom images using a trained ResNet50 model (architecture in code + weights from src/).")
# --------------------------------------------------
# Paths (fixed, HF friendly)
# --------------------------------------------------
MODEL_WEIGHTS_PATH = "src/resnet50_weights_noBN3.h5"
CLASS_NAMES_PATH = "src/class_names4.json"
IMG_SIZE = (224, 224)
# --------------------------------------------------
# β
FIXED CLASS NAMES (YOUR TABLE) β fallback if JSON is wrong
# --------------------------------------------------
CLASS_NAMES_TABLE = [
"amanita", # 0
"boletus", # 1
"chantelle", # 2
"deterrimus", # 3
"rufus", # 4
"torminosus", # 5
"aurantiacum", # 6
"procera", # 7
"involutus", # 8
"russula", # 9
]
# --------------------------------------------------
# Helpers
# --------------------------------------------------
def _safe_listdir(path: str):
try:
return sorted(os.listdir(path))
except Exception as e:
return f"Could not list dir '{path}': {e}"
def load_class_names(path: str) -> list:
"""
Loads class names from JSON.
If JSON is missing or invalid or looks like ["0","1","2"...],
we fall back to CLASS_NAMES_TABLE.
"""
# If file not found -> fallback
if not os.path.exists(path):
return CLASS_NAMES_TABLE
# Try load JSON
try:
with open(path, "r", encoding="utf-8") as f:
names = json.load(f)
except Exception:
return CLASS_NAMES_TABLE
# Must be list and non-empty
if not isinstance(names, list) or len(names) == 0:
return CLASS_NAMES_TABLE
# If JSON contains only numbers as strings -> it's wrong -> fallback
# Example: ["0","1","2","3"...]
if all(isinstance(x, str) and x.strip().isdigit() for x in names):
return CLASS_NAMES_TABLE
# If JSON contains dict like {"amanita":0,...} -> convert to correct order
if isinstance(names, dict):
# Expect name->idx mapping
idx_to_name = {int(v): k for k, v in names.items()}
ordered = [idx_to_name[i] for i in range(len(idx_to_name))]
return ordered
# Otherwise: assume it's already correct list of names
return names
def build_resnet50_classifier(num_classes: int) -> tf.keras.Model:
base_model = tf.keras.applications.ResNet50(
weights="imagenet",
include_top=False,
input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3),
)
base_model.trainable = False
model = tf.keras.Sequential([
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(256, activation="relu"),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(num_classes, activation="softmax"),
])
# Compile not required for inference, but ok
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
return model
@st.cache_resource
def load_model_and_assets(weights_path: str, class_names_path: str):
class_names = load_class_names(class_names_path)
model = build_resnet50_classifier(num_classes=len(class_names))
if not os.path.exists(weights_path):
raise FileNotFoundError(f"Missing weights file: {weights_path}")
model.load_weights(weights_path)
return model, class_names
def preprocess_image(pil_img: Image.Image) -> np.ndarray:
img = pil_img.convert("RGB").resize(IMG_SIZE)
x = np.array(img, dtype=np.float32)
x = np.expand_dims(x, axis=0)
x = tf.keras.applications.resnet50.preprocess_input(x)
return x
# --------------------------------------------------
# Debug info (HF)
# --------------------------------------------------
with st.expander("π Debug info (HuggingFace check)"):
st.write("Files in repo root:", _safe_listdir("."))
st.write("Files in src/:", _safe_listdir("src"))
st.write("Weights exists:", os.path.exists(MODEL_WEIGHTS_PATH), "->", MODEL_WEIGHTS_PATH)
st.write("Class names exists:", os.path.exists(CLASS_NAMES_PATH), "->", CLASS_NAMES_PATH)
st.write("TensorFlow version:", tf.__version__)
# --------------------------------------------------
# Load model + assets
# --------------------------------------------------
try:
model, class_names = load_model_and_assets(MODEL_WEIGHTS_PATH, CLASS_NAMES_PATH)
st.success("β
Model + weights loaded successfully!")
except Exception as e:
st.error("β Model could not be loaded.")
st.exception(e)
st.stop()
# --------------------------------------------------
# Show available classes (INDEX + NAME)
# --------------------------------------------------
with st.expander("π§ͺ Available mushroom classes (you can test these)"):
st.write(f"Total classes: **{len(class_names)}**")
for i, name in enumerate(class_names):
st.write(f"**{i}** β **{name}**")
# --------------------------------------------------
# Image upload + prediction
# --------------------------------------------------
uploaded_file = st.file_uploader(
"Upload a mushroom image",
type=["jpg", "jpeg", "png", "webp"]
)
if uploaded_file is None:
st.info("π Please upload an image to start prediction.")
else:
img = Image.open(uploaded_file)
st.image(img, caption="Uploaded image", use_container_width=True)
x = preprocess_image(img)
preds = model.predict(x, verbose=0)[0]
pred_idx = int(np.argmax(preds))
pred_conf = float(preds[pred_idx])
pred_name = class_names[pred_idx] if 0 <= pred_idx < len(class_names) else f"Class {pred_idx}"
st.subheader("β
Prediction")
st.write(f"**Predicted class index:** {pred_idx}")
st.write(f"**Predicted class name:** {pred_name}")
st.write(f"**Confidence:** {pred_conf:.4f}")
st.subheader("π Top-3 predictions")
top3_idx = np.argsort(preds)[::-1][:3]
for rank, idx in enumerate(top3_idx, start=1):
idx = int(idx)
name = class_names[idx] if 0 <= idx < len(class_names) else f"Class {idx}"
prob = float(preds[idx])
st.write(f"{rank}. **{name}** (class {idx}) β **{prob*100:.2f}%**") |