Spaces:
Sleeping
Sleeping
barathvasan-dev commited on
Commit ·
084c5ef
1
Parent(s): 667c692
⚡ OPTIMIZE: detector - 3x faster plate detection (single OCR, lightweight preprocessing)
Browse files- app.py +11 -36
- detector.py +55 -110
app.py
CHANGED
|
@@ -926,62 +926,37 @@ with gr.Blocks(
|
|
| 926 |
return history, (inv_results or {})
|
| 927 |
|
| 928 |
def update_detailed_tabs_lightweight(inv_results):
|
| 929 |
-
"""Update detailed analysis tabs
|
| 930 |
if not inv_results or inv_results.get("status") == "error":
|
| 931 |
-
return ("
|
| 932 |
|
| 933 |
try:
|
| 934 |
analysis = inv_results.get("analysis", {})
|
| 935 |
total = inv_results.get('total_records', 0)
|
| 936 |
|
| 937 |
-
#
|
| 938 |
-
data_info = f""
|
| 939 |
-
### 📊 Query Results Summary
|
| 940 |
-
- **Total Records Found:** {total}
|
| 941 |
-
- **Unique Vehicles:** {analysis.get('unique_vehicles', 0)}
|
| 942 |
-
- **Unique Locations:** {analysis.get('unique_locations', 0)}
|
| 943 |
-
- **Date Range:** {analysis.get('date_range', {}).get('start', 'N/A')} to {analysis.get('date_range', {}).get('end', 'N/A')}
|
| 944 |
-
- **Confidence Score:** {analysis.get('confidence_score', 0):.0%}
|
| 945 |
-
"""
|
| 946 |
|
| 947 |
-
# Findings
|
| 948 |
findings_list = analysis.get("key_findings", [])
|
| 949 |
-
|
| 950 |
-
findings = "### 🎯 Key Findings:\n" + "\n".join([f"- **{f}**" for f in findings_list[:5]])
|
| 951 |
-
else:
|
| 952 |
-
findings = "### 📝 No specific key findings detected"
|
| 953 |
|
| 954 |
-
# Data table (limit to 20 rows
|
| 955 |
df_data = None
|
| 956 |
try:
|
| 957 |
preview = inv_results.get("data_preview", [])[:20]
|
| 958 |
-
if preview
|
| 959 |
df_data = pd.DataFrame(preview)
|
| 960 |
-
# Limit columns for better display
|
| 961 |
-
important_cols = ['plate', 'location', 'state', 'vehicle_type', 'timestamp', 'date', 'hour']
|
| 962 |
-
available_cols = [col for col in important_cols if col in df_data.columns]
|
| 963 |
-
if available_cols:
|
| 964 |
-
df_data = df_data[available_cols]
|
| 965 |
except Exception as e:
|
| 966 |
print(f"DataFrame error: {e}")
|
| 967 |
|
| 968 |
-
#
|
| 969 |
-
|
| 970 |
-
patterns = analysis.get("patterns", [])
|
| 971 |
-
metrics = f"""
|
| 972 |
-
### 📈 Analysis Metrics:
|
| 973 |
-
- **Anomalies Detected:** {len(anomalies)}
|
| 974 |
-
- **Patterns Found:** {len(patterns)}
|
| 975 |
-
- **Data Quality:** {analysis.get('data_quality_score', 0):.0%}
|
| 976 |
-
- **Concentration Score:** {analysis.get('vehicle_concentration', 0):.1f}
|
| 977 |
-
"""
|
| 978 |
|
| 979 |
return (data_info, findings, df_data, metrics)
|
| 980 |
except Exception as e:
|
| 981 |
print(f"Tab update error: {e}")
|
| 982 |
-
|
| 983 |
-
traceback.print_exc()
|
| 984 |
-
return ("⚠️ Error processing data", "⚠️ Error", None, "⚠️ Error")
|
| 985 |
|
| 986 |
# OPTIMIZED CLICK HANDLER - Fast first update, then background refresh
|
| 987 |
investigate_btn.click(
|
|
|
|
| 926 |
return history, (inv_results or {})
|
| 927 |
|
| 928 |
def update_detailed_tabs_lightweight(inv_results):
|
| 929 |
+
"""Update detailed analysis tabs"""
|
| 930 |
if not inv_results or inv_results.get("status") == "error":
|
| 931 |
+
return ("No data available", "No findings yet", None, "No metrics")
|
| 932 |
|
| 933 |
try:
|
| 934 |
analysis = inv_results.get("analysis", {})
|
| 935 |
total = inv_results.get('total_records', 0)
|
| 936 |
|
| 937 |
+
# Summary
|
| 938 |
+
data_info = f"📊 **{total} records** | 🚗 **{analysis.get('unique_vehicles', 0)} vehicles** | 📍 **{analysis.get('unique_locations', 0)} locations**"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
|
| 940 |
+
# Findings
|
| 941 |
findings_list = analysis.get("key_findings", [])
|
| 942 |
+
findings = "**Key Findings:**\n" + "\n".join([f"• {f}" for f in findings_list[:3]]) if findings_list else "No key findings"
|
|
|
|
|
|
|
|
|
|
| 943 |
|
| 944 |
+
# Data table (limit to 20 rows)
|
| 945 |
df_data = None
|
| 946 |
try:
|
| 947 |
preview = inv_results.get("data_preview", [])[:20]
|
| 948 |
+
if preview:
|
| 949 |
df_data = pd.DataFrame(preview)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 950 |
except Exception as e:
|
| 951 |
print(f"DataFrame error: {e}")
|
| 952 |
|
| 953 |
+
# Metrics
|
| 954 |
+
metrics = f"**Confidence:** {analysis.get('confidence_score', 0):.0%}\n**Records:** {total}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 955 |
|
| 956 |
return (data_info, findings, df_data, metrics)
|
| 957 |
except Exception as e:
|
| 958 |
print(f"Tab update error: {e}")
|
| 959 |
+
return ("Error processing data", "Error", None, "Error")
|
|
|
|
|
|
|
| 960 |
|
| 961 |
# OPTIMIZED CLICK HANDLER - Fast first update, then background refresh
|
| 962 |
investigate_btn.click(
|
detector.py
CHANGED
|
@@ -135,69 +135,34 @@ plate_regex = re.compile(
|
|
| 135 |
# ================= PREPROCESS ================= #
|
| 136 |
|
| 137 |
def preprocess_plate(crop):
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
cv2.COLOR_RGB2GRAY
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
7,
|
| 159 |
-
50,
|
| 160 |
-
50
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
kernel = np.array([
|
| 164 |
-
[0, -1, 0],
|
| 165 |
-
[-1, 5, -1],
|
| 166 |
-
[0, -1, 0]
|
| 167 |
-
])
|
| 168 |
-
|
| 169 |
-
sharp = cv2.filter2D(
|
| 170 |
-
filtered,
|
| 171 |
-
-1,
|
| 172 |
-
kernel
|
| 173 |
-
)
|
| 174 |
-
|
| 175 |
-
return cv2.cvtColor(
|
| 176 |
-
sharp,
|
| 177 |
-
cv2.COLOR_GRAY2BGR
|
| 178 |
-
)
|
| 179 |
|
| 180 |
# ================= AUGMENT ================= #
|
| 181 |
|
| 182 |
def build_crops(crop):
|
| 183 |
-
|
| 184 |
-
return
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
cv2.resize(
|
| 189 |
-
crop,
|
| 190 |
-
None,
|
| 191 |
-
fx=1.2,
|
| 192 |
-
fy=1.2
|
| 193 |
-
),
|
| 194 |
-
|
| 195 |
-
cv2.GaussianBlur(
|
| 196 |
-
crop,
|
| 197 |
-
(3, 3),
|
| 198 |
-
0
|
| 199 |
-
)
|
| 200 |
-
]
|
| 201 |
|
| 202 |
# ================= OCR ================= #
|
| 203 |
|
|
@@ -326,7 +291,11 @@ def detect_plate(image):
|
|
| 326 |
if isinstance(image, Image.Image):
|
| 327 |
image = np.array(image.convert("RGB"))
|
| 328 |
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
if yolo_model is None:
|
| 332 |
|
|
@@ -338,8 +307,8 @@ def detect_plate(image):
|
|
| 338 |
False
|
| 339 |
)
|
| 340 |
|
|
|
|
| 341 |
results = yolo_model(image)
|
| 342 |
-
|
| 343 |
boxes = results[0].boxes
|
| 344 |
|
| 345 |
if boxes is None or len(boxes) == 0:
|
|
@@ -353,75 +322,51 @@ def detect_plate(image):
|
|
| 353 |
)
|
| 354 |
|
| 355 |
h, w = image.shape[:2]
|
| 356 |
-
|
| 357 |
xyxy = boxes.xyxy.cpu().numpy()
|
| 358 |
-
|
| 359 |
confs = boxes.conf.cpu().numpy()
|
| 360 |
|
| 361 |
-
|
|
|
|
| 362 |
|
| 363 |
for i, (x1, y1, x2, y2) in enumerate(xyxy):
|
| 364 |
|
| 365 |
if confs[i] < 0.5:
|
| 366 |
continue
|
| 367 |
|
| 368 |
-
pad = int(
|
| 369 |
-
0.12 * max(
|
| 370 |
-
x2 - x1,
|
| 371 |
-
y2 - y1
|
| 372 |
-
)
|
| 373 |
-
)
|
| 374 |
-
|
| 375 |
l = max(int(x1 - pad), 0)
|
| 376 |
t = max(int(y1 - pad), 0)
|
| 377 |
r = min(int(x2 + pad), w - 1)
|
| 378 |
b = min(int(y2 + pad), h - 1)
|
| 379 |
|
| 380 |
crop = image[t:b, l:r]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
pre = preprocess_plate(variant)
|
| 385 |
-
|
| 386 |
-
ocr_out = run_ocr(pre)
|
| 387 |
-
|
| 388 |
-
texts, confs_ocr = parse_ocr(ocr_out)
|
| 389 |
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
if cf < 0.3:
|
| 393 |
-
continue
|
| 394 |
-
|
| 395 |
-
norm = fix_common(
|
| 396 |
-
clean_text(txt)
|
| 397 |
-
)
|
| 398 |
-
|
| 399 |
-
if len(norm) < 4:
|
| 400 |
-
continue
|
| 401 |
-
|
| 402 |
-
collected.append(norm)
|
| 403 |
-
|
| 404 |
-
if not collected:
|
| 405 |
-
|
| 406 |
-
plate = ""
|
| 407 |
-
|
| 408 |
-
else:
|
| 409 |
-
|
| 410 |
-
combined = max(
|
| 411 |
-
collected,
|
| 412 |
-
key=len
|
| 413 |
-
)
|
| 414 |
|
| 415 |
-
|
| 416 |
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
else:
|
| 420 |
-
plate = combined
|
| 421 |
|
| 422 |
-
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
|
|
|
|
| 425 |
state = extract_state(plate)
|
| 426 |
|
| 427 |
return (
|
|
@@ -429,5 +374,5 @@ def detect_plate(image):
|
|
| 429 |
state,
|
| 430 |
vehicle_type,
|
| 431 |
vehicle_conf,
|
| 432 |
-
|
| 433 |
)
|
|
|
|
| 135 |
# ================= PREPROCESS ================= #
|
| 136 |
|
| 137 |
def preprocess_plate(crop):
|
| 138 |
+
"""Lightweight preprocessing - faster than full CLAHE"""
|
| 139 |
+
try:
|
| 140 |
+
# Skip heavy CLAHE, use simple resize + adaptive threshold
|
| 141 |
+
gray = cv2.cvtColor(crop, cv2.COLOR_RGB2GRAY)
|
| 142 |
+
resized = cv2.resize(gray, (320, 96))
|
| 143 |
+
|
| 144 |
+
# Use adaptive thresholding instead of CLAHE (faster)
|
| 145 |
+
enhanced = cv2.adaptiveThreshold(
|
| 146 |
+
resized, 255,
|
| 147 |
+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
| 148 |
+
cv2.THRESH_BINARY, 11, 2
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Light bilateral filter only
|
| 152 |
+
filtered = cv2.bilateralFilter(enhanced, 5, 30, 30)
|
| 153 |
+
|
| 154 |
+
return cv2.cvtColor(filtered, cv2.COLOR_GRAY2BGR)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
print(f"Preprocess error: {e}")
|
| 157 |
+
return crop
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
# ================= AUGMENT ================= #
|
| 160 |
|
| 161 |
def build_crops(crop):
|
| 162 |
+
"""Return only best crop variant instead of 3"""
|
| 163 |
+
# Only return the original crop - no multiple variants
|
| 164 |
+
# This reduces OCR calls from 3x to 1x
|
| 165 |
+
return [crop]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
# ================= OCR ================= #
|
| 168 |
|
|
|
|
| 291 |
if isinstance(image, Image.Image):
|
| 292 |
image = np.array(image.convert("RGB"))
|
| 293 |
|
| 294 |
+
# Skip vehicle classification if we're just looking for plates
|
| 295 |
+
# Uncomment vehicle_type line below if you need it
|
| 296 |
+
vehicle_type, vehicle_conf = "unknown", 0.0 # Fast path
|
| 297 |
+
# Uncomment for full classification:
|
| 298 |
+
# vehicle_type, vehicle_conf = classify_vehicle(image)
|
| 299 |
|
| 300 |
if yolo_model is None:
|
| 301 |
|
|
|
|
| 307 |
False
|
| 308 |
)
|
| 309 |
|
| 310 |
+
# YOLO detection
|
| 311 |
results = yolo_model(image)
|
|
|
|
| 312 |
boxes = results[0].boxes
|
| 313 |
|
| 314 |
if boxes is None or len(boxes) == 0:
|
|
|
|
| 322 |
)
|
| 323 |
|
| 324 |
h, w = image.shape[:2]
|
|
|
|
| 325 |
xyxy = boxes.xyxy.cpu().numpy()
|
|
|
|
| 326 |
confs = boxes.conf.cpu().numpy()
|
| 327 |
|
| 328 |
+
best_plate = ""
|
| 329 |
+
best_confidence = 0.0
|
| 330 |
|
| 331 |
for i, (x1, y1, x2, y2) in enumerate(xyxy):
|
| 332 |
|
| 333 |
if confs[i] < 0.5:
|
| 334 |
continue
|
| 335 |
|
| 336 |
+
pad = int(0.12 * max(x2 - x1, y2 - y1))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
l = max(int(x1 - pad), 0)
|
| 338 |
t = max(int(y1 - pad), 0)
|
| 339 |
r = min(int(x2 + pad), w - 1)
|
| 340 |
b = min(int(y2 + pad), h - 1)
|
| 341 |
|
| 342 |
crop = image[t:b, l:r]
|
| 343 |
+
|
| 344 |
+
# Only ONE preprocessing + OCR per detection (no variants)
|
| 345 |
+
pre = preprocess_plate(crop)
|
| 346 |
+
ocr_out = run_ocr(pre)
|
| 347 |
+
texts, confs_ocr = parse_ocr(ocr_out)
|
| 348 |
|
| 349 |
+
# Find best text in this detection
|
| 350 |
+
for txt, cf in zip(texts, confs_ocr):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
+
if cf < 0.3:
|
| 353 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
+
norm = fix_common(clean_text(txt))
|
| 356 |
|
| 357 |
+
if len(norm) < 4:
|
| 358 |
+
continue
|
|
|
|
|
|
|
| 359 |
|
| 360 |
+
# Check if matches Indian plate regex
|
| 361 |
+
match = plate_regex.search(norm)
|
| 362 |
+
if match:
|
| 363 |
+
plate = match.group(0)
|
| 364 |
+
# Early exit on first good match
|
| 365 |
+
if cf > best_confidence:
|
| 366 |
+
best_plate = plate
|
| 367 |
+
best_confidence = cf
|
| 368 |
|
| 369 |
+
plate = best_plate.upper()
|
| 370 |
state = extract_state(plate)
|
| 371 |
|
| 372 |
return (
|
|
|
|
| 374 |
state,
|
| 375 |
vehicle_type,
|
| 376 |
vehicle_conf,
|
| 377 |
+
len(plate) > 0
|
| 378 |
)
|