ALYYAN commited on
Commit
00f3d54
·
1 Parent(s): 80019a3

model dir changed

Browse files
Files changed (2) hide show
  1. app.py +36 -31
  2. src/cnnClassifier/pipeline/prediction.py +22 -14
app.py CHANGED
@@ -1,18 +1,26 @@
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
10
- import sys # <-- Add this import
11
- from pathlib import Path # <-- Add this import
 
12
 
 
 
 
13
  sys.path.append(str(Path(__file__).parent / "src"))
 
14
 
15
- # Your existing ML components
16
  from cnnClassifier.utils.common import decodeImage
17
  from cnnClassifier.pipeline.prediction import PredictionPipeline
18
 
@@ -26,8 +34,14 @@ app = FastAPI(
26
  description="An API to predict whether a chest CT scan shows signs of adenocarcinoma cancer."
27
  )
28
 
 
 
 
 
 
 
29
  # --- MIDDLEWARE (for CORS) ---
30
- # This is the FastAPI equivalent of Flask-CORS
31
  app.add_middleware(
32
  CORSMiddleware,
33
  allow_origins=["*"],
@@ -37,39 +51,25 @@ app.add_middleware(
37
  )
38
 
39
  # --- MOUNT STATIC FILES AND TEMPLATES ---
40
- # This is how FastAPI serves your CSS, JS, and HTML files
41
  app.mount("/static", StaticFiles(directory="static"), name="static")
42
  templates = Jinja2Templates(directory="templates")
43
 
44
-
45
  # --- LOAD THE PREDICTION PIPELINE ON STARTUP ---
46
- # This ensures the model is loaded only once when the application starts.
47
  classifier = PredictionPipeline(filename="inputImage.jpg")
48
 
49
-
50
  # --- DEFINE THE REQUEST BODY STRUCTURE ---
51
- # Pydantic model for automatic validation of the incoming JSON
52
  class ImagePayload(BaseModel):
53
  image: str
54
 
55
-
56
  # --- API ENDPOINTS ---
57
-
58
  @app.get("/", response_class=HTMLResponse)
59
  async def home(request: Request):
60
- """
61
- Renders the main user interface (index.html).
62
- """
63
  return templates.TemplateResponse("index.html", {"request": request})
64
 
65
-
66
  @app.post("/train")
67
  async def trainRoute():
68
- """
69
- Triggers the DVC pipeline to retrain the model.
70
- NOTE: This is a blocking operation and not recommended for a real-world, high-traffic production server.
71
- It's suitable for this project's demonstration purposes.
72
- """
73
  os.system("dvc repro")
74
  return {"message": "Training done successfully!"}
75
 
@@ -77,26 +77,31 @@ async def trainRoute():
77
  @app.post("/predict")
78
  async def predictRoute(payload: ImagePayload):
79
  """
80
- Accepts a base64 encoded image, saves it, runs prediction, and returns the result.
 
81
  """
82
- # 1. Decode the image and save it
83
- decodeImage(payload.image, "inputImage.jpg")
84
-
85
- # 2. Run the prediction pipeline
 
 
 
 
 
86
  prediction_value = classifier.predict()
 
87
 
88
  # 3. Translate the numeric prediction into a human-readable string
89
- # Based on your confirmed class indices: {'adenocarcinoma': 0, 'normal': 1}
90
  if prediction_value == 1:
91
  prediction_text = "Normal"
92
- else: # The value was 0
93
  prediction_text = "Cancer"
94
 
95
- # 4. Return the result. FastAPI handles the JSON conversion.
96
  return [{"prediction": prediction_text}]
97
 
98
-
99
  # --- RUN THE APP ---
100
- # This block is for local development. Gunicorn/Uvicorn will run the app in production.
101
  if __name__ == "__main__":
 
102
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  # --- IMPORTS ---
2
  import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
  from fastapi import FastAPI, Request
7
  from fastapi.responses import HTMLResponse
8
  from fastapi.staticfiles import StaticFiles
9
  from fastapi.templating import Jinja2Templates
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel
12
+ import uvicorn
13
+
14
+ # NEW, CRITICAL IMPORT for running behind a proxy
15
+ from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
16
 
17
+ # --- ADD THIS BLOCK TO FIX 'ModuleNotFoundError' ---
18
+ # This adds the 'src' directory to the Python path
19
+ # so it can find the cnnClassifier package in the Docker container.
20
  sys.path.append(str(Path(__file__).parent / "src"))
21
+ # ----------------------------------------------------
22
 
23
+ # Now we can import your custom ML components
24
  from cnnClassifier.utils.common import decodeImage
25
  from cnnClassifier.pipeline.prediction import PredictionPipeline
26
 
 
34
  description="An API to predict whether a chest CT scan shows signs of adenocarcinoma cancer."
35
  )
36
 
37
+ # --- ADD PROXY MIDDLEWARE (FIXES HTTPS/MIXED CONTENT ERROR) ---
38
+ # This middleware is essential for running behind a reverse proxy like Hugging Face Spaces.
39
+ # It tells the app to trust the 'x-forwarded-proto' header from the proxy.
40
+ app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
41
+ # ------------------------------------------------------------
42
+
43
  # --- MIDDLEWARE (for CORS) ---
44
+ # This should come AFTER the ProxyHeadersMiddleware
45
  app.add_middleware(
46
  CORSMiddleware,
47
  allow_origins=["*"],
 
51
  )
52
 
53
  # --- MOUNT STATIC FILES AND TEMPLATES ---
 
