SaniaE commited on
Commit
ccd0a10
·
verified ·
1 Parent(s): 85cd7a9

fixed missing/colliding labels and updated global model attention

Browse files
Files changed (1) hide show
  1. app.py +67 -77
app.py CHANGED
@@ -4,11 +4,7 @@ import time
4
  import cv2
5
  import numpy as np
6
 
7
- # ==========================================
8
- # HARDWARE SCHEDULING INTERFACES (BASELINE)
9
- # ==========================================
10
  os.environ['TF_USE_LEGACY_KERAS'] = '1'
11
- # Initialize clean, un-throttled thread footprints for benchmarking
12
  os.environ["OMP_NUM_THREADS"] = "2"
13
  os.environ["TF_NUM_INTRAOP_THREADS"] = "2"
14
  os.environ["TF_NUM_INTEROP_THREADS"] = "1"
@@ -25,7 +21,6 @@ from object_detection.builders import model_builder
25
 
26
  app = FastAPI()
27
 
28
- # Enable smooth frontend cross-origin header interceptions
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
@@ -35,27 +30,18 @@ app.add_middleware(
35
  expose_headers=["X-Processing-Time", "X-Model-Status"]
36
  )
37
 
38
- # 1. Download Private Models
39
  HF_TOKEN = os.getenv("HF_Token")
40
  REPO_ID = "SaniaE/Car_Damage_Detection"
41
 
42
- print("Downloading models from Hugging Face...")
43
- model_dir = snapshot_download(
44
- repo_id=REPO_ID,
45
- token=HF_TOKEN,
46
- local_dir="./models_data"
47
- )
48
 
49
  PIPELINE_CONFIG = os.path.join(model_dir, "object_detection_model/pipeline.config")
50
  CHECKPOINT_PATH = os.path.join(model_dir, "object_detection_model/ckpt-37")
51
  LABEL_MAP_PATH = os.path.join(model_dir, "object_detection_model/label_map.pbtxt")
52
  CNN_MODEL_PATH = os.path.join(model_dir, "cnn_filter.h5")
53
 
54
- # 3. Load Models
55
- print("Loading CNN Filter...")
56
  cnn_filter = tf.keras.models.load_model(CNN_MODEL_PATH, compile=False)
57
 
58
- print("Loading Object Detection Model...")
59
  configs = config_util.get_configs_from_pipeline_file(PIPELINE_CONFIG)
60
  detection_model = model_builder.build(model_config=configs['model'], is_training=False)
61
  ckpt = tf.compat.v2.train.Checkpoint(model=detection_model)
@@ -69,12 +55,15 @@ def detect_fn(image):
69
  detections = detection_model.postprocess(prediction_dict, shapes)
70
  return detections
71
 
72
- def get_top_prediction(detections):
73
- """Extracts the index of the most confident detection."""
74
  scores = detections['detection_scores'][0].numpy()
75
- if len(scores) > 0 and scores[0] > 0.4:
76
- return 0, int(detections['detection_classes'][0].numpy()[0])
77
- return None, None
 
 
 
78
 
79
  @app.get("/")
80
  def read_root():
@@ -83,8 +72,6 @@ def read_root():
83
  @app.post("/predict")
84
  async def predict(file: UploadFile = File(...)):
85
  start_time = time.perf_counter()
86
-
87
- # Read Image
88
  contents = await file.read()
89
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
90
  image_np = np.array(image_pil)
@@ -114,9 +101,14 @@ async def predict(file: UploadFile = File(...)):
114
  ymin, xmin, ymax, xmax = boxes[i]
115
  (left, right, top, bottom) = (xmin * width, xmax * width,
116
  ymin * height, ymax * height)
 
 
117
  cv2.rectangle(image_cv, (int(left), int(top)), (int(right), int(bottom)), (255, 255, 0), 2)
 
 
118
  label = f"{category_index.get(classes[i] + 1, {}).get('name', 'unknown')}: {int(scores[i]*100)}%"
