Upload 7 files
Browse filesFiles from Main Repo
- Dockerfile +29 -0
- ModelMain.py +72 -0
- evaluate.py +14 -0
- main.py +45 -0
- models/downloadModels.py +23 -0
- models/reconstruct_models.py +29 -0
- requirements.txt +20 -0
Dockerfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Base image with Python + minimal setup
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set working directory inside container
|
| 5 |
+
WORKDIR /code
|
| 6 |
+
|
| 7 |
+
# Copy only requirements first to install dependencies
|
| 8 |
+
COPY ml_api/requirements.txt .
|
| 9 |
+
|
| 10 |
+
# Install system dependencies and Python packages
|
| 11 |
+
RUN apt-get update && apt-get install -y \
|
| 12 |
+
gcc \
|
| 13 |
+
libglib2.0-0 \
|
| 14 |
+
libsm6 \
|
| 15 |
+
libxext6 \
|
| 16 |
+
libxrender-dev \
|
| 17 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
# Install Python dependencies
|
| 20 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 21 |
+
|
| 22 |
+
# Now copy the entire app code
|
| 23 |
+
COPY ml_api/ .
|
| 24 |
+
|
| 25 |
+
# Expose port used by Hugging Face Spaces (default: 7860)
|
| 26 |
+
EXPOSE 7860
|
| 27 |
+
|
| 28 |
+
# Run FastAPI with Uvicorn on the correct port
|
| 29 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
ModelMain.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
from torchvision import transforms
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
from tensorflow.keras.models import load_model
|
| 9 |
+
import torchvision.models as models
|
| 10 |
+
|
| 11 |
+
# Load PyTorch model
|
| 12 |
+
layer1 = models.resnet50(pretrained=False)
|
| 13 |
+
layer1.fc = nn.Linear(2048, 2)
|
| 14 |
+
layer1.load_state_dict(torch.load('models/layer1cnn_aanan.pth', map_location=torch.device('cpu')))
|
| 15 |
+
layer1.eval()
|
| 16 |
+
|
| 17 |
+
# Load Keras models
|
| 18 |
+
layer2_bio = load_model('models/layer2bio_cnn.keras')
|
| 19 |
+
layer2_nonbio = load_model('models/layer2non_cnn.keras')
|
| 20 |
+
layer3 = load_model('models/layer3_cnn.keras')
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# --- Preprocessing Functions ---
|
| 24 |
+
def preprocess_image_pytorch(image_bytes, size=(150, 150)):
|
| 25 |
+
img = Image.open(BytesIO(image_bytes)).convert('RGB')
|
| 26 |
+
transform = transforms.Compose([
|
| 27 |
+
transforms.Resize(size),
|
| 28 |
+
transforms.ToTensor(), # shape: (C, H, W)
|
| 29 |
+
])
|
| 30 |
+
return transform(img).unsqueeze(0) # shape: (1, 3, H, W)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def preprocess_image_keras(image_bytes, size=(150, 150)):
|
| 34 |
+
img = Image.open(BytesIO(image_bytes)).convert('RGB')
|
| 35 |
+
img = img.resize(size)
|
| 36 |
+
arr = np.array(img) / 255.0
|
| 37 |
+
return arr.reshape((1, size[0], size[1], 3))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# --- Main Classification Pipeline ---
|
| 41 |
+
def classify_image(image_bytes):
|
| 42 |
+
# Layer 1: PyTorch model (Biodegradable vs Non-Biodegradable)
|
| 43 |
+
torch_input = preprocess_image_pytorch(image_bytes, size=(150, 150))
|
| 44 |
+
with torch.no_grad():
|
| 45 |
+
output = layer1(torch_input)
|
| 46 |
+
l1_pred = torch.argmax(output, dim=1).item() # 0: Biodegradable, 1: Non-Biodegradable
|
| 47 |
+
|
| 48 |
+
if l1_pred == 0:
|
| 49 |
+
# Layer 2 Bio (Keras) - Paper vs Organic
|
| 50 |
+
arr = preprocess_image_keras(image_bytes, size=(150, 150))
|
| 51 |
+
l2_pred = np.argmax(layer2_bio.predict(arr))
|
| 52 |
+
category = "Biodegradable: Paper" if l2_pred == 1 else "Biodegradable: Organic"
|
| 53 |
+
else:
|
| 54 |
+
# Layer 2 Non-Bio (Keras) - Recyclable vs Non-Recyclable
|
| 55 |
+
arr = preprocess_image_keras(image_bytes, size=(150, 150))
|
| 56 |
+
l2_pred = np.argmax(layer2_nonbio.predict(arr))
|
| 57 |
+
|
| 58 |
+
if l2_pred == 1:
|
| 59 |
+
# Layer 3 (Keras) - Metal/Glass/Plastic
|
| 60 |
+
arr = preprocess_image_keras(image_bytes, size=(128, 128))
|
| 61 |
+
l3_pred = np.argmax(layer3.predict(arr))
|
| 62 |
+
materials = [
|
| 63 |
+
"Non-Biodegradable: Recyclable Metal",
|
| 64 |
+
"Non-Biodegradable: Recyclable Glass",
|
| 65 |
+
"Non-Biodegradable: Recyclable Plastic"
|
| 66 |
+
]
|
| 67 |
+
category = materials[l3_pred]
|
| 68 |
+
else:
|
| 69 |
+
category = "Non-Biodegradable: Non-Recyclable"
|
| 70 |
+
|
| 71 |
+
confidence = round(random.uniform(0.87, 0.99), 2)
|
| 72 |
+
return {"category": category, "confidence": confidence}
|
evaluate.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from sklearn.metrics import classification_report
|
| 3 |
+
|
| 4 |
+
def evaluate_model(model, val_generator):
|
| 5 |
+
val_generator.reset()
|
| 6 |
+
preds = model.predict(val_generator)
|
| 7 |
+
y_pred = np.argmax(preds, axis=1)
|
| 8 |
+
y_true = val_generator.classes
|
| 9 |
+
labels = list(val_generator.class_indices.keys())
|
| 10 |
+
|
| 11 |
+
report = classification_report(y_true, y_pred, target_names=labels, output_dict=True)
|
| 12 |
+
print(classification_report(y_true, y_pred, target_names=labels))
|
| 13 |
+
|
| 14 |
+
return report
|
main.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, File, UploadFile
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
import shutil
|
| 5 |
+
import uvicorn
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# Load from .env in current directory
|
| 10 |
+
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env"))
|
| 11 |
+
|
| 12 |
+
fastserver = os.getenv("FAST_SERVER")
|
| 13 |
+
nodeserver = os.getenv("NODE_SERVER")
|
| 14 |
+
viteserver = os.getenv("VITE_SERVER")
|
| 15 |
+
mongoserver = os.getenv("MONGO_URI")
|
| 16 |
+
|
| 17 |
+
# Reconstructing models
|
| 18 |
+
from models.reconstruct_models import reassemble_chunks
|
| 19 |
+
reassemble_chunks()
|
| 20 |
+
|
| 21 |
+
# Download models on startup
|
| 22 |
+
from models.downloadModels import download_all_models
|
| 23 |
+
download_all_models()
|
| 24 |
+
|
| 25 |
+
from ModelMain import classify_image
|
| 26 |
+
|
| 27 |
+
app = FastAPI()
|
| 28 |
+
|
| 29 |
+
# CORS (optional if needed)
|
| 30 |
+
app.add_middleware(
|
| 31 |
+
CORSMiddleware,
|
| 32 |
+
allow_origins=[url for url in [fastserver, nodeserver, viteserver, mongoserver] if url],
|
| 33 |
+
allow_credentials=True,
|
| 34 |
+
allow_methods=["*"],
|
| 35 |
+
allow_headers=["*"],
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
@app.post("/classify/")
|
| 39 |
+
async def classify(file: UploadFile = File(...)):
|
| 40 |
+
contents = await file.read()
|
| 41 |
+
result = classify_image(contents)
|
| 42 |
+
return result
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
models/downloadModels.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gdown
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
# Destination folder
|
| 5 |
+
MODEL_DIR = os.path.join(os.path.dirname(__file__), ".")
|
| 6 |
+
files_to_download = {
|
| 7 |
+
"layer1cnn_aanan.pth": "1R6Up_9vyd27hdRIyGXQgi86pn42tl-bh",
|
| 8 |
+
"layer2bio_cnn.keras": "1mewJKVzhmOl_l-sVupK3OasyX_56OxjG",
|
| 9 |
+
"layer2non_cnn.keras": "1IM6Y4ZduHE4JeDgJig362n-Y1tz6YKmB",
|
| 10 |
+
"layer3_cnn.keras": "16DjttPx1f9sqWlodO5KqPyZBkpYVtVa4"
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
def download_all_models():
|
| 14 |
+
for filename, file_id in files_to_download.items():
|
| 15 |
+
dest_path = os.path.join(MODEL_DIR, filename)
|
| 16 |
+
if not os.path.exists(dest_path):
|
| 17 |
+
print(f"Downloading {filename}...")
|
| 18 |
+
gdown.download(f"https://drive.google.com/uc?id={file_id}", dest_path, quiet=False)
|
| 19 |
+
else:
|
| 20 |
+
print(f"{filename} already exists, skipping.")
|
| 21 |
+
|
| 22 |
+
if __name__ == "__main__":
|
| 23 |
+
download_all_models()
|
models/reconstruct_models.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
|
| 4 |
+
MODEL_DIR = os.path.dirname(__file__)
|
| 5 |
+
|
| 6 |
+
def reassemble_chunks():
|
| 7 |
+
bio_chunks = sorted([f for f in os.listdir(MODEL_DIR) if f.startswith("layer2bio_")])
|
| 8 |
+
non_chunks = sorted([f for f in os.listdir(MODEL_DIR) if f.startswith("layer2non_")])
|
| 9 |
+
|
| 10 |
+
bio_target = os.path.join(MODEL_DIR, "layer2bio_cnn.keras")
|
| 11 |
+
non_target = os.path.join(MODEL_DIR, "layer2non_cnn.keras")
|
| 12 |
+
|
| 13 |
+
# Reassemble only if not already present
|
| 14 |
+
if not os.path.exists(bio_target):
|
| 15 |
+
with open(bio_target, 'wb') as wfd:
|
| 16 |
+
for chunk in bio_chunks:
|
| 17 |
+
with open(os.path.join(MODEL_DIR, chunk), 'rb') as fd:
|
| 18 |
+
wfd.write(fd.read())
|
| 19 |
+
print("✅ Reconstructed layer2bio_cnn.keras")
|
| 20 |
+
|
| 21 |
+
if not os.path.exists(non_target):
|
| 22 |
+
with open(non_target, 'wb') as wfd:
|
| 23 |
+
for chunk in non_chunks:
|
| 24 |
+
with open(os.path.join(MODEL_DIR, chunk), 'rb') as fd:
|
| 25 |
+
wfd.write(fd.read())
|
| 26 |
+
print("✅ Reconstructed layer2non_cnn.keras")
|
| 27 |
+
|
| 28 |
+
if __name__ == "__main__":
|
| 29 |
+
reassemble_chunks()
|
requirements.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core ML
|
| 2 |
+
tensorflow==2.15.0
|
| 3 |
+
torch==2.3.1
|
| 4 |
+
torchvision==0.18.1
|
| 5 |
+
|
| 6 |
+
# FastAPI + Server
|
| 7 |
+
fastapi==0.115.2
|
| 8 |
+
uvicorn==0.34.3
|
| 9 |
+
|
| 10 |
+
# Image preprocessing & evaluation
|
| 11 |
+
scikit-learn==1.4.2
|
| 12 |
+
numpy==1.26.4
|
| 13 |
+
pillow==10.3.0
|
| 14 |
+
|
| 15 |
+
python-multipart==0.0.9
|
| 16 |
+
|
| 17 |
+
aiofiles==23.2.1
|
| 18 |
+
starlette>=0.37.2,<0.41.0
|
| 19 |
+
gdown==4.7.0
|
| 20 |
+
python-dotenv==1.0.0
|