Omarelrayes commited on
Commit
b6de416
·
verified ·
1 Parent(s): 940c5a3

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +122 -166
app/main.py CHANGED
@@ -1,183 +1,139 @@
1
- # app/main.py
2
- from fastapi import FastAPI, File, UploadFile, HTTPException
3
- from fastapi.responses import JSONResponse
4
- from typing import Dict, Any
5
- import numpy as np
6
- from PIL import Image
7
- import io
8
- import tensorflow as tf
9
-
10
- from app.configs import (
11
- get_classification_model,
12
- get_segmentation_model,
13
- model_classes,
14
- request_history
15
- )
16
- from app.core.config import settings
17
 
18
  app = FastAPI(
19
- title=settings.TITLE,
20
- description=settings.DESCRIPTION,
21
- version=settings.VERSION
22
  )
23
 
24
- # ======================
25
- # Helper Functions
26
- # ======================
27
- def preprocess_image(file: UploadFile, target_size=(224, 224)) -> np.ndarray:
28
- """Preprocess image for model input"""
29
- try:
30
- contents = file.file.read()
31
- image = Image.open(io.BytesIO(contents))
32
-
33
- if image.mode != 'RGB':
34
- image = image.convert('RGB')
35
-
36
- image = image.resize(target_size)
37
- img_array = np.array(image, dtype=np.float32) / 255.0
38
- img_array = np.expand_dims(img_array, axis=0)
39
-
40
- return img_array
41
- except Exception as e:
42
- raise HTTPException(status_code=400, detail=f"Error processing image: {str(e)}")
43
 
44
- # ======================
45
- # Endpoints
46
- # ======================
47
- @app.get("/")
48
- async def root():
49
- """Health check endpoint"""
50
- try:
51
- class_model = get_classification_model()
52
- seg_model = get_segmentation_model()
53
-
54
- return {
55
- "message": "Skin Cancer API is running!",
56
- "classification_model": "READY" if class_model else "REQUIRED !!!!",
57
- "segmentation_model": "READY" if seg_model else "REQUIRED !!!!",
58
- "endpoints": {
59
- "/classify": "POST - Classify skin cancer image",
60
- "/segment": "POST - Segment skin cancer image",
61
- "/docs": "GET - API documentation"
62
- }
63
- }
64
- except Exception as e:
65
- return {
66
- "message": "Skin Cancer API is running!",
67
- "error": str(e),
68
- "classification_model": "ERROR",
69
- "segmentation_model": "ERROR"
70
  }
 
71
 
72
- @app.post("/classify", response_model=Dict[str, Any])
73
- async def classify(file: UploadFile = File(...)):
74
- """Classify skin cancer image"""
75
- try:
76
- model = get_classification_model()
77
-
78
- if model is None:
79
- raise HTTPException(status_code=503, detail="Classification model not loaded")
80
-
81
- img_array = preprocess_image(file, target_size=(224, 224))
82
-
83
- # 🔥 Call SavedModel signature
84
- result = model(tf.constant(img_array))
85
-
86
- # Get prediction
87
- if isinstance(result, dict):
88
- predictions = list(result.values())[0].numpy()
89
- else:
90
- predictions = result.numpy()
91
-
92
- predicted_class = int(np.argmax(predictions[0]))
93
- confidence = float(np.max(predictions[0]))
94
- class_name = model_classes.get(predicted_class, "unknown")
95
-
96
- request_history.append({
97
- "endpoint": "/classify",
98
- "prediction": class_name,
99
- "confidence": confidence
100
- })
101
-
102
- return {
103
- "prediction": class_name,
104
- "class_id": predicted_class,
105
- "confidence": confidence,
106
- "class_probabilities": {
107
- model_classes.get(i, "unknown"): float(prob)
108
- for i, prob in enumerate(predictions[0])
109
- }
110
- }
111
-
112
- except HTTPException:
113
- raise
114
- except Exception as e:
115
- raise HTTPException(status_code=500, detail=f"Classification error: {str(e)}")
116
 
117
- @app.post("/segment")
118
- async def segment(file: UploadFile = File(...)):
119
- """Segment skin cancer image"""
120
  try:
121
- model = get_segmentation_model()
122
-
123
- if model is None:
124
- raise HTTPException(status_code=503, detail="Segmentation model not loaded")
125
-
126
- contents = file.file.read()
127
- original_image = Image.open(io.BytesIO(contents))
128
-
129
- if original_image.mode != 'RGB':
130
- original_image = original_image.convert('RGB')
131
-
132
- original_size = original_image.size
133
- img_array = np.array(original_image.resize((256, 256)), dtype=np.float32) / 255.0
134
- img_array = np.expand_dims(img_array, axis=0)
135
-
136
- # 🔥 Call SavedModel signature
137
- result = model(tf.constant(img_array))
138
-
139
- if isinstance(result, dict):
140
- mask = list(result.values())[0].numpy()
141
- else:
142
- mask = result.numpy()
143
-
144
- mask = mask[0]
145
-
146
- if mask.ndim == 3:
147
- mask = mask[:, :, 0]
148
-
149
- mask = (mask * 255).astype(np.uint8)
150
- mask_image = Image.fromarray(mask).resize(original_size)
151
-
152
- img_byte_arr = io.BytesIO()
153
- mask_image.save(img_byte_arr, format='PNG')
154
- img_byte_arr.seek(0)
155
-
156
- request_history.append({
157
- "endpoint": "/segment",
158
- "status": "success"
159
- })
160
-
161
- return {
162
- "message": "Segmentation completed",
163
- "output_format": "PNG",
164
- "original_size": original_size,
165
- "mask_size": mask_image.size
166
- }
167
-
168
- except HTTPException:
169
- raise
170
  except Exception as e:
