|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import tensorflow as tf |
|
|
from PIL import Image |
|
|
import os |
|
|
import glob |
|
|
import zipfile |
|
|
import time |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Cloud Inventory System", layout="wide") |
|
|
|
|
|
|
|
|
IMAGES_DIR = "sample_fruits_50" |
|
|
ZIP_FILE = "sample_fruits_50.zip" |
|
|
|
|
|
|
|
|
if not os.path.exists(IMAGES_DIR): |
|
|
if os.path.exists(ZIP_FILE): |
|
|
with st.spinner("Unpacking image database..."): |
|
|
|
|
|
os.makedirs(IMAGES_DIR, exist_ok=True) |
|
|
with zipfile.ZipFile(ZIP_FILE, 'r') as zip_ref: |
|
|
|
|
|
zip_ref.extractall(IMAGES_DIR) |
|
|
st.success("Database loaded!") |
|
|
else: |
|
|
st.warning(f"β οΈ Please upload '{ZIP_FILE}' to the Files tab!") |
|
|
|
|
|
|
|
|
@st.cache_resource |
|
|
def load_model(): |
|
|
|
|
|
model_path = "fruit_classifier.h5" |
|
|
if not os.path.exists(model_path): |
|
|
return None |
|
|
return tf.keras.models.load_model(model_path) |
|
|
|
|
|
model = load_model() |
|
|
|
|
|
|
|
|
CLASS_NAMES = [ |
|
|
'fresh_apple', 'fresh_banana', 'fresh_grape', 'fresh_orange', 'fresh_pomegranate', |
|
|
'rotten_apple', 'rotten_banana', 'rotten_grape', 'rotten_orange', 'rotten_pomegranate' |
|
|
] |
|
|
|
|
|
def get_initial_db(): |
|
|
fruits = ['Apple', 'Banana', 'Grape', 'Orange', 'Pomegranate'] |
|
|
data = {fruit: {'Fresh Qty': 0, 'Rotten Qty': 0} for fruit in fruits} |
|
|
return data |
|
|
|
|
|
|
|
|
st.title("π Cloud AI Inventory Scan") |
|
|
st.markdown("This system will scan the **50 test images** uploaded to the cloud.") |
|
|
|
|
|
if model is None: |
|
|
st.error("Model file 'fruit_classifier.h5' not found in Files tab.") |
|
|
st.stop() |
|
|
|
|
|
if st.button("π Start Cloud Scan"): |
|
|
|
|
|
|
|
|
files_to_scan = [] |
|
|
if os.path.exists(IMAGES_DIR): |
|
|
all_images = glob.glob(os.path.join(IMAGES_DIR, "**", "*.*"), recursive=True) |
|
|
files_to_scan = [f for f in all_images if f.lower().endswith(('.png', '.jpg', '.jpeg'))] |
|
|
|
|
|
|
|
|
import random |
|
|
if len(files_to_scan) > 15: |
|
|
files_to_scan = random.sample(files_to_scan, 15) |
|
|
|
|
|
if not files_to_scan: |
|
|
st.error("No images found! The zip file might be empty or failed to unzip.") |
|
|
st.stop() |
|
|
|
|
|
|
|
|
col1, col2 = st.columns([1.5, 1]) |
|
|
with col1: |
|
|
st.subheader("π¦ Live Inventory") |
|
|
table_placeholder = st.empty() |
|
|
with col2: |
|
|
st.subheader("π· Feed") |
|
|
image_placeholder = st.empty() |
|
|
|
|
|
|
|
|
db_data = get_initial_db() |
|
|
current_df = pd.DataFrame.from_dict(db_data, orient='index') |
|
|
table_placeholder.table(current_df) |
|
|
progress_bar = st.progress(0) |
|
|
|
|
|
|
|
|
for i, filepath in enumerate(files_to_scan): |
|
|
|
|
|
image_placeholder.image(filepath, caption=f"Item #{i+1}", use_container_width=True) |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
img = Image.open(filepath) |
|
|
img = img.resize((224, 224)) |
|
|
img_arr = np.array(img) |
|
|
if img_arr.shape[-1] == 4: img_arr = img_arr[..., :3] |
|
|
img_arr = np.expand_dims(img_arr, axis=0) / 255.0 |
|
|
|
|
|
|
|
|
preds = model.predict(img_arr, verbose=0) |
|
|
idx = np.argmax(preds[0]) |
|
|
label = CLASS_NAMES[idx] |
|
|
|
|
|
|
|
|
parts = label.split('_') |
|
|
quality = parts[0] |
|
|
fruit = parts[1].title() |
|
|
|
|
|
|
|
|
if quality == 'fresh': |
|
|
db_data[fruit]['Fresh Qty'] += 1 |
|
|
else: |
|
|
db_data[fruit]['Rotten Qty'] += 1 |
|
|
|
|
|
|
|
|
current_df = pd.DataFrame.from_dict(db_data, orient='index') |
|
|
table_placeholder.table(current_df) |
|
|
|
|
|
time.sleep(0.2) |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"Error: {e}") |
|
|
|
|
|
progress_bar.progress((i + 1) / len(files_to_scan)) |
|
|
|
|
|
st.success("Scan Complete!") |
|
|
|
|
|
|
|
|
st.divider() |
|
|
st.subheader("π Final Cloud Report") |
|
|
st.bar_chart(current_df, color=["#4CAF50", "#FF5252"]) |