Omarelrayes commited on
Commit
2f89a36
·
verified ·
1 Parent(s): 4f001c4

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +208 -125
app/main.py CHANGED
@@ -1,140 +1,223 @@
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 . import configs as config
8
- from .models import PredictionResponse, HistoryItem
9
- from .services.predictor import predict_image
10
- from .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
57
- model = config.get_classification_model()
58
- model_version = getattr(model, "name", "mlflow-production")
59
-
60
- #create history
61
- request_id = str(uuid.uuid4())
62
-
63
- history_item = {
64
- "request_id": request_id,
65
- "filename": file.filename or "image.jpg",
66
- "image_path": image_path,
67
- "prediction": prediction,
68
- "confidence": confidence,
69
- "model_version": model_version,
70
- "timestamp": datetime.now().isoformat(),
71
- "status": "classified",
72
- "segmentation_path": None
73
- }
74
-
75
- config.request_history.append(history_item)
76
-
77
- # run segmentation in background if needed
78
- if prediction == "malicious":
79
- background_tasks.add_task(
80
- run_segmentation,
81
- request_id,
82
- Path(image_path).name,
83
- image_path
84
- )
85
-
86
- return PredictionResponse(**history_item)
87
-
88
- # =========================
89
- # History
90
- # =========================
91
- @app.get("/history", response_model=List[HistoryItem])
92
- async def get_history():
93
- return [HistoryItem(**item) for item in config.request_history]
94
-
95
- @app.get("/history/{request_id}", response_model=HistoryItem)
96
- async def get_prediction(request_id: str):
97
- for item in config.request_history:
98
- if item["request_id"] == request_id:
99
- return HistoryItem(**item)
100
-
101
- raise HTTPException(status_code=404, detail="Prediction not found")
102
-
103
- # =========================
104
- # Root
105
- # =========================
106
  @app.get("/")
107
  async def root():
