ALYYAN commited on
Commit
4ce9bb0
·
1 Parent(s): f3432dc

feat: Final working version with FastAPI deployment

Browse files
Files changed (5) hide show
  1. README.md +9 -8
  2. app.py +83 -40
  3. dockerfile +1 -7
  4. requirements.txt +0 -0
  5. templates/index.html +2 -2
README.md CHANGED
@@ -64,17 +64,18 @@ The project follows a structured MLOps workflow, which is fully automated by the
64
 
65
  ```mermaid
66
  graph TD
67
- A["Start: Push Code to GitHub"] --> B{"GitHub Actions CI/CD"};
68
- B --> C["CI: Install Dependencies & Run Tests"];
69
- C -->|Success| D["CD: Deploy to Hugging Face"];
70
- D --> E["🚀 Live Application"];
71
 
72
  subgraph "DVC & MLflow Cycle (Local/Remote)"
73
- F["1. `dvc repro`"] --> G["2. Pull Data (DVC)"];
74
- G --> H["3. Train Model"];
75
- H --> I["4. Log Metrics & Model (MLflow)"];
76
- I --> J["5. Push Model (DVC)"];
77
  end
 
78
 
79
  ## ⚙️ Getting Started - Local Setup
80
 
 
64
 
65
  ```mermaid
66
  graph TD
67
+ A[Start: Push Code to GitHub] --> B{GitHub Actions CI/CD};
68
+ B --> C[CI: Install Dependencies & Run Tests];
69
+ C -->|Success| D[CD: Deploy to Hugging Face];
70
+ D --> E[🚀 Live Application];
71
 
72
  subgraph "DVC & MLflow Cycle (Local/Remote)"
73
+ F[1. `dvc repro`] --> G[2. Pull Data (DVC)];
74
+ G --> H[3. Train Model];
75
+ H --> I[4. Log Metrics & Model (MLflow)];
76
+ I --> J[5. Push Model (DVC)];
77
  end