54
  app.mount("/static", StaticFiles(directory="static"), name="static")
55
  templates = Jinja2Templates(directory="templates")
56
 
 
57
  # --- LOAD THE PREDICTION PIPELINE ON STARTUP ---
 
58
  classifier = PredictionPipeline(filename="inputImage.jpg")
59
 
 
60
  # --- DEFINE THE REQUEST BODY STRUCTURE ---
 
61
  class ImagePayload(BaseModel):
62
  image: str
63
 
 
64
  # --- API ENDPOINTS ---
 
65
  @app.get("/", response_class=HTMLResponse)
66
  async def home(request: Request):
67
+ """Renders the main user interface (index.html)."""
 
 
68
  return templates.TemplateResponse("index.html", {"request": request})
69
 
 
70
  @app.post("/train")
71
  async def trainRoute():
72
+ """Triggers the DVC pipeline to retrain the model."""
 
 
 
 
73
  os.system("dvc repro")
74
  return {"message": "Training done successfully!"}
75
 
 
77
  @app.post("/predict")
78
  async def predictRoute(payload: ImagePayload):
79
  """
80
+ Accepts a base64 encoded image, saves it to a temporary location,
81
+ runs prediction, and returns the result.
82
  """
83
+ # --- THIS IS THE FIX ---
84
+ # Define a writable filename inside the /tmp directory.
85
+ temp_image_path = "/tmp/inputImage.jpg"
86
+
87
+ # 1. Decode the image and save it to the temporary path
88
+ decodeImage(payload.image, temp_image_path)
89
+
90
+ # 2. Update the classifier's filename to the new temporary path and predict
91
+ classifier.filename = temp_image_path
92
  prediction_value = classifier.predict()
93
+ # ----------------------
94
 
95
  # 3. Translate the numeric prediction into a human-readable string
 
96
  if prediction_value == 1:
97
  prediction_text = "Normal"
98
+ else:
99
  prediction_text = "Cancer"
100
 
101
+ # 4. Return the result
102
  return [{"prediction": prediction_text}]
103
 
 
104
  # --- RUN THE APP ---
 
105
  if __name__ == "__main__":
106
+ # Note: Hugging Face uses port 7860 by default for its apps
107
  uvicorn.run(app, host="0.0.0.0", port=7860)
src/cnnClassifier/pipeline/prediction.py CHANGED
@@ -5,32 +5,40 @@ import os
5
 
6
  class PredictionPipeline:
7
  def __init__(self, filename):
 
 
 
 
 
 
8
  self.filename = filename
 
 
 
 
 
 
 
9
 
10
  def predict(self):
11
- # --- FIX #1: LOAD THE CORRECT MODEL ---
12
- # Load the BEST model produced by your DVC pipeline.
13
- model_path = os.path.join("artifacts", "training", "best_model.h5")
14
- model = tf.keras.models.load_model(model_path)
15
-
16
- # --- Load and preprocess the image ---
17
  imagename = self.filename
18
  test_image = image.load_img(imagename, target_size=(224, 224))
19
  test_image_array = image.img_to_array(test_image)
20
 
21
- # --- FIX #2: THE CRITICAL RESCALING STEP ---
22
  # Scale the pixel values to be between 0 and 1, just like the training data.
23
  scaled_image_array = test_image_array / 255.0
24
 
25
  # Add the batch dimension
26
  input_data = np.expand_dims(scaled_image_array, axis=0)
27
 
28
- # --- Make the prediction on the CORRECTLY preprocessed image ---
29
- result_index = np.argmax(model.predict(input_data), axis=1)[0]
30
- print(f"Model predicted index: {result_index}")
31
 
32
- # --- FIX #3: RETURN THE CORRECT JSON STRUCTURE ---
33
- # The logic for translation should be in app.py to keep this pipeline clean,
34
- # but for now, we will just return the raw index.
35
- # app.py will handle translating 0/1 to "Cancer"/"Normal".
36
  return result_index
 
5
 
6
  class PredictionPipeline:
7
  def __init__(self, filename):
8
+ """
9
+ Initializes the prediction pipeline.
10
+
11
+ This is where we load the model ONCE when the application starts.
12
+ This is much more efficient than loading it for every prediction.
13
+ """
14
  self.filename = filename
15
+
16
+ # --- THIS IS THE FIX ---
17
+ # The Dockerfile places the model in a 'model' directory.
18
+ # This is the correct path inside the container.
19
+ model_path = os.path.join("model", "best_model.h5")
20
+ self.model = tf.keras.models.load_model(model_path)
21
+ # ----------------------
22
 
23
  def predict(self):
24
+ """
25
+ Performs the prediction on the image file.
26
+ It uses the model that was already loaded in the constructor.
27
+ """
28
+ # Load and preprocess the image
 
29
  imagename = self.filename
30
  test_image = image.load_img(imagename, target_size=(224, 224))
31
  test_image_array = image.img_to_array(test_image)
32
 
 
33
  # Scale the pixel values to be between 0 and 1, just like the training data.
34
  scaled_image_array = test_image_array / 255.0
35
 
36
  # Add the batch dimension
37
  input_data = np.expand_dims(scaled_image_array, axis=0)
38
 
39
+ # Make the prediction using the pre-loaded model
40
+ prediction_probs = self.model.predict(input_data)
41
+ result_index = np.argmax(prediction_probs, axis=1)
42
 
43
+ # Return the raw index (e.g., [0] or [1])
 
 
 
44
  return result_index