Spaces:
Sleeping
Sleeping
Jatin-tec
commited on
Commit
·
4c4f437
1
Parent(s):
aa30915
crop footer
Browse files- trufor_runner.py +130 -3
trufor_runner.py
CHANGED
|
@@ -7,7 +7,7 @@ import subprocess
|
|
| 7 |
import tempfile
|
| 8 |
from dataclasses import dataclass
|
| 9 |
from pathlib import Path
|
| 10 |
-
from typing import Dict, Optional
|
| 11 |
|
| 12 |
import numpy as np
|
| 13 |
from PIL import Image
|
|
@@ -136,10 +136,17 @@ class TruForEngine:
|
|
| 136 |
if image is None:
|
| 137 |
raise TruForUnavailableError("No image supplied to TruFor inference.")
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
if self.backend == "docker":
|
| 140 |
-
return self._infer_docker(
|
| 141 |
if self.backend == "native":
|
| 142 |
-
return self._infer_native(
|
| 143 |
|
| 144 |
raise TruForUnavailableError("TruFor backend not configured.")
|
| 145 |
|
|
@@ -267,3 +274,123 @@ class TruForEngine:
|
|
| 267 |
|
| 268 |
heat_img = Image.fromarray(heat, mode="RGB").resize(base_rgb.size, Image.BILINEAR)
|
| 269 |
return Image.blend(base_rgb, heat_img, alpha)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import tempfile
|
| 8 |
from dataclasses import dataclass
|
| 9 |
from pathlib import Path
|
| 10 |
+
from typing import Dict, Optional, Tuple
|
| 11 |
|
| 12 |
import numpy as np
|
| 13 |
from PIL import Image
|
|
|
|
| 136 |
if image is None:
|
| 137 |
raise TruForUnavailableError("No image supplied to TruFor inference.")
|
| 138 |
|
| 139 |
+
prepared_image, cropped = self._strip_gps_overlay(image)
|
| 140 |
+
if cropped:
|
| 141 |
+
LOGGER.debug(
|
| 142 |
+
"Cropping %d px GPS overlay before TruFor inference.",
|
| 143 |
+
image.height - prepared_image.height,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
if self.backend == "docker":
|
| 147 |
+
return self._infer_docker(prepared_image)
|
| 148 |
if self.backend == "native":
|
| 149 |
+
return self._infer_native(prepared_image)
|
| 150 |
|
| 151 |
raise TruForUnavailableError("TruFor backend not configured.")
|
| 152 |
|
|
|
|
| 274 |
|
| 275 |
heat_img = Image.fromarray(heat, mode="RGB").resize(base_rgb.size, Image.BILINEAR)
|
| 276 |
return Image.blend(base_rgb, heat_img, alpha)
|
| 277 |
+
|
| 278 |
+
@staticmethod
|
| 279 |
+
def _strip_gps_overlay(image: Image.Image) -> Tuple[Image.Image, bool]:
|
| 280 |
+
gray = np.asarray(image.convert("L"), dtype=np.uint8)
|
| 281 |
+
hsv = np.asarray(image.convert("HSV"), dtype=np.uint8)
|
| 282 |
+
hue = hsv[..., 0] / 255.0
|
| 283 |
+
sat = hsv[..., 1] / 255.0
|
| 284 |
+
val = hsv[..., 2] / 255.0
|
| 285 |
+
height, width = gray.shape
|
| 286 |
+
min_overlay = max(int(height * 0.08), 40)
|
| 287 |
+
max_overlay = max(int(height * 0.45), min_overlay + 1)
|
| 288 |
+
if height <= min_overlay:
|
| 289 |
+
return image, False
|
| 290 |
+
start_row = height - min_overlay
|
| 291 |
+
stop_row = max(height - max_overlay, 1)
|
| 292 |
+
|
| 293 |
+
row_means = gray.mean(axis=1)
|
| 294 |
+
sat_means = sat.mean(axis=1)
|
| 295 |
+
blue_mask = (hue >= 0.5) & (hue <= 0.75) & (sat >= 0.25) & (val <= 0.95)
|
| 296 |
+
yellow_mask = (hue >= 0.08) & (hue <= 0.18) & (sat >= 0.35) & (val >= 0.45)
|
| 297 |
+
white_mask = (val >= 0.87) & (sat <= 0.28)
|
| 298 |
+
dark_mask = val <= 0.28
|
| 299 |
+
|
| 300 |
+
overlay_mask = blue_mask | yellow_mask | white_mask | dark_mask
|
| 301 |
+
overlay_ratio_rows = overlay_mask.mean(axis=1)
|
| 302 |
+
|
| 303 |
+
boundary = None
|
| 304 |
+
best_score = 0.0
|
| 305 |
+
|
| 306 |
+
# First, try to detect a long contiguous overlay band using hysteresis on coverage.
|
| 307 |
+
high_ratio = 0.52
|
| 308 |
+
low_ratio = 0.36
|
| 309 |
+
run_len = 0
|
| 310 |
+
run_top = height
|
| 311 |
+
for row in range(height - 1, stop_row - 1, -1):
|
| 312 |
+
ratio = overlay_ratio_rows[row]
|
| 313 |
+
if ratio >= high_ratio or (run_len > 0 and ratio >= low_ratio):
|
| 314 |
+
run_len += 1
|
| 315 |
+
run_top = row
|
| 316 |
+
if run_len >= max_overlay:
|
| 317 |
+
break
|
| 318 |
+
elif run_len > 0:
|
| 319 |
+
if run_len >= min_overlay:
|
| 320 |
+
break
|
| 321 |
+
run_len = 0
|
| 322 |
+
run_top = height
|
| 323 |
+
elif height - row >= max_overlay:
|
| 324 |
+
break
|
| 325 |
+
|
| 326 |
+
if run_len >= min_overlay:
|
| 327 |
+
boundary_candidate = run_top
|
| 328 |
+
overlay_consistency = overlay_ratio_rows[boundary_candidate:height].mean()
|
| 329 |
+
boundary_strength = abs(row_means[boundary_candidate - 1] - row_means[boundary_candidate]) if boundary_candidate > 0 else abs(row_means[boundary_candidate] - row_means[min(boundary_candidate + 1, height - 1)])
|
| 330 |
+
|
| 331 |
+
if overlay_consistency >= 0.45 and boundary_strength >= 4.0:
|
| 332 |
+
overlay_height = height - boundary_candidate
|
| 333 |
+
margin = min(max(int(overlay_height * 0.25), 18), boundary_candidate)
|
| 334 |
+
crop_row = max(boundary_candidate - margin, 0)
|
| 335 |
+
cropped_image = image.crop((0, 0, width, crop_row))
|
| 336 |
+
return cropped_image, True
|
| 337 |
+
|
| 338 |
+
# Detect GPS Map Camera overlay at the bottom and crop it out if present.
|
| 339 |
+
for row in range(start_row, stop_row - 1, -1):
|
| 340 |
+
overlay_height = height - row
|
| 341 |
+
if overlay_height < min_overlay:
|
| 342 |
+
continue
|
| 343 |
+
if overlay_height > max_overlay:
|
| 344 |
+
break
|
| 345 |
+
|
| 346 |
+
overlay_hue = hue[row:height, :]
|
| 347 |
+
overlay_sat = sat[row:height, :]
|
| 348 |
+
overlay_val = val[row:height, :]
|
| 349 |
+
|
| 350 |
+
high_sat_ratio = float((overlay_sat > 0.3).mean())
|
| 351 |
+
dark_ratio = float((overlay_val < 0.3).mean())
|
| 352 |
+
bright_ratio = float((overlay_val > 0.88).mean())
|
| 353 |
+
colored_band_ratio = float(((overlay_sat > 0.32) & (overlay_val > 0.25) & (overlay_val < 0.85)).mean())
|
| 354 |
+
blue_ratio = float(((overlay_hue > 0.48) & (overlay_hue < 0.74) & (overlay_sat > 0.28)).mean())
|
| 355 |
+
yellow_ratio = float(((overlay_hue > 0.07) & (overlay_hue < 0.2) & (overlay_sat > 0.35) & (overlay_val > 0.45)).mean())
|
| 356 |
+
|
| 357 |
+
prev_mean = row_means[row - 1] if row > 0 else row_means[row]
|
| 358 |
+
boundary_strength = abs(prev_mean - row_means[row])
|
| 359 |
+
saturation_jump = sat_means[row - 1] - sat_means[row] if row > 0 else 0.0
|
| 360 |
+
|
| 361 |
+
score = 0.0
|
| 362 |
+
if high_sat_ratio > 0.38:
|
| 363 |
+
score += 0.8
|
| 364 |
+
if colored_band_ratio > 0.35:
|
| 365 |
+
score += 0.7
|
| 366 |
+
if blue_ratio > 0.22:
|
| 367 |
+
score += 0.8
|
| 368 |
+
if yellow_ratio > 0.12:
|
| 369 |
+
score += 0.5
|
| 370 |
+
if dark_ratio > 0.32:
|
| 371 |
+
score += 0.6
|
| 372 |
+
if bright_ratio > 0.07:
|
| 373 |
+
score += 0.5
|
| 374 |
+
if boundary_strength > 5.5:
|
| 375 |
+
score += 0.6
|
| 376 |
+
if saturation_jump < -0.05:
|
| 377 |
+
score += 0.4
|
| 378 |
+
|
| 379 |
+
edge_ratio = boundary_strength / max(row_means[row], 1.0)
|
| 380 |
+
if edge_ratio > 0.11:
|
| 381 |
+
score += 0.3
|
| 382 |
+
if overlay_ratio_rows[row:height].mean() > 0.42:
|
| 383 |
+
score += 0.3
|
| 384 |
+
|
| 385 |
+
if score > best_score:
|
| 386 |
+
best_score = score
|
| 387 |
+
boundary = row
|
| 388 |
+
|
| 389 |
+
if boundary is None or best_score < 1.6:
|
| 390 |
+
return image, False
|
| 391 |
+
|
| 392 |
+
overlay_height = height - boundary
|
| 393 |
+
margin = min(max(int(overlay_height * 0.25), 18), boundary)
|
| 394 |
+
crop_row = max(boundary - margin, 0)
|
| 395 |
+
cropped_image = image.crop((0, 0, width, crop_row))
|
| 396 |
+
return cropped_image, True
|