Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import tensorflow as tf | |
| from PIL import Image | |
| import numpy as np | |
| import json | |
| import os | |
| import pandas as pd | |
| st.set_page_config(page_title="Food Image Classification", layout="centered") | |
| def load_model_and_metadata(): | |
| """ | |
| Load the saved Keras model (best_model.keras) and metadata.json | |
| from the current directory. | |
| """ | |
| cwd = os.getcwd() | |
| # 1. Load model file | |
| model_file = os.path.join(cwd, "best_model.keras") | |
| if not os.path.exists(model_file): | |
| st.error(f"Model file not found at '{model_file}'. Ensure best_model.keras is present.") | |
| return None, None, None, None | |
| try: | |
| model = tf.keras.models.load_model(model_file) | |
| except Exception as e: | |
| st.error(f"Failed to load model from {model_file}: {e}") | |
| return None, None, None, None | |
| # 2. Load metadata.json at root | |
| meta_path = os.path.join(cwd, "metadata.json") | |
| if not os.path.exists(meta_path): | |
| st.error(f"metadata.json not found at '{meta_path}'.") | |
| return model, None, None, None | |
| try: | |
| with open(meta_path, "r") as f: | |
| metadata = json.load(f) | |
| except Exception as e: | |
| st.error(f"Failed to read metadata.json: {e}") | |
| return model, None, None, None | |
| class_names = metadata.get("class_names") | |
| IMG_HEIGHT = metadata.get("IMG_HEIGHT") | |
| IMG_WIDTH = metadata.get("IMG_WIDTH") | |
| if class_names is None or IMG_HEIGHT is None or IMG_WIDTH is None: | |
| st.error("metadata.json must contain 'class_names', 'IMG_HEIGHT', and 'IMG_WIDTH'") | |
| return model, None, None, None | |
| return model, class_names, IMG_HEIGHT, IMG_WIDTH | |
| model, class_names, IMG_HEIGHT, IMG_WIDTH = load_model_and_metadata() | |
| if model is None or class_names is None: | |
| st.stop() | |
| st.title("Food Image Classification") | |
| st.write("Upload a food image (Bread / Soup / Vegetable-Fruit) to classify.") | |
| with st.sidebar: | |
| st.header("Instructions") | |
| st.write( | |
| f""" | |
| - Upload a JPG/PNG image of food. | |
| - The model expects images resized to {IMG_HEIGHT}×{IMG_WIDTH}. | |
| - The model classes: {', '.join(class_names)}. | |
| - For best results, upload a clear image of a single food item. | |
| """ | |
| ) | |
| st.subheader("Image Prediction") | |
| uploaded_file = st.file_uploader("Choose an image...", type=["jpg","jpeg","png"], key="single") | |
| if uploaded_file is not None: | |
| try: | |
| image = Image.open(uploaded_file).convert("RGB") | |
| except Exception as e: | |
| st.error(f"Cannot open image: {e}") | |
| image = None | |
| if image: | |
| st.image(image, caption="Uploaded Image", use_column_width=True) | |
| # Preprocess: resize and scale to [0,1] | |
| img_resized = image.resize((IMG_WIDTH, IMG_HEIGHT)) | |
| img_array = np.array(img_resized).astype("float32") / 255.0 | |
| input_tensor = np.expand_dims(img_array, axis=0) # shape (1, H, W, 3) | |
| # Predict | |
| preds = model.predict(input_tensor, verbose=0) | |
| pred_idx = int(np.argmax(preds[0])) | |
| pred_class = class_names[pred_idx] | |
| confidence = float(preds[0][pred_idx]) | |
| st.write(f"**Prediction:** {pred_class} \n**Confidence:** {confidence:.3f}") | |
| # Show full probability distribution | |
| prob_df = pd.DataFrame({ | |
| "class": class_names, | |
| "probability": preds[0] | |
| }) | |
| st.bar_chart(data=prob_df.set_index("class")) | |
| # Download result as CSV | |
| result_df = pd.DataFrame([{ | |
| "filename": uploaded_file.name, | |
| "predicted_class": pred_class, | |
| "confidence": confidence | |
| }]) | |
| csv = result_df.to_csv(index=False).encode('utf-8') | |
| st.download_button( | |
| label="Download prediction as CSV", | |
| data=csv, | |
| file_name="prediction.csv", | |
| mime="text/csv" | |
| ) | |