119
- cv2.putText(image_cv, label, (int(left), int(top) - 10),
 
120
  cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
121
 
122
  _, buffer = cv2.imencode('.jpg', image_cv)
@@ -132,7 +124,6 @@ async def predict(file: UploadFile = File(...)):
132
  @app.post("/explain")
133
  async def explain(file: UploadFile = File(...)):
134
  start_time = time.perf_counter()
135
-
136
  contents = await file.read()
137
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
138
  image_np = np.array(image_pil).astype(np.float32)
@@ -145,12 +136,13 @@ async def explain(file: UploadFile = File(...)):
145
  raw_scores = prediction_dict['class_predictions_with_background'][0]
146
 
147
  detections = detection_model.postprocess(prediction_dict, shapes)
148
- _, top_class = get_top_prediction(detections)
149
 
150
- if top_class is None:
151
  elapsed_time = time.perf_counter() - start_time
152
  return Response(status_code=204, headers={"X-Processing-Time": f"{elapsed_time:.4f}"})
153
 
 
154
  loss = tf.reduce_max(raw_scores[:, top_class])
155
 
156
  grads = tape.gradient(loss, input_tensor)
@@ -171,16 +163,11 @@ async def explain(file: UploadFile = File(...)):
171
  elapsed_time = time.perf_counter() - start_time
172
  print(f"[BENCHMARK] /explain turnaround: {elapsed_time:.4f}s")
173
 
174
- return StreamingResponse(
175
- io.BytesIO(buffer.tobytes()),
176
- media_type="image/jpeg",
177
- headers={"X-Processing-Time": f"{elapsed_time:.4f}"}
178
- )
179
 
180
  @app.post("/explain/tiled")
181
  async def explain_tiled(file: UploadFile = File(...)):
182
  start_time = time.perf_counter()
183
-
184
  contents = await file.read()
185
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
186
  image_np = np.array(image_pil).astype(np.float32)
@@ -193,41 +180,48 @@ async def explain_tiled(file: UploadFile = File(...)):
193
 
194
  base_image = cv2.cvtColor(image_np.astype(np.uint8), cv2.COLOR_RGB2BGR)
195
  h_img, w_img, _ = base_image.shape
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- for i in range(min(len(scores), 3)):
198
- if scores[i] > 0.4:
199
- ymin, xmin, ymax, xmax = boxes[i]
200
- cv2.rectangle(base_image, (int(xmin*w_img), int(ymin*h_img)),
201
- (int(xmax*w_img), int(ymax*h_img)), (255, 255, 0), 2)
202
 
203
- panels = [base_image]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- for i in range(3):
206
- if i < len(scores) and scores[i] > 0.4:
207
- target_class = classes[i]
208
-
209
- with tf.GradientTape() as tape:
210
- tape.watch(input_tensor)
211
- image, shapes = detection_model.preprocess(input_tensor)
212
- prediction_dict = detection_model.predict(image, shapes)
213
- raw_scores = prediction_dict['class_predictions_with_background'][0]
214
- loss = tf.reduce_max(raw_scores[:, target_class])
215
-
216
- grads = tape.gradient(loss, input_tensor)
217
- saliency = np.max(np.abs(grads.numpy()), axis=-1)[0]
218
-
219
- v_min, v_max = np.percentile(saliency, (5, 95))
220
- saliency = np.clip((saliency - v_min) / (v_max - v_min + 1e-8), 0, 1)
221
- heatmap = cv2.applyColorMap(np.uint8(255 * saliency), cv2.COLORMAP_JET)
222
-
223
- overlay = cv2.addWeighted(cv2.cvtColor(image_np.astype(np.uint8), cv2.COLOR_RGB2BGR), 0.6, heatmap, 0.4, 0)
224
-
225
- class_name = category_index.get(target_class + 1, {}).get('name', 'unknown')
226
- cv2.putText(overlay, f"Top {i+1}: {class_name}", (10, 30),
227
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
228
- panels.append(overlay)
229
- else:
230
- panels.append(np.zeros_like(base_image))
231
 
232
  top_row = np.hstack((panels[0], panels[1]))
233
  bottom_row = np.hstack((panels[2], panels[3]))
@@ -237,16 +231,11 @@ async def explain_tiled(file: UploadFile = File(...)):
237
  elapsed_time = time.perf_counter() - start_time
238
  print(f"[BENCHMARK] /explain/tiled turnaround: {elapsed_time:.4f}s")
239
 
240
- return StreamingResponse(
241
- io.BytesIO(buffer.tobytes()),
242
- media_type="image/jpeg",
243
- headers={"X-Processing-Time": f"{elapsed_time:.4f}"}
244
- )
245
 
246
  @app.post("/explain/global")
247
  async def explain_global(file: UploadFile = File(...)):
248
  start_time = time.perf_counter()
249
-
250
  contents = await file.read()
251
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
252
  image_np = np.array(image_pil).astype(np.float32)
@@ -259,14 +248,19 @@ async def explain_global(file: UploadFile = File(...)):
259
  image, shapes = detection_model.preprocess(input_tensor)
260
  prediction_dict = detection_model.predict(image, shapes)
261
  raw_scores = prediction_dict['class_predictions_with_background'][0]
 
262
  foreground_scores = raw_scores[:, 1:]
263
- loss = tf.reduce_sum(tf.reduce_max(foreground_scores, axis=-1))
 
 
264
 
265
  grads = tape.gradient(loss, input_tensor)
266
  saliency = np.max(np.abs(grads.numpy()), axis=-1)[0]
267
 
268
- v_min, v_max = np.percentile(saliency, (5, 95))
 
269
  saliency = np.clip((saliency - v_min) / (v_max - v_min + 1e-8), 0, 1)
 
270
 
271
  heatmap = cv2.applyColorMap(np.uint8(255 * saliency), cv2.COLORMAP_JET)
272
  overlay = cv2.addWeighted(image_bgr, 0.6, heatmap, 0.4, 0)
@@ -278,8 +272,4 @@ async def explain_global(file: UploadFile = File(...)):
278
  elapsed_time = time.perf_counter() - start_time
279
  print(f"[BENCHMARK] /explain/global turnaround: {elapsed_time:.4f}s")
280
 
281
- return StreamingResponse(
282
- io.BytesIO(buffer.tobytes()),
283
- media_type="image/jpeg",
284
- headers={"X-Processing-Time": f"{elapsed_time:.4f}"}
285
- )
 
4
  import cv2
5
  import numpy as np
6
 
 
 
 
7
  os.environ['TF_USE_LEGACY_KERAS'] = '1'
 
8
  os.environ["OMP_NUM_THREADS"] = "2"
9
  os.environ["TF_NUM_INTRAOP_THREADS"] = "2"
10
  os.environ["TF_NUM_INTEROP_THREADS"] = "1"
 
21
 
22
  app = FastAPI()
23
 
 
24
  app.add_middleware(
25
  CORSMiddleware,
26
  allow_origins=["*"],
 
30
  expose_headers=["X-Processing-Time", "X-Model-Status"]
31
  )
32
 
 
33
  HF_TOKEN = os.getenv("HF_Token")
34
  REPO_ID = "SaniaE/Car_Damage_Detection"
35
 
36
+ model_dir = snapshot_download(repo_id=REPO_ID, token=HF_TOKEN, local_dir="./models_data")
 
 
 
 
 
37
 
38
  PIPELINE_CONFIG = os.path.join(model_dir, "object_detection_model/pipeline.config")
39
  CHECKPOINT_PATH = os.path.join(model_dir, "object_detection_model/ckpt-37")
40
  LABEL_MAP_PATH = os.path.join(model_dir, "object_detection_model/label_map.pbtxt")
41
  CNN_MODEL_PATH = os.path.join(model_dir, "cnn_filter.h5")
42
 
 
 
43
  cnn_filter = tf.keras.models.load_model(CNN_MODEL_PATH, compile=False)
44
 
 
45
  configs = config_util.get_configs_from_pipeline_file(PIPELINE_CONFIG)
46
  detection_model = model_builder.build(model_config=configs['model'], is_training=False)
47
  ckpt = tf.compat.v2.train.Checkpoint(model=detection_model)
 
55
  detections = detection_model.postprocess(prediction_dict, shapes)
56
  return detections
57
 
58
+ def get_top_predictions(detections, max_predictions=3):
59
+ """Extracts top predictions matching criteria."""
60
  scores = detections['detection_scores'][0].numpy()
61
+ classes = detections['detection_classes'][0].numpy().astype(int)
62
+ valid_indices = []
63
+ for idx in range(min(len(scores), max_predictions)):
64
+ if scores[idx] > 0.4:
65
+ valid_indices.append((idx, classes[idx]))
66
+ return valid_indices
67
 
68
  @app.get("/")
69
  def read_root():
 
72
  @app.post("/predict")
73
  async def predict(file: UploadFile = File(...)):
74
  start_time = time.perf_counter()
 
 
75
  contents = await file.read()
76
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
77
  image_np = np.array(image_pil)
 
101
  ymin, xmin, ymax, xmax = boxes[i]
102
  (left, right, top, bottom) = (xmin * width, xmax * width,
103
  ymin * height, ymax * height)
104
+
105
+ # Draw Box
106
  cv2.rectangle(image_cv, (int(left), int(top)), (int(right), int(bottom)), (255, 255, 0), 2)
107
+
108
+ # OPTIMIZATION: Bounds-safe layout rendering to prevent clipping
109
  label = f"{category_index.get(classes[i] + 1, {}).get('name', 'unknown')}: {int(scores[i]*100)}%"
110
+ text_y = int(top) - 10 if int(top) - 10 > 15 else int(top) + 20
111
+ cv2.putText(image_cv, label, (int(left), text_y),
112
  cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
113
 
114
  _, buffer = cv2.imencode('.jpg', image_cv)
 
124
  @app.post("/explain")
125
  async def explain(file: UploadFile = File(...)):
126
  start_time = time.perf_counter()
 
127
  contents = await file.read()
128
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
129
  image_np = np.array(image_pil).astype(np.float32)
 
136
  raw_scores = prediction_dict['class_predictions_with_background'][0]
137
 
138
  detections = detection_model.postprocess(prediction_dict, shapes)
139
+ valid_preds = get_top_predictions(detections, max_predictions=1)
140
 
141
+ if not valid_preds:
142
  elapsed_time = time.perf_counter() - start_time
143
  return Response(status_code=204, headers={"X-Processing-Time": f"{elapsed_time:.4f}"})
144
 
145
+ _, top_class = valid_preds[0]
146
  loss = tf.reduce_max(raw_scores[:, top_class])
147
 
148
  grads = tape.gradient(loss, input_tensor)
 
163
  elapsed_time = time.perf_counter() - start_time
164
  print(f"[BENCHMARK] /explain turnaround: {elapsed_time:.4f}s")
165
 
166
+ return StreamingResponse(io.BytesIO(buffer.tobytes()), media_type="image/jpeg", headers={"X-Processing-Time": f"{elapsed_time:.4f}"})
 
 
 
 
167
 
168
  @app.post("/explain/tiled")
169
  async def explain_tiled(file: UploadFile = File(...)):
170
  start_time = time.perf_counter()
 
171
  contents = await file.read()
172
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
173
  image_np = np.array(image_pil).astype(np.float32)
 
180
 
181
  base_image = cv2.cvtColor(image_np.astype(np.uint8), cv2.COLOR_RGB2BGR)
182
  h_img, w_img, _ = base_image.shape
183
+
184
+ valid_preds = get_top_predictions(detections, max_predictions=3)
185
+ panels = [base_image]
186
+
187
+ for idx, (target_idx, _) in enumerate(valid_preds):
188
+ ymin, xmin, ymax, xmax = boxes[target_idx]
189
+ cv2.rectangle(base_image, (int(xmin*w_img), int(ymin*h_img)),
190
+ (int(xmax*w_img), int(ymax*h_img)), (255, 255, 0), 2)
191
+
192
+ # OPTIMIZATION: Unified single-pass Gradient Tape for all targets
193
+ with tf.GradientTape(persistent=True) as tape:
194
+ tape.watch(input_tensor)
195
+ image, shapes = detection_model.preprocess(input_tensor)
196
+ prediction_dict = detection_model.predict(image, shapes)
197
+ raw_scores = prediction_dict['class_predictions_with_background'][0]
198
 
199
+ losses = []
200
+ for _, target_class in valid_preds:
201
+ losses.append(tf.reduce_max(raw_scores[:, target_class]))
 
 
202
 
203
+ original_bgr_raw = cv2.cvtColor(image_np.astype(np.uint8), cv2.COLOR_RGB2BGR)
204
+
205
+ for idx, loss in enumerate(losses):
206
+ target_class = valid_preds[idx][1]
207
+ grads = tape.gradient(loss, input_tensor)
208
+ saliency = np.max(np.abs(grads.numpy()), axis=-1)[0]
209
+
210
+ v_min, v_max = np.percentile(saliency, (5, 95))
211
+ saliency = np.clip((saliency - v_min) / (v_max - v_min + 1e-8), 0, 1)
212
+ heatmap = cv2.applyColorMap(np.uint8(255 * saliency), cv2.COLORMAP_JET)
213
+
214
+ overlay = cv2.addWeighted(original_bgr_raw.copy(), 0.6, heatmap, 0.4, 0)
215
+
216
+ class_name = category_index.get(target_class + 1, {}).get('name', 'unknown')
217
+ cv2.putText(overlay, f"Top {idx+1}: {class_name}", (10, 30),
218
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
219
+ panels.append(overlay)
220
 
221
+ del tape # Clean up persistent allocations
222
+
223
+ while len(panels) < 4:
224
+ panels.append(np.zeros_like(base_image))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  top_row = np.hstack((panels[0], panels[1]))
227
  bottom_row = np.hstack((panels[2], panels[3]))
 
231
  elapsed_time = time.perf_counter() - start_time
232
  print(f"[BENCHMARK] /explain/tiled turnaround: {elapsed_time:.4f}s")
233
 
234
+ return StreamingResponse(io.BytesIO(buffer.tobytes()), media_type="image/jpeg", headers={"X-Processing-Time": f"{elapsed_time:.4f}"})
 
 
 
 
235
 
236
  @app.post("/explain/global")
237
  async def explain_global(file: UploadFile = File(...)):
238
  start_time = time.perf_counter()
 
239
  contents = await file.read()
240
  image_pil = Image.open(io.BytesIO(contents)).convert("RGB")
241
  image_np = np.array(image_pil).astype(np.float32)
 
248
  image, shapes = detection_model.preprocess(input_tensor)
249
  prediction_dict = detection_model.predict(image, shapes)
250
  raw_scores = prediction_dict['class_predictions_with_background'][0]
251
+
252
  foreground_scores = raw_scores[:, 1:]
253
+ # REFINEMENT: Take top-K elements to filter out background anchor static
254
+ top_anchor_values, _ = tf.math.top_k(tf.reduce_max(foreground_scores, axis=-1), k=20)
255
+ loss = tf.reduce_sum(top_anchor_values)
256
 
257
  grads = tape.gradient(loss, input_tensor)
258
  saliency = np.max(np.abs(grads.numpy()), axis=-1)[0]
259
 
260
+ # Dilate and blur to form clean structural contours instead of fuzz
261
+ v_min, v_max = np.percentile(saliency, (10, 98))
262
  saliency = np.clip((saliency - v_min) / (v_max - v_min + 1e-8), 0, 1)
263
+ saliency = cv2.GaussianBlur(saliency, (9, 9), 0)
264
 
265
  heatmap = cv2.applyColorMap(np.uint8(255 * saliency), cv2.COLORMAP_JET)
266
  overlay = cv2.addWeighted(image_bgr, 0.6, heatmap, 0.4, 0)
 
272
  elapsed_time = time.perf_counter() - start_time
273
  print(f"[BENCHMARK] /explain/global turnaround: {elapsed_time:.4f}s")
274
 
275
+ return StreamingResponse(io.BytesIO(buffer.tobytes()), media_type="image/jpeg", headers={"X-Processing-Time": f"{elapsed_time:.4f}"})