171
- raise HTTPException(status_code=500, detail=f"Segmentation error: {str(e)}")
172
 
173
- @app.get("/history")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  async def get_history():
175
- """Get request history"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  return {
177
- "total_requests": len(request_history),
178
- "history": request_history[-10:]
 
179
  }
180
 
 
 
 
181
  if __name__ == "__main__":
182
  import uvicorn
183
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
1
+ import uuid
2
+ from pathlib import Path
3
+ from datetime import datetime
4
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException
5
+ from typing import List
6
+
7
+ from app import configs as config
8
+ from app.models import PredictionResponse, HistoryItem
9
+ from app.services.predictor import predict_image
10
+ from app.services.segmenter import run_segmentation
 
 
 
 
 
 
11
 
12
  app = FastAPI(
13
+ title="AI Image Classification API",
14
+ version="2.0.0"
 
15
  )
16
 
17
+ # =========================
18
+ # Models Status
19
+ # =========================
20
+ @app.get("/models/status")
21
+ async def models_status():
22
+ classification_model = config.get_classification_model()
23
+ segmentation_model = config.get_segmentation_model()
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ return {
26
+ "classification_loaded": classification_model is not None,
27
+ "segmentation_loaded": segmentation_model is not None,
28
+ "storage_paths": {
29
+ "images": str(config.IMAGES_DIR),
30
+ "segments": str(config.SEGMENTS_DIR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
32
+ }
33
 
34
+ # =========================
35
+ # Prediction Endpoint
36
+ # =========================
37
+ @app.post("/predict", response_model=PredictionResponse)
38
+ async def predict(
39
+ background_tasks: BackgroundTasks,
40
+ file: UploadFile = File(...)
41
+ ):
42
+ file_bytes = await file.read()
43
+
44
+ # Validation
45
+ if len(file_bytes) > 10 * 1024 * 1024:
46
+ raise HTTPException(status_code=400, detail="File too large (10MB max)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
 
 
 
48
  try:
49
+ prediction, confidence, image_path = predict_image(
50
+ file_bytes,
51
+ file.filename or "image.jpg"
52
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  except Exception as e:
54
+ raise HTTPException(status_code=500, detail=str(e))
55
 
56
+ # Get model version
57
+ model_version = "savedmodel-v1"
58
+
59
+ # Create history
60
+ request_id = str(uuid.uuid4())
61
+
62
+ history_item = {
63
+ "request_id": request_id,
64
+ "filename": file.filename or "image.jpg",
65
+ "image_path": image_path,
66
+ "prediction": prediction,
67
+ "confidence": confidence,
68
+ "model_version": model_version,
69
+ "timestamp": datetime.now().isoformat(),
70
+ "status": "classified",
71
+ "segmentation_path": None
72
+ }
73
+
74
+ config.request_history.append(history_item)
75
+
76
+ # 🔥 Run segmentation in background if malignant
77
+ if prediction.lower() == "malignant":
78
+ background_tasks.add_task(
79
+ run_segmentation,
80
+ request_id,
81
+ Path(image_path).name,
82
+ image_path
83
+ )
84
+
85
+ return PredictionResponse(**history_item)
86
+
87
+ # =========================
88
+ # History
89
+ # =========================
90
+ @app.get("/history", response_model=List[HistoryItem])
91
  async def get_history():
92
+ return [HistoryItem(**item) for item in config.request_history]
93
+
94
+ @app.get("/history/{request_id}", response_model=HistoryItem)
95
+ async def get_prediction(request_id: str):
96
+ for item in config.request_history:
97
+ if item["request_id"] == request_id:
98
+ return HistoryItem(**item)
99
+
100
+ raise HTTPException(status_code=404, detail="Prediction not found")
101
+
102
+ # =========================
103
+ # Root
104
+ # =========================
105
+ @app.get("/")
106
+ async def root():
107
+ class_ready = "READY" if config.get_classification_model() else "REQUIRED !!!!"
108
+ seg_ready = "READY" if config.get_segmentation_model() else "OPTIONAL !"
109
+
110
+ return {
111
+ "service": "AI Image Classification API",
112
+ "classification": class_ready,
113
+ "segmentation": seg_ready,
114
+ "endpoint": "POST /predict",
115
+ "history": f"GET /history ({len(config.request_history)} records)",
116
+ "docs": "/docs"
117
+ }
118
+
119
+ # =========================
120
+ # Health Check
121
+ # =========================
122
+ @app.get("/health")
123
+ async def health():
124
  return {
125
+ "status": "healthy",
126
+ "predict_ready": config.get_classification_model() is not None,
127
+ "total_predictions": len(config.request_history)
128
  }
129
 
130
+ # =========================
131
+ # Run Server
132
+ # =========================
133
  if __name__ == "__main__":
134
  import uvicorn
135
+ uvicorn.run(
136
+ "app.main:app",
137
+ host="0.0.0.0",
138
+ port=7860
139
+ )