78
+ ```
79
 
80
  ## ⚙️ Getting Started - Local Setup
81
 
app.py CHANGED
@@ -1,55 +1,98 @@
1
- from flask import Flask, request, jsonify, render_template
2
  import os
3
- from flask_cors import CORS, cross_origin
 
 
 
 
 
 
 
 
4
  from cnnClassifier.utils.common import decodeImage
5
  from cnnClassifier.pipeline.prediction import PredictionPipeline
6
 
7
- # Set environment variables for consistent encoding
8
  os.putenv('LANG', 'en_US.UTF-8')
9
  os.putenv('LC_ALL', 'en_US.UTF-8')
10
 
11
- app = Flask(__name__)
12
- CORS(app)
13
-
14
- class ClientApp:
15
- def __init__(self):
16
- self.filename = "inputImage.jpg"
17
- self.classifier = PredictionPipeline(self.filename)
18
-
19
- @app.route("/", methods=['GET'])
20
- @cross_origin()
21
- def home():
22
- """Renders the main user interface."""
23
- return render_template('index.html')
24
-
25
- @app.route("/train", methods=['GET','POST'])
26
- @cross_origin()
27
- def trainRoute():
28
- """Triggers the DVC pipeline to retrain the model."""
29
- # os.system("python main.py") # You can use this if you have a main orchestrator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  os.system("dvc repro")
31
- return "Training done successfully!"
32
-
33
- @app.route("/predict", methods=['POST'])
34
- @cross_origin()
35
- def predictRoute():
36
- image = request.json['image']
37
- decodeImage(image, clApp.filename)
38
-
39
- # The predict() method now returns just the index (0 or 1)
40
- prediction_value = clApp.classifier.predict()
41
-
42
- # This logic is confirmed by your class indices: {'adenocarcinoma': 0, 'normal': 1}
 
 
 
 
43
  if prediction_value == 1:
44
  prediction_text = "Normal"
45
- else: # The value was 0
46
  prediction_text = "Cancer"
47
 
48
- # The front-end expects the key "prediction"
49
- return jsonify([{"prediction": prediction_text}])
50
 
51
 
 
 
52
  if __name__ == "__main__":
53
- clApp = ClientApp()
54
- # Run the app on all available interfaces (for Docker/deployment) and port 8080
55
- app.run(host='0.0.0.0', port=8080)
 
1
+ # --- IMPORTS ---
2
  import os
3
+ from fastapi import FastAPI, Request
4
+ from fastapi.responses import HTMLResponse
5
+ from fastapi.staticfiles import StaticFiles
6
+ from fastapi.templating import Jinja2Templates
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from pydantic import BaseModel
9
+ import uvicorn # For running the app directly if needed
10
+
11
+ # Your existing ML components
12
  from cnnClassifier.utils.common import decodeImage
13
  from cnnClassifier.pipeline.prediction import PredictionPipeline
14
 
15
+ # --- CONFIGURATION ---
16
  os.putenv('LANG', 'en_US.UTF-8')
17
  os.putenv('LC_ALL', 'en_US.UTF-8')
18
 
19
+ # --- INITIALIZE FastAPI APP ---
20
+ app = FastAPI(
21
+ title="Chest Cancer Classification API",
22
+ description="An API to predict whether a chest CT scan shows signs of adenocarcinoma cancer."
23
+ )
24
+
25
+ # --- MIDDLEWARE (for CORS) ---
26
+ # This is the FastAPI equivalent of Flask-CORS
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"],
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ # --- MOUNT STATIC FILES AND TEMPLATES ---
36
+ # This is how FastAPI serves your CSS, JS, and HTML files
37
+ app.mount("/static", StaticFiles(directory="static"), name="static")
38
+ templates = Jinja2Templates(directory="templates")
39
+
40
+
41
+ # --- LOAD THE PREDICTION PIPELINE ON STARTUP ---
42
+ # This ensures the model is loaded only once when the application starts.
43
+ classifier = PredictionPipeline(filename="inputImage.jpg")
44
+
45
+
46
+ # --- DEFINE THE REQUEST BODY STRUCTURE ---
47
+ # Pydantic model for automatic validation of the incoming JSON
48
+ class ImagePayload(BaseModel):
49
+ image: str
50
+
51
+
52
+ # --- API ENDPOINTS ---
53
+
54
+ @app.get("/", response_class=HTMLResponse)
55
+ async def home(request: Request):
56
+ """
57
+ Renders the main user interface (index.html).
58
+ """
59
+ return templates.TemplateResponse("index.html", {"request": request})
60
+
61
+
62
+ @app.post("/train")
63
+ async def trainRoute():
64
+ """
65
+ Triggers the DVC pipeline to retrain the model.
66
+ NOTE: This is a blocking operation and not recommended for a real-world, high-traffic production server.
67
+ It's suitable for this project's demonstration purposes.
68
+ """
69
  os.system("dvc repro")
70
+ return {"message": "Training done successfully!"}
71
+
72
+
73
+ @app.post("/predict")
74
+ async def predictRoute(payload: ImagePayload):
75
+ """
76
+ Accepts a base64 encoded image, saves it, runs prediction, and returns the result.
77
+ """
78
+ # 1. Decode the image and save it
79
+ decodeImage(payload.image, "inputImage.jpg")
80
+
81
+ # 2. Run the prediction pipeline
82
+ prediction_value = classifier.predict()
83
+
84
+ # 3. Translate the numeric prediction into a human-readable string
85
+ # Based on your confirmed class indices: {'adenocarcinoma': 0, 'normal': 1}
86
  if prediction_value == 1:
87
  prediction_text = "Normal"
88
+ else: # The value was 0
89
  prediction_text = "Cancer"
90
 
91
+ # 4. Return the result. FastAPI handles the JSON conversion.
92
+ return [{"prediction": prediction_text}]
93
 
94
 
95
+ # --- RUN THE APP ---
96
+ # This block is for local development. Gunicorn/Uvicorn will run the app in production.
97
  if __name__ == "__main__":
98
+ uvicorn.run(app, host="0.0.0.0", port=8001)
 
 
dockerfile CHANGED
@@ -1,13 +1,7 @@
1
  FROM python:3.8-slim
2
-
3
  WORKDIR /app
4
-
5
  COPY requirements.txt .
6
-
7
  RUN pip install --no-cache-dir -r requirements.txt
8
-
9
  COPY . .
10
-
11
  EXPOSE 8080
12
-
13
- CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]
 
1
  FROM python:3.8-slim
 
2
  WORKDIR /app
 
3
  COPY requirements.txt .
 
4
  RUN pip install --no-cache-dir -r requirements.txt
 
5
  COPY . .
 
6
  EXPOSE 8080
7
+ CMD [" pip install uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
 
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
templates/index.html CHANGED
@@ -17,7 +17,7 @@
17
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
18
 
19
  <!-- Your Custom CSS -->
20
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
21
  </head>
22
  <body>
23
 
@@ -93,6 +93,6 @@
93
  <!-- Bootstrap 5 JS -->
94
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
95
  <!-- Your Custom JS -->
96
- <script src="{{ url_for('static', filename='script.js') }}"></script>
97
  </body>
98
  </html>
 
17
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
18
 
19
  <!-- Your Custom CSS -->
20
+ <link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
21
  </head>
22
  <body>
23
 
 
93
  <!-- Bootstrap 5 JS -->
94
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
95
  <!-- Your Custom JS -->
96
+ <script src="{{ url_for('static', path='script.js') }}"></script>
97
  </body>
98
  </html>