anilegin commited on
Commit
0bbba03
·
verified ·
1 Parent(s): 1c58c60

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +7 -16
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) # [1, 468, 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 (CRASH PROOF)
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 V9: Stable",
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