108
- class_ready = "READY" if config.get_classification_model() else "REQUIRED !!!!"
109
- seg_ready = " READY" if config.get_segmentation_model() else "PTIONAL !"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- return {
112
- "service": "AI Image Classification API",
113
- "classification": class_ready,
114
- "segmentation": seg_ready,
115
- "endpoint": "POST /predict",
116
- "history": f"GET /history ({len(config.request_history)} records)",
117
- "docs": "/docs"
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- # =========================
121
- # Health Check
122
- # =========================
123
- @app.get("/health")
124
- async def health():
125
  return {
126
- "status": "healthy",
127
- "predict_ready": config.get_classification_model() is not None,
128
- "total_predictions": len(config.request_history)
129
  }
130
 
131
- # =========================
132
- # Run Server
133
- # =========================
134
- if __name__ == "main":
135
  import uvicorn
136
- uvicorn.run(
137
- "app.main:app",
138
- host="0.0.0.0",
139
- port=7860
140
- )
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from fastapi.responses import JSONResponse
3
+ from typing import Dict, Any
4
+ import numpy as np
5
+ from PIL import Image
6
+ import io
7
+ import tensorflow as tf
8
+
9
+ from app.configs import (
10
+ get_classification_model,
11
+ get_segmentation_model,
12
+ model_classes,
13
+ request_history
14
+ )
15
+ from app.core.config import settings
16
 
17
  app = FastAPI(
18
+ title=settings.TITLE,
19
+ description=settings.DESCRIPTION,
20
+ version=settings.VERSION
21
  )
22
 
23
+ # ======================
24
+ # Helper Functions
25
+ # ======================
26
+ def preprocess_image(file: UploadFile, target_size=(224, 224)) -> np.ndarray:
27
+ """Preprocess image for model input"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  try:
29
+ # Read image
30
+ contents = file.file.read()
31
+ image = Image.open(io.BytesIO(contents))
32
+
33
+ # Convert to RGB if necessary
34
+ if image.mode != 'RGB':
35
+ image = image.convert('RGB')
36
+
37
+ # Resize and normalize
38
+ image = image.resize(target_size)
39
+ img_array = np.array(image, dtype=np.float32) / 255.0
40
+
41
+ # Add batch dimension
42
+ img_array = np.expand_dims(img_array, axis=0)
43
+
44
+ return img_array
45
  except Exception as e:
46
+ raise HTTPException(status_code=400, detail=f"Error processing image: {str(e)}")
47
 
48
+ # ======================
49
+ # Endpoints
50
+ # ======================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  @app.get("/")
52
  async def root():
53
+ """Health check endpoint"""
54
+ try:
55
+ class_model = get_classification_model()
56
+ seg_model = get_segmentation_model()
57
+
58
+ return {
59
+ "message": "Skin Cancer API is running!",
60
+ "classification_model": "READY" if class_model else "REQUIRED !!!!",
61
+ "segmentation_model": "READY" if seg_model else "REQUIRED !!!!",
62
+ "endpoints": {
63
+ "/classify": "POST - Classify skin cancer image",
64
+ "/segment": "POST - Segment skin cancer image",
65
+ "/docs": "GET - API documentation"
66
+ }
67
+ }
68
+ except Exception as e:
69
+ return {
70
+ "message": "Skin Cancer API is running!",
71
+ "error": str(e),
72
+ "classification_model": "ERROR",
73
+ "segmentation_model": "ERROR"
74
+ }
75
 
76
+ @app.post("/classify", response_model=Dict[str, Any])
77
+ async def classify(file: UploadFile = File(...)):
78
+ """
79
+ Classify skin cancer image
80
+
81
+ - **file**: Image file (JPG, PNG)
82
+ """
83
+ try:
84
+ # Get model
85
+ model = get_classification_model()
86
+
87
+ if model is None:
88
+ raise HTTPException(status_code=503, detail="Classification model not loaded")
89
+
90
+ # Preprocess image
91
+ img_array = preprocess_image(file, target_size=(224, 224))
92
+
93
+ # 🔥 Call SavedModel signature directly
94
+ # The signature expects 'input_1' or 'serving_default' input
95
+ if hasattr(model, 'structured_input_signature'):
96
+ # Get input tensor name from signature
97
+ input_tensor = tf.constant(img_array)
98
+ result = model(input_1=input_tensor)
99
+ else:
100
+ # Fallback: call directly
101
+ result = model(tf.constant(img_array))
102
+
103
+ # Get prediction
104
+ # SavedModel returns a dict with output keys
105
+ if isinstance(result, dict):
106
+ # Get the first output value
107
+ predictions = list(result.values())[0].numpy()
108
+ else:
109
+ predictions = result.numpy()
110
+
111
+ # Get class and confidence
112
+ predicted_class = int(np.argmax(predictions[0]))
113
+ confidence = float(np.max(predictions[0]))
114
+
115
+ # Get class name
116
+ class_name = model_classes.get(predicted_class, "unknown")
117
+
118
+ # Log request
119
+ request_history.append({
120
+ "endpoint": "/classify",
121
+ "prediction": class_name,
122
+ "confidence": confidence
123
+ })
124
+
125
+ return {
126
+ "prediction": class_name,
127
+ "class_id": predicted_class,
128
+ "confidence": confidence,
129
+ "class_probabilities": {
130
+ model_classes.get(i, "unknown"): float(prob)
131
+ for i, prob in enumerate(predictions[0])
132
+ }
133
+ }
134
+
135
+ except HTTPException:
136
+ raise
137
+ except Exception as e:
138
+ raise HTTPException(status_code=500, detail=f"Classification error: {str(e)}")
139
+
140
+ @app.post("/segment")
141
+ async def segment(file: UploadFile = File(...)):
142
+ """
143
+ Segment skin cancer image
144
+
145
+ - **file**: Image file (JPG, PNG)
146
+ """
147
+ try:
148
+ # Get model
149
+ model = get_segmentation_model()
150
+
151
+ if model is None:
152
+ raise HTTPException(status_code=503, detail="Segmentation model not loaded")
153
+
154
+ # Read and preprocess image
155
+ contents = file.file.read()
156
+ original_image = Image.open(io.BytesIO(contents))
157
+
158
+ if original_image.mode != 'RGB':
159
+ original_image = original_image.convert('RGB')
160
+
161
+ # Keep original size for output
162
+ original_size = original_image.size
163
+
164
+ # Resize for model input
165
+ img_array = np.array(original_image.resize((256, 256)), dtype=np.float32) / 255.0
166
+ img_array = np.expand_dims(img_array, axis=0)
167
+
168
+ # 🔥 Call SavedModel signature directly
169
+ if hasattr(model, 'structured_input_signature'):
170
+ result = model(input_1=tf.constant(img_array))
171
+ else:
172
+ result = model(tf.constant(img_array))
173
+
174
+ # Get segmentation mask
175
+ if isinstance(result, dict):
176
+ mask = list(result.values())[0].numpy()
177
+ else:
178
+ mask = result.numpy()
179
+
180
+ # Post-process mask
181
+ mask = mask[0] # Remove batch dimension
182
+
183
+ # Resize mask to original size
184
+ if mask.ndim == 3:
185
+ mask = mask[:, :, 0] # Take first channel if needed
186
+
187
+ mask = (mask * 255).astype(np.uint8)
188
+ mask_image = Image.fromarray(mask).resize(original_size)
189
+
190
+ # Convert to bytes
191
+ img_byte_arr = io.BytesIO()
192
+ mask_image.save(img_byte_arr, format='PNG')
193
+ img_byte_arr.seek(0)
194
+
195
+ # Log request
196
+ request_history.append({
197
+ "endpoint": "/segment",
198
+ "status": "success"
199
+ })
200
+
201
+ return {
202
+ "message": "Segmentation completed",
203
+ "output_format": "PNG",
204
+ "original_size": original_size,
205
+ "mask_size": mask_image.size
206
+ }
207
+
208
+ except HTTPException:
209
+ raise
210
+ except Exception as e:
211
+ raise HTTPException(status_code=500, detail=f"Segmentation error: {str(e)}")
212
 
213
+ @app.get("/history")
214
+ async def get_history():
215
+ """Get request history"""
 
 
216
  return {
217
+ "total_requests": len(request_history),
218
+ "history": request_history[-10:] # Last 10 requests
 
219
  }
220
 
221
+ if __name__ == "__main__":
 
 
 
222
  import uvicorn
223
+ uvicorn.run(app, host="0.0.0.0", port=7860)