anilegin commited on
Commit
3060f1f
Β·
verified Β·
1 Parent(s): 490a66f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +98 -95
main.py CHANGED
@@ -9,6 +9,7 @@ from fastapi import FastAPI, File, UploadFile
9
  from fastapi.responses import JSONResponse
10
  from torch_geometric.data import Data
11
  from torch_geometric.nn import GATv2Conv, BatchNorm
 
12
  from insightface.app import FaceAnalysis
13
 
14
  # ==========================================
@@ -26,34 +27,91 @@ REGION_DATA = {
26
  }
27
 
28
  def get_region_tensor(device):
29
- # Initialize with 8 (Other)
30
  region_map = torch.full((468,), 8, dtype=torch.long, device=device)
31
  for region_id, points in REGION_DATA.items():
32
  for p in points:
33
  if p < 468: region_map[p] = region_id
34
  return region_map
35
 
 
 
 
 
 
 
 
 
36
  # ==========================================
37
  # 2. MODEL ARCHITECTURES
38
  # ==========================================
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  class GatedFusion(nn.Module):
41
  def __init__(self, geo_dim, context_dim):
42
  super().__init__()
43
  self.context_adapter = nn.Linear(context_dim, geo_dim)
44
- self.gate_net = nn.Sequential(
45
- nn.Linear(geo_dim * 2, geo_dim // 2),
46
- nn.ReLU(),
47
- nn.Linear(geo_dim // 2, geo_dim),
48
- nn.Sigmoid()
49
- )
50
  def forward(self, x_geo, x_ctx):
51
  ctx_adapted = self.context_adapter(x_ctx)
52
- num_nodes = x_geo.shape[1]
53
- ctx_expanded = ctx_adapted.expand(-1, num_nodes, -1)
54
  combined = torch.cat([x_geo, ctx_expanded], dim=-1)
55
- gate = self.gate_net(combined)
56
- return x_geo + (gate * ctx_expanded)
57
 
58
  class SmartClinicalNet(nn.Module):
59
  def __init__(self, hidden=32, heads=2):
@@ -65,67 +123,20 @@ class SmartClinicalNet(nn.Module):
65
  self.bn1 = BatchNorm(hidden * heads)
66
  self.conv2 = GATv2Conv(hidden * heads, hidden, heads=heads, concat=True)
67
  self.bn2 = BatchNorm(hidden * heads)
68
- # Note: self.head is removed/ignored for feature extraction
69
-
70
  def get_features(self, data):
71
- # Extracts the 64-dim Visual + Geometric features
72
  x, edges = data.x, data.edge_index
73
  batch_size = data.embedding.shape[0]
74
  global_ctx = torch.cat([data.embedding, data.virt_prof], dim=1).unsqueeze(1)
75
-
76
  ids = torch.arange(468, device=x.device).repeat(batch_size)
77
  if len(ids) > x.shape[0]: ids = ids[:x.shape[0]]
78
-
79
  geo_feat = self.geo_proj(torch.cat([x, self.id_emb(ids)], dim=1))
80
  geo_reshaped = geo_feat.view(batch_size, 468, -1)
81
-
82
  fused = self.fusion(geo_reshaped, global_ctx)
83
  fused_flat = fused.view(-1, 32)
84
-
85
  h = F.elu(self.bn1(self.conv1(fused_flat, edges)))
86
  h = F.elu(self.bn2(self.conv2(h, edges)))
87
- return h # [Batch*468, 64]
88
-
89
- class RegionAwareExpert(nn.Module):
90
- def __init__(self):
91
- super().__init__()
92
- # Inputs: Visual(64) + PointID(64) + RegionID(32) + Coords(32)
93
- self.visual_proj = nn.Linear(64, 64)
94
- self.point_id_emb = nn.Embedding(468, 64)
95
- self.region_emb = nn.Embedding(9, 32)
96
- self.coord_proj = nn.Linear(3, 32)
97
-
98
- self.neck = nn.Sequential(
99
- nn.Linear(192, 256),
100
- nn.BatchNorm1d(256),
101
- nn.LeakyReLU(0.2),
102
- nn.Dropout(0.3),
103
- nn.Linear(256, 128),
104
- nn.LeakyReLU(0.2)
105
- )
106
-
107
- self.head_gate = nn.Linear(128, 1) # This is now the "Location/Probability" model
108
- self.head_tech = nn.Linear(128, 3)
109
- self.head_dosage = nn.Linear(128, 8)
110
- self.head_depth = nn.Linear(128, 4)
111
- self.head_prod = nn.Linear(128, 8)
112
-
113
- def forward(self, features, coords, point_ids, region_ids):
114
- vis = self.visual_proj(features)
115
- pid = self.point_id_emb(point_ids)
116
- reg = self.region_emb(region_ids)
117
- xyz = self.coord_proj(coords)
118
-
119
- combined = torch.cat([vis, pid, reg, xyz], dim=-1)
120
- x = self.neck(combined.view(-1, 192))
121
-
122
- return {
123
- "is_injection": torch.sigmoid(self.head_gate(x)),
124
- "tech": self.head_tech(x),
125
- "dosage": self.head_dosage(x),
126
- "depth": self.head_depth(x),
127
- "product": self.head_prod(x)
128
- }
129
 
130
  # ==========================================
131
  # 3. INITIALIZATION
@@ -133,40 +144,42 @@ class RegionAwareExpert(nn.Module):
133
  app = FastAPI()
134
  DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
135
 
136
- print("πŸ”„ Initializing Clinical AI...")
137
 
138
  # 1. Analyzers
139
  mp_mesh = mp.solutions.face_mesh.FaceMesh(static_image_mode=True, max_num_faces=1)
140
  face_app = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
141
  face_app.prepare(ctx_id=0, det_size=(640, 640))
142
 
143
- # 2. Static Data (Pre-computed for speed)
144
- # Graph Edges
145
  mp_edges = mp.solutions.face_mesh.FACEMESH_TESSELATION
146
  s, t = [], []
147
  for src, dst in mp_edges:
148
  s.extend([src, dst]); t.extend([dst, src])
149
  GLOBAL_EDGE_INDEX = torch.tensor([s, t], dtype=torch.long).to(DEVICE)
150
-
151
- # Region & Point Tensors [1, 468] for batching
152
  STATIC_REGION_IDS = get_region_tensor(DEVICE).unsqueeze(0)
153
  STATIC_POINT_IDS = torch.arange(468, dtype=torch.long, device=DEVICE).unsqueeze(0)
154
 
155
- # 3. Load Models
156
  backbone = SmartClinicalNet(hidden=32, heads=2).to(DEVICE)
157
  expert_model = RegionAwareExpert().to(DEVICE)
 
158
 
159
  # Load Weights
160
  if os.path.exists("smart_clinical_model.pth"):
161
- print("πŸ“‚ Loading Backbone...")
162
- # strict=False allows us to ignore the old 'head' layer
163
  backbone.load_state_dict(torch.load("smart_clinical_model.pth", map_location=DEVICE), strict=False)
164
  backbone.eval()
 
165
 
166
  if os.path.exists("region_expert.pth"):
167
- print("πŸ“‚ Loading God Mode Expert...")
168
  expert_model.load_state_dict(torch.load("region_expert.pth", map_location=DEVICE))
169
  expert_model.eval()
 
 
 
 
 
 
170
 
171
  print("βœ… System Ready.")
172
 
@@ -174,7 +187,6 @@ print("βœ… System Ready.")
174
  # 4. HELPERS
175
  # ==========================================
176
  def get_virtual_profile_norm(x_tensor):
177
- # Calculates relative facial depth metrics
178
  NOSE=1; LIP=13; CHIN=152; L_CHEEK=234; L_JAW=172; L_EYE=33; R_EYE=263
179
  eye_dist = torch.norm(x_tensor[L_EYE] - x_tensor[R_EYE]) + 1e-6
180
  chin = (x_tensor[CHIN, 2] - x_tensor[LIP, 2]) / eye_dist
@@ -183,24 +195,18 @@ def get_virtual_profile_norm(x_tensor):
183
  return torch.tensor([chin, cheek, jaw], dtype=torch.float)
184
 
185
  def apply_nms_indices(landmarks_np, probs_np, spacing_mm=12.0, threshold=0.5):
186
- # Non-Maximum Suppression to find optimal points
187
  valid = np.where(probs_np > threshold)[0]
188
  if len(valid) == 0: return []
189
-
190
  sorted_idx = valid[np.argsort(probs_np[valid])[::-1]]
191
  keep = []
192
-
193
- # Approx pixel-to-mm scale using eyes
194
  eye_l, eye_r = landmarks_np[33], landmarks_np[263]
195
  eye_dist_px = np.linalg.norm(eye_l - eye_r)
196
  mm_per_px = 63.0 / (eye_dist_px + 1e-6)
197
  min_dist_sq = (spacing_mm / mm_per_px) ** 2
198
-
199
  while len(sorted_idx) > 0:
200
  curr = sorted_idx[0]
201
  keep.append(int(curr))
202
  if len(sorted_idx) == 1: break
203
-
204
  rest = sorted_idx[1:]
205
  dists = np.sum((landmarks_np[rest] - landmarks_np[curr])**2, axis=1)
206
  survivors = np.where(dists > min_dist_sq)[0]
@@ -212,7 +218,7 @@ def apply_nms_indices(landmarks_np, probs_np, spacing_mm=12.0, threshold=0.5):
212
  # ==========================================
213
  @app.get("/")
214
  def home():
215
- return {"message": "Region-Aware Clinical AI is Running"}
216
 
217
  @app.post("/predict")
218
  async def predict_injections(file: UploadFile = File(...)):
@@ -231,30 +237,29 @@ async def predict_injections(file: UploadFile = File(...)):
231
  lms = res.multi_face_landmarks[0].landmark
232
  coords = [[p.x, p.y, p.z] for p in lms[:468]]
233
  x_geo = torch.tensor(coords, dtype=torch.float).to(DEVICE)
234
- x_geo_norm = x_geo - x_geo.mean(dim=0) # Normalize for model
235
 
236
  faces = face_app.get(img_bgr)
237
  emb = torch.tensor(faces[0].embedding).float().to(DEVICE) if faces else torch.zeros(512).to(DEVICE)
238
  virt_prof = get_virtual_profile_norm(x_geo_norm.cpu()).to(DEVICE)
239
 
240
- # 3. Backbone Inference
241
  data = Data(x=x_geo_norm, edge_index=GLOBAL_EDGE_INDEX, embedding=emb.unsqueeze(0), virt_prof=virt_prof.unsqueeze(0))
242
 
 
 
 
243
  with torch.no_grad():
244
- # A. Get Visual Features (64-dim)
245
- smart_features = backbone.get_features(data).unsqueeze(0) # [1, 468, 64]
 
 
246
 
247
- # B. Prepare Inputs for God Mode
248
- # [1, 468, 3] Coords
249
- coords_input = x_geo_norm.unsqueeze(0)
250
-
251
- # C. Expert Inference
252
  preds = expert_model(smart_features, coords_input, STATIC_POINT_IDS, STATIC_REGION_IDS)
253
 
254
- # D. Decode Outputs
255
- # The Gate is now the "Location Probability"
256
- probs_loc = preds['is_injection'].squeeze().cpu().numpy()
257
-
258
  # Attributes
259
  prob_t = torch.softmax(preds['tech'], dim=-1).squeeze().cpu().numpy()
260
  prob_d = torch.softmax(preds['dosage'], dim=-1).squeeze().cpu().numpy()
@@ -265,12 +270,10 @@ async def predict_injections(file: UploadFile = File(...)):
265
  h, w, _ = img_bgr.shape
266
  pixel_coords = np.array([[p.x*w, p.y*h, p.z*w] for p in lms[:468]])
267
 
268
- # Use the Gate probability for NMS
269
  optimal_indices = apply_nms_indices(pixel_coords, probs_loc, spacing_mm=12.0, threshold=0.4)
270
 
271
- # 5. Formatting Response
272
- # Labels must match your training mappings exactly
273
- classes_tech = ["Bolus", "Fanning", "Microbolus"] # 0, 1, 2
274
  classes_dosage = ["0.01ml", "0.02ml", "0.05ml", "0.1ml", "0.2ml", "0.3ml", "0.5ml", "1.0ml"]
275
  classes_depth = ["Periosteal", "Subdermal", "Hypodermic", "Dermal"]
276
  classes_prod = ["XXL", "XL", "L", "M", "S", "Hydro", "Induce", "Lips"]
@@ -294,7 +297,7 @@ async def predict_injections(file: UploadFile = File(...)):
294
 
295
  return {
296
  "status": "success",
297
- "message": "expert attribute V5: ACTIVE",
298
  "summary": {
299
  "total_optimal": len(optimal_list),
300
  "max_confidence": float(probs_loc.max())
 
9
  from fastapi.responses import JSONResponse
10
  from torch_geometric.data import Data
11
  from torch_geometric.nn import GATv2Conv, BatchNorm
12
+ from torch_geometric.utils import scatter # Needed for density calculation
13
  from insightface.app import FaceAnalysis
14
 
15
  # ==========================================
 
27
  }
28
 
29
  def get_region_tensor(device):
 
30
  region_map = torch.full((468,), 8, dtype=torch.long, device=device)
31
  for region_id, points in REGION_DATA.items():
32
  for p in points:
33
  if p < 468: region_map[p] = region_id
34
  return region_map
35
 
36
+ def calculate_density(coords, edge_index):
37
+ # Calculates how "crowded" each point is (Key feature for Location Finder)
38
+ row, col = edge_index
39
+ dist = torch.norm(coords[row] - coords[col], dim=1)
40
+ mean_dist = scatter(dist, row, dim=0, reduce='mean', dim_size=468)
41
+ density = 1.0 / (mean_dist + 1e-6)
42
+ return density.unsqueeze(1) # [468, 1]
43
+
44
  # ==========================================
45
  # 2. MODEL ARCHITECTURES
46
  # ==========================================
47
 
48
+ # --- A. ATTRIBUTE EXPERT (RegionAwareExpert) ---
49
+ class RegionAwareExpert(nn.Module):
50
+ def __init__(self):
51
+ super().__init__()
52
+ self.visual_proj = nn.Linear(64, 64)
53
+ self.point_id_emb = nn.Embedding(468, 64)
54
+ self.region_emb = nn.Embedding(9, 32)
55
+ self.coord_proj = nn.Linear(3, 32)
56
+
57
+ self.neck = nn.Sequential(
58
+ nn.Linear(192, 256), nn.BatchNorm1d(256), nn.LeakyReLU(0.2), nn.Dropout(0.3),
59
+ nn.Linear(256, 128), nn.LeakyReLU(0.2)
60
+ )
61
+ # Heads
62
+ self.head_gate = nn.Linear(128, 1) # Ignored in this version (using Location Net instead)
63
+ self.head_tech = nn.Linear(128, 3)
64
+ self.head_dosage = nn.Linear(128, 8)
65
+ self.head_depth = nn.Linear(128, 4)
66
+ self.head_prod = nn.Linear(128, 8)
67
+
68
+ def forward(self, features, coords, point_ids, region_ids):
69
+ vis = self.visual_proj(features)
70
+ pid = self.point_id_emb(point_ids)
71
+ reg = self.region_emb(region_ids)
72
+ xyz = self.coord_proj(coords)
73
+ combined = torch.cat([vis, pid, reg, xyz], dim=-1)
74
+ x = self.neck(combined.view(-1, 192))
75
+ return {
76
+ "tech": self.head_tech(x),
77
+ "dosage": self.head_dosage(x),
78
+ "depth": self.head_depth(x),
79
+ "product": self.head_prod(x)
80
+ }
81
+
82
+ # --- B. LOCATION FINDER (AnatomyLocationNet) [NEW!] ---
83
+ class AnatomyLocationNet(nn.Module):
84
+ def __init__(self):
85
+ super().__init__()
86
+ self.coord_proj = nn.Linear(3, 32)
87
+ self.point_id_emb = nn.Embedding(468, 64)
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)
95
+ )
96
+
97
+ def forward(self, coords, pids, rids, den, ctx):
98
+ B, N, _ = coords.shape
99
+ c_emb = self.context_proj(ctx).expand(-1, N, -1)
100
+ combined = torch.cat([self.coord_proj(coords), self.point_id_emb(pids),
101
+ self.region_emb(rids), self.density_proj(den), c_emb], dim=-1)
102
+ return self.neck(combined.view(-1, 176)).view(B, N, 1)
103
+
104
+ # --- C. BACKBONE (SmartClinicalNet - Feature Extractor) ---
105
  class GatedFusion(nn.Module):
106
  def __init__(self, geo_dim, context_dim):
107
  super().__init__()
108
  self.context_adapter = nn.Linear(context_dim, geo_dim)
109
+ self.gate_net = nn.Sequential(nn.Linear(geo_dim * 2, geo_dim // 2), nn.ReLU(), nn.Linear(geo_dim // 2, geo_dim), nn.Sigmoid())
 
 
 
 
 
110
  def forward(self, x_geo, x_ctx):
111
  ctx_adapted = self.context_adapter(x_ctx)
112
+ ctx_expanded = ctx_adapted.expand(-1, 468, -1)
 
113
  combined = torch.cat([x_geo, ctx_expanded], dim=-1)
114
+ return x_geo + (self.gate_net(combined) * ctx_expanded)
 
115
 
116
  class SmartClinicalNet(nn.Module):
117
  def __init__(self, hidden=32, heads=2):
 
123
  self.bn1 = BatchNorm(hidden * heads)
124
  self.conv2 = GATv2Conv(hidden * heads, hidden, heads=heads, concat=True)
125
  self.bn2 = BatchNorm(hidden * heads)
126
+
 
127
  def get_features(self, data):
 
128
  x, edges = data.x, data.edge_index
129
  batch_size = data.embedding.shape[0]
130
  global_ctx = torch.cat([data.embedding, data.virt_prof], dim=1).unsqueeze(1)
 
131
  ids = torch.arange(468, device=x.device).repeat(batch_size)
132
  if len(ids) > x.shape[0]: ids = ids[:x.shape[0]]
 
133
  geo_feat = self.geo_proj(torch.cat([x, self.id_emb(ids)], dim=1))
134
  geo_reshaped = geo_feat.view(batch_size, 468, -1)
 
135
  fused = self.fusion(geo_reshaped, global_ctx)
136
  fused_flat = fused.view(-1, 32)
 
137
  h = F.elu(self.bn1(self.conv1(fused_flat, edges)))
138
  h = F.elu(self.bn2(self.conv2(h, edges)))
139
+ return h
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  # ==========================================
142
  # 3. INITIALIZATION
 
144
  app = FastAPI()
145
  DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
146
 
147
+ print("πŸ”„ Initializing SOTA Clinical System...")
148
 
149
  # 1. Analyzers
150
  mp_mesh = mp.solutions.face_mesh.FaceMesh(static_image_mode=True, max_num_faces=1)
151
  face_app = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider'])
152
  face_app.prepare(ctx_id=0, det_size=(640, 640))
153
 
154
+ # 2. Static Data
 
155
  mp_edges = mp.solutions.face_mesh.FACEMESH_TESSELATION
156
  s, t = [], []
157
  for src, dst in mp_edges:
158
  s.extend([src, dst]); t.extend([dst, src])
159
  GLOBAL_EDGE_INDEX = torch.tensor([s, t], dtype=torch.long).to(DEVICE)
 
 
160
  STATIC_REGION_IDS = get_region_tensor(DEVICE).unsqueeze(0)
161
  STATIC_POINT_IDS = torch.arange(468, dtype=torch.long, device=DEVICE).unsqueeze(0)
162
 
163
+ # 3. Load ALL 3 Models
164
  backbone = SmartClinicalNet(hidden=32, heads=2).to(DEVICE)
165
  expert_model = RegionAwareExpert().to(DEVICE)
166
+ location_model = AnatomyLocationNet().to(DEVICE) # New Model
167
 
168
  # Load Weights
169
  if os.path.exists("smart_clinical_model.pth"):
 
 
170
  backbone.load_state_dict(torch.load("smart_clinical_model.pth", map_location=DEVICE), strict=False)
171
  backbone.eval()
172
+ print("βœ… Backbone Loaded")
173
 
174
  if os.path.exists("region_expert.pth"):
 
175
  expert_model.load_state_dict(torch.load("region_expert.pth", map_location=DEVICE))
176
  expert_model.eval()
177
+ print("βœ… Attribute Expert Loaded")
178
+
179
+ if os.path.exists("anatomy_location.pth"):
180
+ location_model.load_state_dict(torch.load("anatomy_location.pth", map_location=DEVICE))
181
+ location_model.eval()
182
+ print("βœ… Location Finder Loaded")
183
 
184
  print("βœ… System Ready.")
185
 
 
187
  # 4. HELPERS
188
  # ==========================================
189
  def get_virtual_profile_norm(x_tensor):
 
190
  NOSE=1; LIP=13; CHIN=152; L_CHEEK=234; L_JAW=172; L_EYE=33; R_EYE=263
191
  eye_dist = torch.norm(x_tensor[L_EYE] - x_tensor[R_EYE]) + 1e-6
192
  chin = (x_tensor[CHIN, 2] - x_tensor[LIP, 2]) / eye_dist
 
195
  return torch.tensor([chin, cheek, jaw], dtype=torch.float)
196
 
197
  def apply_nms_indices(landmarks_np, probs_np, spacing_mm=12.0, threshold=0.5):
 
198
  valid = np.where(probs_np > threshold)[0]
199
  if len(valid) == 0: return []
 
200
  sorted_idx = valid[np.argsort(probs_np[valid])[::-1]]
201
  keep = []
 
 
202
  eye_l, eye_r = landmarks_np[33], landmarks_np[263]
203
  eye_dist_px = np.linalg.norm(eye_l - eye_r)
204
  mm_per_px = 63.0 / (eye_dist_px + 1e-6)
205
  min_dist_sq = (spacing_mm / mm_per_px) ** 2
 
206
  while len(sorted_idx) > 0:
207
  curr = sorted_idx[0]
208
  keep.append(int(curr))
209
  if len(sorted_idx) == 1: break
 
210
  rest = sorted_idx[1:]
211
  dists = np.sum((landmarks_np[rest] - landmarks_np[curr])**2, axis=1)
212
  survivors = np.where(dists > min_dist_sq)[0]
 
218
  # ==========================================
219
  @app.get("/")
220
  def home():
221
+ return {"message": "SOTA Clinical AI (Location + Attributes) V6"}
222
 
223
  @app.post("/predict")
224
  async def predict_injections(file: UploadFile = File(...)):
 
237
  lms = res.multi_face_landmarks[0].landmark
238
  coords = [[p.x, p.y, p.z] for p in lms[:468]]
239
  x_geo = torch.tensor(coords, dtype=torch.float).to(DEVICE)
240
+ x_geo_norm = x_geo - x_geo.mean(dim=0)
241
 
242
  faces = face_app.get(img_bgr)
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
 
249
+ # --- CALCULATE DENSITY (Required for new model) ---
250
+ density = calculate_density(x_geo_norm, GLOBAL_EDGE_INDEX).unsqueeze(0).to(DEVICE)
251
+
252
  with torch.no_grad():
253
+ # A. Run Location Finder (The 94% Model)
254
+ # Inputs: Coords, IDs, Regions, Density, Context
255
+ logits_loc = location_model(x_geo_norm.unsqueeze(0), STATIC_POINT_IDS, STATIC_REGION_IDS, density, emb.unsqueeze(0))
256
+ probs_loc = torch.sigmoid(logits_loc).squeeze().cpu().numpy()
257
 
258
+ # B. Run Attribute Expert (Only if we need attributes)
259
+ smart_features = backbone.get_features(data).unsqueeze(0)
260
+ coords_input = x_geo_norm.unsqueeze(0)
 
 
261
  preds = expert_model(smart_features, coords_input, STATIC_POINT_IDS, STATIC_REGION_IDS)
262
 
 
 
 
 
263
  # Attributes
264
  prob_t = torch.softmax(preds['tech'], dim=-1).squeeze().cpu().numpy()
265
  prob_d = torch.softmax(preds['dosage'], dim=-1).squeeze().cpu().numpy()
 
270
  h, w, _ = img_bgr.shape
271
  pixel_coords = np.array([[p.x*w, p.y*h, p.z*w] for p in lms[:468]])
272
 
273
+ # Use the NEW high-accuracy probabilities for NMS
274
  optimal_indices = apply_nms_indices(pixel_coords, probs_loc, spacing_mm=12.0, threshold=0.4)
275
 
276
+ classes_tech = ["Bolus", "Fanning", "Microbolus"]
 
 
277
  classes_dosage = ["0.01ml", "0.02ml", "0.05ml", "0.1ml", "0.2ml", "0.3ml", "0.5ml", "1.0ml"]
278
  classes_depth = ["Periosteal", "Subdermal", "Hypodermic", "Dermal"]
279
  classes_prod = ["XXL", "XL", "L", "M", "S", "Hydro", "Induce", "Lips"]
 
297
 
298
  return {
299
  "status": "success",
300
+ "message": "SOTA V6: Dual-Model Active",
301
  "summary": {
302
  "total_optimal": len(optimal_list),
303
  "max_confidence": float(probs_loc.max())