smonizzzz commited on
Commit
bfae9aa
·
verified ·
1 Parent(s): faa4320

Upload server.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. server.py +131 -0
server.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API REST — Pipeline de Escoliose
3
+ Endpoint: POST /analyze
4
+
5
+ Uso:
6
+ uvicorn server:app --host 0.0.0.0 --port 8000
7
+ """
8
+
9
+ import time
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import numpy as np
14
+ import torch
15
+ import cv2
16
+ import requests
17
+ import albumentations as A
18
+ from albumentations.pytorch import ToTensorV2
19
+ from fastapi import FastAPI, HTTPException
20
+ from fastapi.middleware.cors import CORSMiddleware
21
+ from pydantic import BaseModel
22
+
23
+ sys.path.insert(0, str(Path(__file__).parent))
24
+ from models.hrnet import build_model
25
+ from data.dataset import IMAGE_SIZE
26
+
27
+ # ── Configuração ──────────────────────────────────────────────────────────────
28
+
29
+ CKPT_PATH = "results/scoliosis_hrnet/best.pth"
30
+
31
+ TRANSFORM = A.Compose([
32
+ A.CLAHE(clip_limit=3.0, p=1.0),
33
+ A.Resize(IMAGE_SIZE, IMAGE_SIZE),
34
+ A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
35
+ ToTensorV2(),
36
+ ])
37
+
38
+ # ── Carregar modelo uma vez no arranque ───────────────────────────────────────
39
+
40
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
41
+ model = build_model(arch="hrnet", num_outputs=3)
42
+ ckpt = torch.load(CKPT_PATH, map_location=device, weights_only=False)
43
+ model.load_state_dict(ckpt["model"])
44
+ model = model.to(device).eval()
45
+ print(f"[server] Modelo carregado em {device}")
46
+
47
+ # ── App ───────────────────────────────────────────────────────────────────────
48
+
49
+ app = FastAPI(title="Scoliosis Analysis API", version="1.0.0")
50
+
51
+ app.add_middleware(
52
+ CORSMiddleware,
53
+ allow_origins=["*"],
54
+ allow_methods=["POST", "GET"],
55
+ allow_headers=["*"],
56
+ )
57
+
58
+ # ── Schemas ───────────────────────────────────────────────────────────────────
59
+
60
+ class AnalyzeRequest(BaseModel):
61
+ estudoId: str
62
+ imageUrl: str
63
+
64
+ class CobbAngles(BaseModel):
65
+ thoracic_proximal: float
66
+ thoracic_main: float
67
+ lumbar: float
68
+
69
+ class AnalyzeResponse(BaseModel):
70
+ estudoId: str
71
+ cobb_angles: CobbAngles
72
+ max_angle: float
73
+ classification: str
74
+ processing_time_ms: float
75
+
76
+ # ── Helpers ───────────────────────────────────────────────────────────────────
77
+
78
+ def classify(angle: float) -> str:
79
+ if angle < 10: return "NORMAL"
80
+ if angle < 25: return "LEVE"
81
+ if angle < 40: return "MODERADA"
82
+ return "GRAVE"
83
+
84
+
85
+ def download_image(url: str) -> np.ndarray:
86
+ try:
87
+ resp = requests.get(url, timeout=15)
88
+ resp.raise_for_status()
89
+ except requests.RequestException as e:
90
+ raise HTTPException(status_code=400, detail=f"Erro ao descarregar imagem: {e}")
91
+
92
+ arr = np.frombuffer(resp.content, np.uint8)
93
+ img = cv2.imdecode(arr, cv2.IMREAD_GRAYSCALE)
94
+ if img is None:
95
+ raise HTTPException(status_code=400, detail="Não foi possível descodificar a imagem.")
96
+ return cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
97
+
98
+
99
+ @torch.no_grad()
100
+ def run_inference(img: np.ndarray) -> list[float]:
101
+ tensor = TRANSFORM(image=img)["image"].unsqueeze(0).to(device)
102
+ return model(tensor)[0].cpu().tolist()
103
+
104
+ # ── Endpoints ─────────────────────────────────────────────────────────────────
105
+
106
+ @app.get("/health")
107
+ def health():
108
+ return {"status": "ok", "device": str(device)}
109
+
110
+
111
+ @app.post("/analyze", response_model=AnalyzeResponse)
112
+ def analyze(body: AnalyzeRequest):
113
+ t0 = time.perf_counter()
114
+
115
+ img = download_image(body.imageUrl)
116
+ angles = run_inference(img)
117
+
118
+ t1 = time.perf_counter()
119
+ max_angle = max(angles)
120
+
121
+ return AnalyzeResponse(
122
+ estudoId = body.estudoId,
123
+ cobb_angles = CobbAngles(
124
+ thoracic_proximal = round(angles[0], 2),
125
+ thoracic_main = round(angles[1], 2),
126
+ lumbar = round(angles[2], 2),
127
+ ),
128
+ max_angle = round(max_angle, 2),
129
+ classification = classify(max_angle),
130
+ processing_time_ms = round((t1 - t0) * 1000, 1),
131
+ )