xray / model.py
yamxxx1's picture
Rename app.py to model.py
5988780 verified
Raw
History Blame Contribute Delete
11 kB
from fastapi import FastAPI, File, UploadFile, HTTPException
import torch
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
import os
from uuid import uuid4
from ultralytics import YOLO
import logging
import glob
import ultralytics
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import cv2
from fastapi.staticfiles import StaticFiles
import traceback
import json
app = FastAPI()
# Configure logging with more details
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Enhanced CORS configuration
origins = [
"http://localhost",
"http://localhost:8000",
"http://127.0.0.1",
"http://127.0.0.1:8000",
"http://localhost:5500",
"http://127.0.0.1:5500", # Common port for live server extensions
"*" # Allow all origins for testing purposes
]
# Configure CORS middleware with more options
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"]
)
# Create directories for results if they don't exist
os.makedirs("uploads", exist_ok=True)
os.makedirs("results", exist_ok=True)
os.makedirs("results/explanations", exist_ok=True)
os.makedirs("results/gradcam", exist_ok=True)
# Load YOLOv8 model
model = None
@app.on_event("startup")
async def load_model():
global model
try:
logger.info("Loading YOLOv8 model...")
logger.info(f"Ultralytics version: {ultralytics.__version__}")
model = YOLO('./Final.pt')
logger.info("Model loaded successfully.")
except Exception as e:
logger.error(f"Error loading model: {e}")
logger.error(traceback.format_exc())
raise e
# Mount the static directories
app.mount("/results", StaticFiles(directory="results"), name="results")
# Also mount the uploads directory for debugging purposes
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
# File to store the analysis count
ANALYSIS_COUNT_FILE = "analysis_count.json"
def get_analysis_count():
if os.path.exists(ANALYSIS_COUNT_FILE):
with open(ANALYSIS_COUNT_FILE, "r") as f:
return json.load(f).get("count", 0)
return 0
def increment_analysis_count():
count = get_analysis_count()
count += 1
with open(ANALYSIS_COUNT_FILE, "w") as f:
json.dump({"count": count}, f)
return count
@app.get("/")
async def root():
return {"message": "Welcome to the YOLOv8 detection API!"}
@app.get("/status")
async def get_status():
return {"message": "Server is running", "status": "success"}
# Add this health check route for troubleshooting
@app.get("/health")
async def health_check():
return {"status": "healthy", "message": "API is running properly"}
@app.get("/total_analyses")
async def get_total_analyses():
return {"total_analyses": get_analysis_count()}
@app.post("/detect")
async def detect(file: UploadFile = File(...)):
# Check if file is an image
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="File provided is not an image")
# Increment analysis count
increment_analysis_count()
# Create a unique ID for this detection
detection_id = str(uuid4())
# Create paths for saving
upload_folder = "uploads"
os.makedirs(upload_folder, exist_ok=True)
file_extension = os.path.splitext(file.filename)[1]
input_file_path = f"{upload_folder}/{detection_id}{file_extension}"
# Save the uploaded file
with open(input_file_path, "wb") as buffer:
buffer.write(await file.read())
try:
# Load image with PIL for processing
image = Image.open(input_file_path).convert("RGB")
image_array = np.array(image)
# Run YOLOv8 inference
results = model(image_array)
# Save the visualization image
output_file_path = f"results/{detection_id}_result.jpg"
results_plotted = results[0].plot()
cv2.imwrite(output_file_path, results_plotted)
# Extract detection information
detections = []
detection_boxes = []
for result in results:
boxes = result.boxes.cpu().numpy()
for i, box in enumerate(boxes):
x1, y1, x2, y2 = box.xyxy[0]
confidence = box.conf[0]
if confidence > 0.5:
class_id = int(box.cls[0])
class_name = result.names[class_id]
detection_boxes.append([x1, y1, x2, y2])
detections.append({
"id": i,
"class": class_name,
"confidence": float(confidence),
"box": {
"x1": float(x1),
"y1": float(y1),
"x2": float(x2),
"y2": float(y2)
}
})
# Generate simple explanation by highlighting the detection areas
explanation_file_path = None
if len(detection_boxes) > 0:
# Create a copy of the original image for the explanation
explanation_img = image_array.copy()
# Draw rectangles for the detections
for box in detection_boxes:
x1, y1, x2, y2 = [int(coord) for coord in box]
cv2.rectangle(explanation_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
# Add semi-transparent red highlight around the fracture area
highlight = np.zeros_like(explanation_img, dtype=np.uint8)
# Create a slightly larger box for highlight area
pad = 10
cv2.rectangle(highlight, (max(0, x1-pad), max(0, y1-pad)),
(min(explanation_img.shape[1], x2+pad), min(explanation_img.shape[0], y2+pad)),
(0, 0, 255), -1)
# Apply highlight with transparency
explanation_img = cv2.addWeighted(explanation_img, 1, highlight, 0.3, 0)
explanation_file_path = f"results/explanations/{detection_id}_explanation.jpg"
cv2.imwrite(explanation_file_path, explanation_img)
# Generate Grad-CAM visualization
gradcam_file_path = None
if len(detection_boxes) > 0:
# Generate Grad-CAM heatmap
gradcam_img = generate_gradcam(image_array, detection_boxes, results[0])
gradcam_file_path = f"results/gradcam/{detection_id}_gradcam.jpg"
cv2.imwrite(gradcam_file_path, gradcam_img)
return {
"detection_id": detection_id,
"message": "Detection completed successfully",
"result_image": f"/results/{detection_id}_result.jpg",
"explanation_image": f"/results/explanations/{detection_id}_explanation.jpg" if explanation_file_path else None,
"gradcam_image": f"/results/gradcam/{detection_id}_gradcam.jpg" if gradcam_file_path else None,
"detections": detections
}
except Exception as e:
logger.error(f"Error during detection: {str(e)}")
logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail=f"Error during detection: {str(e)}")
def generate_gradcam(image, boxes, result):
"""
Generate a Grad-CAM visualization for the detected fractures.
This is a simplified implementation - in a production environment, you would use
actual gradients from the model's final convolutional layer.
"""
# Create a copy of the image for visualization
vis_img = image.copy()
# Convert to RGB if grayscale
if len(vis_img.shape) == 2:
vis_img = cv2.cvtColor(vis_img, cv2.COLOR_GRAY2RGB)
elif vis_img.shape[2] == 1:
vis_img = cv2.cvtColor(vis_img, cv2.COLOR_GRAY2RGB)
# Create a heatmap overlay
height, width = vis_img.shape[:2]
heatmap = np.zeros((height, width), dtype=np.float32)
# For each detection, add a gaussian blob to the heatmap centered on the detection
for box in boxes:
x1, y1, x2, y2 = [int(coord) for coord in box]
# Calculate center of the box
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
# Box dimensions
box_width = x2 - x1
box_height = y2 - y1
# Create a gaussian blob around the center
y, x = np.ogrid[:height, :width]
# Adjust sigma based on box size
sigma_x = box_width / 6 # Cover the full box width with 3 sigma
sigma_y = box_height / 6
# Ensure sigma is not too small
sigma_x = max(sigma_x, 10)
sigma_y = max(sigma_y, 10)
# Generate gaussian
gaussian = np.exp(-(
((x - center_x) ** 2) / (2 * sigma_x ** 2) +
((y - center_y) ** 2) / (2 * sigma_y ** 2)
))
# Add to heatmap
heatmap = np.maximum(heatmap, gaussian)
# Normalize heatmap
heatmap = np.uint8(255 * heatmap)
# Apply colormap to create a visualization
heatmap_colored = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
# Overlay the heatmap on the image with transparency
alpha = 0.4
gradcam_visualization = cv2.addWeighted(vis_img, 1 - alpha, heatmap_colored, alpha, 0)
# Draw the detection boxes
for box in boxes:
x1, y1, x2, y2 = [int(coord) for coord in box]
cv2.rectangle(gradcam_visualization, (x1, y1), (x2, y2), (0, 255, 0), 2)
return gradcam_visualization
@app.get("/gradcam/{image_id}")
async def get_gradcam(image_id: str):
"""
Generate custom Grad-CAM visualization for a previously detected image
"""
# Find the original result image
result_path = f"results/{image_id}_result.jpg"
if not os.path.exists(result_path):
raise HTTPException(status_code=404, detail="Image not found")
# Generate path for Grad-CAM
gradcam_path = f"results/gradcam/{image_id}_gradcam.jpg"
# If already exists, return it
if os.path.exists(gradcam_path):
return FileResponse(gradcam_path)
# Otherwise, would typically regenerate it here
# For this example, we'll return an error if it doesn't exist
raise HTTPException(status_code=404, detail="Grad-CAM not available for this image")