|
|
|
|
|
|
| from fastapi import FastAPI, File, UploadFile
|
| from pydantic import BaseModel
|
| from fastapi.middleware.cors import CORSMiddleware
|
| from fastapi.responses import JSONResponse
|
| import pandas as pd
|
| import numpy as np
|
| import joblib
|
| import tensorflow as tf
|
| from PIL import Image
|
| import io
|
| import os
|
|
|
|
|
| BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
|
|
|
|
|
| app = FastAPI(
|
| title="Stroke Detection API (CT + Clinical Data)",
|
| description="Deep Learning (DenseNet121) + ML Logistic Regression",
|
| version="2.0"
|
| )
|
|
|
|
|
| app.add_middleware(
|
| CORSMiddleware,
|
| allow_origins=["*"],
|
| allow_credentials=True,
|
| allow_methods=["*"],
|
| allow_headers=["*"],
|
| )
|
|
|
|
|
|
|
|
|
| logistic_model = joblib.load(os.path.join(BASE_DIR, "models", "stroke_logistic_regression_model.pkl"))
|
| preprocessor = joblib.load(os.path.join(BASE_DIR, "models", "preprocessor.pkl"))
|
| cnn_model = tf.keras.models.load_model(os.path.join(BASE_DIR, "models", "dense_final_finetuned.keras"))
|
|
|
| IMG_SIZE = (224, 224)
|
|
|
|
|
|
|
|
|
| class StrokeInput(BaseModel):
|
| age: float
|
| avg_glucose_level: float
|
| bmi: float
|
| hypertension: int
|
| heart_disease: int
|
| gender: str
|
| ever_married: str
|
| Residence_type: str
|
| work_type: str
|
| smoking_status: str
|
|
|
| class StrokeOutput(BaseModel):
|
| stroke_prediction: int
|
| stroke_probability: float
|
|
|
|
|
|
|
|
|
| def preprocess_image(image_bytes):
|
| img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
|
| img = img.resize(IMG_SIZE)
|
| img_arr = tf.keras.preprocessing.image.img_to_array(img) / 255.0
|
| img_arr = np.expand_dims(img_arr, axis=0)
|
| return img_arr
|
|
|
| def predict_image_cnn(img_tensor, threshold=0.5):
|
| prob = cnn_model.predict(img_tensor)[0][0]
|
| label = "Stroke Detected" if prob >= threshold else "Normal Brain"
|
| return label, float(prob)
|
|
|
|
|
|
|
|
|
| @app.post("/stroke-predict-struct", response_model=StrokeOutput)
|
| def predict_stroke_struct(data: StrokeInput):
|
|
|
| df = pd.DataFrame([data.dict()])
|
|
|
|
|
| df['age_glu_interaction'] = df['age'] * df['avg_glucose_level']
|
| df['ht_hd_score'] = df['hypertension'] + df['heart_disease']
|
|
|
| df['work_type_simplified'] = df['work_type'].replace({
|
| 'children': 'No_Work',
|
| 'Never_worked': 'No_Work',
|
| 'Private': 'Private',
|
| 'Self-employed': 'Self_Employed',
|
| 'Govt_job': 'Govt'
|
| })
|
|
|
| df['smoke_simplified'] = df['smoking_status'].replace({
|
| 'formerly smoked': 'Former',
|
| 'never smoked': 'Never',
|
| 'smokes': 'Smoker',
|
| 'Unknown': 'Unknown'
|
| })
|
|
|
| df['glucose_bin'] = pd.cut(
|
| df['avg_glucose_level'],
|
| bins=[0, 100, 140, np.inf],
|
| labels=['Normal', 'Prediabetic', 'High']
|
| )
|
|
|
| selected_features = [
|
| 'age','avg_glucose_level','bmi','age_glu_interaction',
|
| 'hypertension','heart_disease','ht_hd_score',
|
| 'gender','ever_married','Residence_type',
|
| 'work_type_simplified','smoke_simplified','glucose_bin'
|
| ]
|
|
|
| df = df[selected_features]
|
|
|
| processed = preprocessor.transform(df)
|
| prob = logistic_model.predict_proba(processed)[0][1]
|
| pred = logistic_model.predict(processed)[0]
|
|
|
| return {
|
| "stroke_prediction": int(pred),
|
| "stroke_probability": float(round(prob, 4))
|
| }
|
|
|
|
|
|
|
|
|
| @app.post("/stroke-predict-image")
|
| async def predict_stroke_image(file: UploadFile = File(...)):
|
| image_bytes = await file.read()
|
| img_tensor = preprocess_image(image_bytes)
|
|
|
| label, prob = predict_image_cnn(img_tensor)
|
|
|
| return JSONResponse({
|
| "filename": file.filename,
|
| "prediction": label,
|
| "confidence_score": float(round(prob, 4))
|
| }) |