Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -34,7 +34,6 @@ def get_region_tensor(device):
|
|
| 34 |
return region_map
|
| 35 |
|
| 36 |
def calculate_density(coords, edge_index):
|
| 37 |
-
# Safe Density Calculation
|
| 38 |
row, col = edge_index
|
| 39 |
dist = torch.norm(coords[row] - coords[col], dim=1)
|
| 40 |
sum_dist = torch.zeros(468, device=coords.device)
|
|
@@ -43,12 +42,11 @@ def calculate_density(coords, edge_index):
|
|
| 43 |
count.scatter_add_(0, row, torch.ones_like(dist))
|
| 44 |
mean_dist = sum_dist / (count + 1e-6)
|
| 45 |
density = 1.0 / (mean_dist + 1e-6)
|
| 46 |
-
return density.unsqueeze(1)
|
| 47 |
|
| 48 |
# ==========================================
|
| 49 |
# 2. MODEL ARCHITECTURES
|
| 50 |
# ==========================================
|
| 51 |
-
|
| 52 |
class RegionAwareExpert(nn.Module):
|
| 53 |
def __init__(self):
|
| 54 |
super().__init__()
|
|
@@ -88,7 +86,6 @@ class AnatomyLocationNet(nn.Module):
|
|
| 88 |
self.region_emb = nn.Embedding(9, 32)
|
| 89 |
self.density_proj = nn.Linear(1, 16)
|
| 90 |
self.context_proj = nn.Linear(512, 32)
|
| 91 |
-
|
| 92 |
self.neck = nn.Sequential(
|
| 93 |
nn.Linear(176, 256), nn.BatchNorm1d(256), nn.LeakyReLU(0.2), nn.Dropout(0.4),
|
| 94 |
nn.Linear(256, 128), nn.LeakyReLU(0.2), nn.Linear(128, 1)
|
|
@@ -96,9 +93,7 @@ class AnatomyLocationNet(nn.Module):
|
|
| 96 |
|
| 97 |
def forward(self, coords, pids, rids, den, ctx):
|
| 98 |
B, N, _ = coords.shape
|
| 99 |
-
# ✅ FIXED: Use repeat instead of expand for safety on all pytorch versions
|
| 100 |
c_emb = self.context_proj(ctx).unsqueeze(1).repeat(1, N, 1)
|
| 101 |
-
|
| 102 |
combined = torch.cat([self.coord_proj(coords), self.point_id_emb(pids),
|
| 103 |
self.region_emb(rids), self.density_proj(den), c_emb], dim=-1)
|
| 104 |
return self.neck(combined.view(-1, 176)).view(B, N, 1)
|
|
@@ -110,7 +105,6 @@ class GatedFusion(nn.Module):
|
|
| 110 |
self.gate_net = nn.Sequential(nn.Linear(geo_dim * 2, geo_dim // 2), nn.ReLU(), nn.Linear(geo_dim // 2, geo_dim), nn.Sigmoid())
|
| 111 |
def forward(self, x_geo, x_ctx):
|
| 112 |
ctx_adapted = self.context_adapter(x_ctx)
|
| 113 |
-
# Use repeat for safety
|
| 114 |
ctx_expanded = ctx_adapted.unsqueeze(1).repeat(1, 468, 1)
|
| 115 |
combined = torch.cat([x_geo, ctx_expanded], dim=-1)
|
| 116 |
return x_geo + (self.gate_net(combined) * ctx_expanded)
|
|
@@ -129,7 +123,6 @@ class SmartClinicalNet(nn.Module):
|
|
| 129 |
def get_features(self, data):
|
| 130 |
x, edges = data.x, data.edge_index
|
| 131 |
batch_size = data.embedding.shape[0]
|
| 132 |
-
# Fixed unsqueeze/cat logic
|
| 133 |
global_ctx = torch.cat([data.embedding, data.virt_prof], dim=1)
|
| 134 |
ids = torch.arange(468, device=x.device).repeat(batch_size)
|
| 135 |
if len(ids) > x.shape[0]: ids = ids[:x.shape[0]]
|
|
@@ -213,7 +206,7 @@ def get_top_probs(probs_array, class_list):
|
|
| 213 |
return {class_list[i]: float(f"{probs_array[i]:.4f}") for i in sorted_idxs}
|
| 214 |
|
| 215 |
# ==========================================
|
| 216 |
-
# 5. API ENDPOINT (
|
| 217 |
# ==========================================
|
| 218 |
@app.get("/")
|
| 219 |
def home():
|
|
@@ -222,14 +215,12 @@ def home():
|
|
| 222 |
@app.post("/predict")
|
| 223 |
async def predict_injections(file: UploadFile = File(...)):
|
| 224 |
try:
|
| 225 |
-
# 1. Image Processing
|
| 226 |
contents = await file.read()
|
| 227 |
nparr = np.frombuffer(contents, np.uint8)
|
| 228 |
img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
| 229 |
if img_bgr is None: return JSONResponse(status_code=400, content={"error": "Invalid image"})
|
| 230 |
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 231 |
|
| 232 |
-
# 2. Extract Features
|
| 233 |
res = mp_mesh.process(img_rgb)
|
| 234 |
if not res.multi_face_landmarks:
|
| 235 |
return JSONResponse(status_code=400, content={"error": "No face detected"})
|
|
@@ -243,16 +234,13 @@ async def predict_injections(file: UploadFile = File(...)):
|
|
| 243 |
emb = torch.tensor(faces[0].embedding).float().to(DEVICE) if faces else torch.zeros(512).to(DEVICE)
|
| 244 |
virt_prof = get_virtual_profile_norm(x_geo_norm.cpu()).to(DEVICE)
|
| 245 |
|
| 246 |
-
# 3. Prepare Inputs
|
| 247 |
data = Data(x=x_geo_norm, edge_index=GLOBAL_EDGE_INDEX, embedding=emb.unsqueeze(0), virt_prof=virt_prof.unsqueeze(0))
|
| 248 |
density = calculate_density(x_geo_norm, GLOBAL_EDGE_INDEX).unsqueeze(0).to(DEVICE)
|
| 249 |
|
| 250 |
with torch.no_grad():
|
| 251 |
-
# A. Location Finder
|
| 252 |
logits_loc = location_model(x_geo_norm.unsqueeze(0), STATIC_POINT_IDS, STATIC_REGION_IDS, density, emb.unsqueeze(0))
|
| 253 |
probs_loc = torch.sigmoid(logits_loc).squeeze().cpu().numpy()
|
| 254 |
|
| 255 |
-
# B. Attribute Expert
|
| 256 |
smart_features = backbone.get_features(data).unsqueeze(0)
|
| 257 |
coords_input = x_geo_norm.unsqueeze(0)
|
| 258 |
preds = expert_model(smart_features, coords_input, STATIC_POINT_IDS, STATIC_REGION_IDS)
|
|
@@ -262,7 +250,6 @@ async def predict_injections(file: UploadFile = File(...)):
|
|
| 262 |
prob_de = torch.softmax(preds['depth'], dim=-1).squeeze().cpu().numpy()
|
| 263 |
prob_p = torch.softmax(preds['product'], dim=-1).squeeze().cpu().numpy()
|
| 264 |
|
| 265 |
-
# 4. Build Response
|
| 266 |
h, w, _ = img_bgr.shape
|
| 267 |
pixel_coords = np.array([[p.x*w, p.y*h, p.z*w] for p in lms[:468]])
|
| 268 |
optimal_indices_list = apply_nms_indices(pixel_coords, probs_loc, spacing_mm=12.0, threshold=0.4)
|
|
@@ -293,11 +280,15 @@ async def predict_injections(file: UploadFile = File(...)):
|
|
| 293 |
}
|
| 294 |
}
|
| 295 |
all_points_list.append(pt_info)
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
return {
|
| 298 |
"status": "success",
|
| 299 |
-
"message": "SOTA
|
| 300 |
"injection_sites": all_points_list,
|
|
|
|
| 301 |
"summary": {"total_optimal": len(optimal_set)}
|
| 302 |
}
|
| 303 |
|
|
|
|
| 34 |
return region_map
|
| 35 |
|
| 36 |
def calculate_density(coords, edge_index):
|
|
|
|
| 37 |
row, col = edge_index
|
| 38 |
dist = torch.norm(coords[row] - coords[col], dim=1)
|
| 39 |
sum_dist = torch.zeros(468, device=coords.device)
|
|
|
|
| 42 |
count.scatter_add_(0, row, torch.ones_like(dist))
|
| 43 |
mean_dist = sum_dist / (count + 1e-6)
|
| 44 |
density = 1.0 / (mean_dist + 1e-6)
|
| 45 |
+
return density.unsqueeze(1)
|
| 46 |
|
| 47 |
# ==========================================
|
| 48 |
# 2. MODEL ARCHITECTURES
|
| 49 |
# ==========================================
|
|
|
|
| 50 |
class RegionAwareExpert(nn.Module):
|
| 51 |
def __init__(self):
|
| 52 |
super().__init__()
|
|
|
|
| 86 |
self.region_emb = nn.Embedding(9, 32)
|
| 87 |
self.density_proj = nn.Linear(1, 16)
|
| 88 |
self.context_proj = nn.Linear(512, 32)
|
|
|
|
| 89 |
self.neck = nn.Sequential(
|
| 90 |
nn.Linear(176, 256), nn.BatchNorm1d(256), nn.LeakyReLU(0.2), nn.Dropout(0.4),
|
| 91 |
nn.Linear(256, 128), nn.LeakyReLU(0.2), nn.Linear(128, 1)
|
|
|
|
| 93 |
|
| 94 |
def forward(self, coords, pids, rids, den, ctx):
|
| 95 |
B, N, _ = coords.shape
|
|
|
|
| 96 |
c_emb = self.context_proj(ctx).unsqueeze(1).repeat(1, N, 1)
|
|
|
|
| 97 |
combined = torch.cat([self.coord_proj(coords), self.point_id_emb(pids),
|
| 98 |
self.region_emb(rids), self.density_proj(den), c_emb], dim=-1)
|
| 99 |
return self.neck(combined.view(-1, 176)).view(B, N, 1)
|
|
|
|
| 105 |
self.gate_net = nn.Sequential(nn.Linear(geo_dim * 2, geo_dim // 2), nn.ReLU(), nn.Linear(geo_dim // 2, geo_dim), nn.Sigmoid())
|
| 106 |
def forward(self, x_geo, x_ctx):
|
| 107 |
ctx_adapted = self.context_adapter(x_ctx)
|
|
|
|
| 108 |
ctx_expanded = ctx_adapted.unsqueeze(1).repeat(1, 468, 1)
|
| 109 |
combined = torch.cat([x_geo, ctx_expanded], dim=-1)
|
| 110 |
return x_geo + (self.gate_net(combined) * ctx_expanded)
|
|
|
|
| 123 |
def get_features(self, data):
|
| 124 |
x, edges = data.x, data.edge_index
|
| 125 |
batch_size = data.embedding.shape[0]
|
|
|
|
| 126 |
global_ctx = torch.cat([data.embedding, data.virt_prof], dim=1)
|
| 127 |
ids = torch.arange(468, device=x.device).repeat(batch_size)
|
| 128 |
if len(ids) > x.shape[0]: ids = ids[:x.shape[0]]
|
|
|
|
| 206 |
return {class_list[i]: float(f"{probs_array[i]:.4f}") for i in sorted_idxs}
|
| 207 |
|
| 208 |
# ==========================================
|
| 209 |
+
# 5. API ENDPOINT (FINAL - WITH ALL COORDINATES)
|
| 210 |
# ==========================================
|
| 211 |
@app.get("/")
|
| 212 |
def home():
|
|
|
|
| 215 |
@app.post("/predict")
|
| 216 |
async def predict_injections(file: UploadFile = File(...)):
|
| 217 |
try:
|
|
|
|
| 218 |
contents = await file.read()
|
| 219 |
nparr = np.frombuffer(contents, np.uint8)
|
| 220 |
img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
| 221 |
if img_bgr is None: return JSONResponse(status_code=400, content={"error": "Invalid image"})
|
| 222 |
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 223 |
|
|
|
|
| 224 |
res = mp_mesh.process(img_rgb)
|
| 225 |
if not res.multi_face_landmarks:
|
| 226 |
return JSONResponse(status_code=400, content={"error": "No face detected"})
|
|
|
|
| 234 |
emb = torch.tensor(faces[0].embedding).float().to(DEVICE) if faces else torch.zeros(512).to(DEVICE)
|
| 235 |
virt_prof = get_virtual_profile_norm(x_geo_norm.cpu()).to(DEVICE)
|
| 236 |
|
|
|
|
| 237 |
data = Data(x=x_geo_norm, edge_index=GLOBAL_EDGE_INDEX, embedding=emb.unsqueeze(0), virt_prof=virt_prof.unsqueeze(0))
|
| 238 |
density = calculate_density(x_geo_norm, GLOBAL_EDGE_INDEX).unsqueeze(0).to(DEVICE)
|
| 239 |
|
| 240 |
with torch.no_grad():
|
|
|
|
| 241 |
logits_loc = location_model(x_geo_norm.unsqueeze(0), STATIC_POINT_IDS, STATIC_REGION_IDS, density, emb.unsqueeze(0))
|
| 242 |
probs_loc = torch.sigmoid(logits_loc).squeeze().cpu().numpy()
|
| 243 |
|
|
|
|
| 244 |
smart_features = backbone.get_features(data).unsqueeze(0)
|
| 245 |
coords_input = x_geo_norm.unsqueeze(0)
|
| 246 |
preds = expert_model(smart_features, coords_input, STATIC_POINT_IDS, STATIC_REGION_IDS)
|
|
|
|
| 250 |
prob_de = torch.softmax(preds['depth'], dim=-1).squeeze().cpu().numpy()
|
| 251 |
prob_p = torch.softmax(preds['product'], dim=-1).squeeze().cpu().numpy()
|
| 252 |
|
|
|
|
| 253 |
h, w, _ = img_bgr.shape
|
| 254 |
pixel_coords = np.array([[p.x*w, p.y*h, p.z*w] for p in lms[:468]])
|
| 255 |
optimal_indices_list = apply_nms_indices(pixel_coords, probs_loc, spacing_mm=12.0, threshold=0.4)
|
|
|
|
| 280 |
}
|
| 281 |
}
|
| 282 |
all_points_list.append(pt_info)
|
| 283 |
+
|
| 284 |
+
# ✅ BACKEND FIX: Explicitly generating the list of landmarks here
|
| 285 |
+
all_coords_list = [[float(f"{p.x:.4f}"), float(f"{p.y:.4f}")] for p in lms[:468]]
|
| 286 |
|
| 287 |
return {
|
| 288 |
"status": "success",
|
| 289 |
+
"message": "SOTA V10: All Data + Landmarks",
|
| 290 |
"injection_sites": all_points_list,
|
| 291 |
+
"all_coordinates": all_coords_list, # <--- SENDING SEPARATE LIST AGAIN
|
| 292 |
"summary": {"total_optimal": len(optimal_set)}
|
| 293 |
}
|
| 294 |
|