AdeelHassan commited on
Commit
32398f2
·
verified ·
1 Parent(s): 1763c45

Model's backend

Browse files
Files changed (8) hide show
  1. .dockerignore +5 -0
  2. Dcokerfile +9 -0
  3. README.md +157 -8
  4. app.py +102 -0
  5. inference.py +96 -0
  6. model.py +13 -0
  7. predict.py +38 -0
  8. requirements.txt +6 -0
.dockerignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ venv/
2
+ .git/
3
+ __pycache__/
4
+ *.pyc
5
+ .DS_Store
Dcokerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+ WORKDIR /app
3
+ RUN apt-get update && apt-get install -y build-essential rm -rf /var/lib/apt/lists/*
4
+ COPY requirements.txt .
5
+ RUN pip install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+ COPY . .
8
+ EXPOSE 7860
9
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Deep Detect Api
3
- emoji: 📚
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Image_Detector — FastAPI Backend 🐍
2
+
3
+ This directory contains the complete Python backend for Deep-Detect. It exposes a REST API that accepts image uploads and returns an AI vs. Real classification result using a custom-trained PyTorch CNN.
4
+
5
+ ---
6
+
7
+ ## 📁 Directory Structure
8
+
9
+ ```
10
+ Image_Detector/
11
+ ├── app.py # FastAPI application — server entry point, routes, CORS
12
+ ├── inference.py # Model loading, image preprocessing, prediction pipeline
13
+ ├── model.py # Custom CNN architecture (PyTorch nn.Module definition)
14
+ ├── predict.py # Standalone Tkinter desktop GUI for local inference
15
+ ├── requirements.txt # All Python package dependencies
16
+ ├── models/ # Trained model weights (gitignored — download separately)
17
+ │ └── custom_cnn_standalone.pt # TorchScript model (~103 MB)
18
+ └── notebooks/ # Jupyter notebooks for research and training
19
+ ├── Preprocessing.ipynb # Dataset loading, augmentation, visualization
20
+ ├── Model_training.ipynb # Full training loop with loss/accuracy tracking
21
+ ├── Model_evaluation.ipynb # Confusion matrix, ROC, per-class metrics
22
+ └── Pretrained_Models.ipynb # Experiments with ResNet50, EfficientNet, ViT
23
+ ```
24
+
25
  ---
26
+
27
+ ## ⚙️ Setup
28
+
29
+ ### 1. Create Virtual Environment
30
+
31
+ ```bash
32
+ cd Image_Detector
33
+
34
+ python -m venv venv
35
+
36
+ # Windows
37
+ .\venv\Scripts\activate
38
+
39
+ # macOS / Linux
40
+ source venv/bin/activate
41
+ ```
42
+
43
+ ### 2. Install Dependencies
44
+
45
+ ```bash
46
+ pip install -r requirements.txt
47
+ ```
48
+
49
+ **Dependencies:**
50
+ | Package | Version | Purpose |
51
+ |---|---|---|
52
+ | `fastapi` | ≥0.100.0 | REST API framework |
53
+ | `uvicorn` | ≥0.20.0 | ASGI server |
54
+ | `python-multipart` | ≥0.0.6 | File upload parsing |
55
+ | `torch` | ≥2.0.0 | PyTorch deep learning |
56
+ | `torchvision` | ≥0.15.0 | Image transforms |
57
+ | `pillow` | ≥9.5.0 | Image I/O |
58
+
59
+ ### 3. Download Model Weights
60
+
61
+ The model file `custom_cnn_standalone.pt` is too large for GitHub (103 MB > 100 MB limit). Download it from the project's [Releases](../../../releases) page and place it at:
62
+
63
+ ```
64
+ Image_Detector/models/custom_cnn_standalone.pt
65
+ ```
66
+
67
  ---
68
 
69
+ ## 🚀 Running the Server
70
+
71
+ ```bash
72
+ python app.py
73
+ ```
74
+
75
+ The server starts on **`http://0.0.0.0:8000`**. Verify it's healthy:
76
+
77
+ ```bash
78
+ curl http://localhost:8000/
79
+ ```
80
+
81
+ Expected response:
82
+ ```json
83
+ {
84
+ "status": "healthy",
85
+ "api_name": "Deep-Detect Image Classification Service",
86
+ "model_architecture": "Custom CNN Standalone (PyTorch)",
87
+ "device_running": "cpu"
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🔌 API Endpoints
94
+
95
+ ### `POST /predict`
96
+
97
+ Classifies an uploaded image as Deep-Fake or Real.
98
+
99
+ ```bash
100
+ curl -X POST http://localhost:8000/predict \
101
+ -F "file=@/path/to/image.jpg"
102
+ ```
103
+
104
+ **Success Response:**
105
+ ```json
106
+ {
107
+ "prediction": "ai",
108
+ "confidence": 94.85,
109
+ "status": "success"
110
+ }
111
+ ```
112
+
113
+ - `prediction`: `"ai"` or `"real"`
114
+ - `confidence`: percentage confidence (0–100)
115
+
116
+ ---
117
+
118
+ ## 🧠 Model Details
119
+
120
+ | Property | Value |
121
+ |---|---|
122
+ | Architecture | Custom CNN (defined in `model.py`) |
123
+ | Input Size | 224 × 224 (RGB) |
124
+ | Normalization | ImageNet mean/std |
125
+ | Output | Single logit → Sigmoid → binary probability |
126
+ | Format | TorchScript (`.pt`) — runs without class definition at load time |
127
+ | Inference Device | CPU (configurable) |
128
+
129
+ **Inference Pipeline** (see [`inference.py`](inference.py)):
130
+ 1. Load image bytes → convert to RGB PIL Image
131
+ 2. Resize to 224×224
132
+ 3. Normalize with ImageNet statistics
133
+ 4. Run forward pass through TorchScript model
134
+ 5. Apply Sigmoid to raw logit → confidence score
135
+ 6. Threshold at 0.5: `< 0.5` → `"real"`, `≥ 0.5` → `"ai"`
136
+
137
+ ---
138
+
139
+ ## 🖥️ Desktop Utility
140
+
141
+ Run local inference via a GUI without starting the API server:
142
+
143
+ ```bash
144
+ python predict.py
145
+ ```
146
+
147
+ Opens a Tkinter window where you can browse and classify local image files directly.
148
+
149
+ ---
150
+
151
+ ## 📓 Notebooks
152
+
153
+ The [`notebooks/`](notebooks/) directory contains the full ML research workflow:
154
+
155
+ | Notebook | Description |
156
+ |---|---|
157
+ | [`Preprocessing.ipynb`](notebooks/Preprocessing.ipynb) | Dataset exploration, augmentation strategy, class balance analysis |
158
+ | [`Model_training.ipynb`](notebooks/Model_training.ipynb) | Custom CNN training from scratch with loss curves and checkpointing |
159
+ | [`Model_evaluation.ipynb`](notebooks/Model_evaluation.ipynb) | Confusion matrix, ROC-AUC, precision/recall metrics |
160
+ | [`Pretrained_Models.ipynb`](notebooks/Pretrained_Models.ipynb) | Transfer learning experiments: ResNet50, EfficientNet, ViT |
app.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException, status
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.responses import JSONResponse
5
+ from inference import predict_with_confidence, device
6
+
7
+ # Configure logging
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
11
+ handlers=[
12
+ logging.StreamHandler()
13
+ ]
14
+ )
15
+ logger = logging.getLogger("deep-detect-api")
16
+
17
+ # Initialize FastAPI App
18
+ app = FastAPI(
19
+ title="Deep-Detect API",
20
+ description="Production-grade API for AI vs Real Image Detection using a custom CNN.",
21
+ version="1.0.0"
22
+ )
23
+
24
+ # Enable CORS middleware
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=["*"], # Adjust for production
28
+ allow_credentials=True,
29
+ allow_methods=["*"],
30
+ allow_headers=["*"],
31
+ )
32
+
33
+
34
+ @app.get("/", tags=["General"])
35
+ async def root():
36
+ """
37
+ Health check and system information endpoint.
38
+ """
39
+ return {
40
+ "status": "healthy",
41
+ "api_name": "Deep-Detect Image Classification Service",
42
+ "model_architecture": "Custom CNN Standalone (PyTorch)",
43
+ "device_running": str(device),
44
+ "endpoints": {
45
+ "health_check": "/",
46
+ "inference": "/predict"
47
+ }
48
+ }
49
+
50
+
51
+ @app.post("/predict", tags=["Inference"])
52
+ async def predict_image(file: UploadFile = File(...)):
53
+ """
54
+ Accepts an uploaded image file, preprocesses it, runs it through
55
+ the custom CNN, and returns whether it is Deep-Fake ('ai') or 'real'.
56
+ """
57
+ logger.info(f"Received prediction request. File: {file.filename}")
58
+
59
+ # Validate file extension
60
+ content_type = file.content_type or ""
61
+ if not (content_type.startswith("image/") or file.filename.lower().endswith((".png", ".jpg", ".jpeg"))):
62
+ logger.warning(f"Rejected invalid file format: {file.filename} (Content-Type: {content_type})")
63
+ raise HTTPException(
64
+ status_code=status.HTTP_400_BAD_REQUEST,
65
+ detail="Uploaded file must be a valid JPEG or PNG image."
66
+ )
67
+
68
+ try:
69
+ # Read image bytes
70
+ image_bytes = await file.read()
71
+ if len(image_bytes) == 0:
72
+ raise HTTPException(
73
+ status_code=status.HTTP_400_BAD_REQUEST,
74
+ detail="Uploaded file is empty."
75
+ )
76
+
77
+ # Run inference
78
+ label, confidence = predict_with_confidence(image_bytes)
79
+ logger.info(f"Prediction successful for {file.filename} -> Result: {label} (Confidence: {confidence:.2f}%)")
80
+
81
+ return {
82
+ "prediction": label,
83
+ "confidence": round(confidence, 2),
84
+ "status": "success"
85
+ }
86
+
87
+ except Exception as e:
88
+ logger.error(f"Inference pipeline error: {str(e)}", exc_info=True)
89
+ return JSONResponse(
90
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
91
+ content={
92
+ "status": "error",
93
+ "message": "Internal error processing the image. Ensure the image is not corrupted.",
94
+ "details": str(e)
95
+ }
96
+ )
97
+
98
+
99
+ if __name__ == "__main__":
100
+ import uvicorn
101
+ logger.info("Starting Deep-Detect backend server...")
102
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False)
inference.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import logging
4
+ import torch
5
+ from PIL import Image
6
+ from torchvision import transforms
7
+
8
+ # Set up logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger("inference")
11
+
12
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13
+ # Note: The folder is "models", and model is "custom_cnn_standalone.pt"
14
+ MODEL_PATH = os.path.join(BASE_DIR, "models", "custom_cnn_standalone.pt")
15
+
16
+ # GPU/CPU Device mapping
17
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
+ logger.info(f"Using device: {device} for inference.")
19
+
20
+ # Hyperparameters matching the Custom CNN Standalone training
21
+ IMG_SIZE = 224
22
+ MEAN = [0.485, 0.456, 0.406]
23
+ STD = [0.229, 0.224, 0.225]
24
+ OPTIMAL_THRESHOLD = 0.5
25
+
26
+ # Image Preprocessing Transformation Pipeline
27
+ transform = transforms.Compose(
28
+ [
29
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
30
+ transforms.ToTensor(),
31
+ transforms.Normalize(mean=MEAN, std=STD),
32
+ ]
33
+ )
34
+
35
+ # Load the compiled TorchScript model
36
+ if not os.path.exists(MODEL_PATH):
37
+ raise FileNotFoundError(f"Model file not found at: {MODEL_PATH}")
38
+
39
+ try:
40
+ logger.info(f"Loading TorchScript model from {MODEL_PATH}...")
41
+ model = torch.jit.load(MODEL_PATH, map_location=device)
42
+ model.eval()
43
+ logger.info("Model loaded successfully and set to evaluation mode.")
44
+ except Exception as e:
45
+ logger.error(f"Failed to load model: {str(e)}")
46
+ raise e
47
+
48
+
49
+ def _predict_probability(image: Image.Image) -> float:
50
+ """
51
+ Pass the preprocessed image through the loaded Custom CNN model.
52
+ Applies Sigmoid to the output logit to compute probability.
53
+ """
54
+ input_tensor = transform(image.convert("RGB")).unsqueeze(0).to(device)
55
+
56
+ with torch.no_grad():
57
+ output = model(input_tensor)
58
+ # Apply sigmoid since model outputs a single class raw logit
59
+ probability = torch.sigmoid(output).item()
60
+ return probability
61
+
62
+
63
+ def _load_image(image_source: str | bytes | Image.Image) -> Image.Image:
64
+ """
65
+ Load an image from filepath, raw bytes, or an existing PIL Image.
66
+ """
67
+ if isinstance(image_source, Image.Image):
68
+ return image_source
69
+ if isinstance(image_source, bytes):
70
+ return Image.open(io.BytesIO(image_source))
71
+ return Image.open(image_source)
72
+
73
+
74
+ def predict_label(image_source: str | bytes | Image.Image) -> str:
75
+ """
76
+ Predict if the image is 'real' or 'ai'.
77
+ """
78
+ image = _load_image(image_source)
79
+ probability = _predict_probability(image)
80
+ return "real" if probability > OPTIMAL_THRESHOLD else "ai"
81
+
82
+
83
+ def predict_with_confidence(image_source: str | bytes | Image.Image) -> tuple[str, float]:
84
+ """
85
+ Predict if the image is 'real' or 'ai' and return the confidence percentage.
86
+
87
+ If probability > 0.5: Class 1 (real). Confidence is probability * 100
88
+ If probability <= 0.5: Class 0 (ai). Confidence is (1.0 - probability) * 100
89
+ """
90
+ image = _load_image(image_source)
91
+ probability = _predict_probability(image)
92
+
93
+ if probability > OPTIMAL_THRESHOLD:
94
+ return "real", probability * 100
95
+
96
+ return "ai", (1.0 - probability) * 100
model.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from inference import predict_label
2
+
3
+ def predict(image_path: str) -> str:
4
+ """
5
+ Predict if the image is real or Deep-Fake.
6
+
7
+ Args:
8
+ image_path (str): Absolute or relative path to the image file.
9
+
10
+ Returns:
11
+ str: "ai" or "real".
12
+ """
13
+ return predict_label(image_path)
predict.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tkinter as tk
2
+ from tkinter import filedialog, Label, Button
3
+ from PIL import Image, ImageTk
4
+ from model import predict
5
+
6
+ root = tk.Tk()
7
+ root.title("AI vs Real Image Detector")
8
+ root.geometry("600x500")
9
+
10
+ def upload_image():
11
+ file_path = filedialog.askopenfilename(
12
+ filetypes=[("Image files", "*.jpg;*.jpeg;*.png")]
13
+ )
14
+ if not file_path:
15
+ return
16
+
17
+ # Display image
18
+ img = Image.open(file_path)
19
+ img_resized = img.resize((300, 300))
20
+ img_tk = ImageTk.PhotoImage(img_resized)
21
+ label_image.config(image=img_tk)
22
+ label_image.image = img_tk
23
+
24
+ # Predict
25
+ label = predict(file_path)
26
+ result_text = "Deep-Fake" if label.lower() in ("fake", "ai") else "Real"
27
+ label_result.config(text=f"Prediction: {result_text}")
28
+
29
+ btn_upload = Button(root, text="Upload Image", command=upload_image)
30
+ btn_upload.pack(pady=20)
31
+
32
+ label_image = Label(root)
33
+ label_image.pack()
34
+
35
+ label_result = Label(root, text="Prediction: ", font=("Arial", 16))
36
+ label_result.pack(pady=20)
37
+
38
+ root.mainloop()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi>=0.100.0
2
+ uvicorn>=0.20.0
3
+ python-multipart>=0.0.6
4
+ torch>=2.0.0
5
+ torchvision>=0.15.0
6
+ pillow>=9.5.0