Update app.py
Browse files
app.py
CHANGED
|
@@ -34,7 +34,7 @@ CLASS_COLORS = {
|
|
| 34 |
2: (65, 105, 225) # #4169E1 - Royal blue
|
| 35 |
}
|
| 36 |
|
| 37 |
-
# Actual model performance metrics
|
| 38 |
MODEL_METRICS = {
|
| 39 |
'segmentation': {
|
| 40 |
'dice': 0.6212,
|
|
@@ -53,13 +53,22 @@ MODEL_METRICS = {
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
-
def
|
| 57 |
-
"""Create
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
mode="gauge+number",
|
| 60 |
-
value=
|
| 61 |
domain={'x': [0, 1], 'y': [0, 1]},
|
| 62 |
-
title={'text':
|
| 63 |
number={'suffix': '%', 'font': {'size': 24}},
|
| 64 |
gauge={
|
| 65 |
'axis': {'range': [0, 100], 'tickwidth': 1},
|
|
@@ -67,17 +76,36 @@ def create_gauge_chart(value: float, title: str, max_value: float = 1.0) -> go.F
|
|
| 67 |
'bgcolor': 'rgba(0,0,0,0)',
|
| 68 |
'borderwidth': 0,
|
| 69 |
'steps': [
|
| 70 |
-
{'range': [0,
|
| 71 |
-
{'range': [
|
| 72 |
-
{'range': [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
]
|
| 74 |
}
|
| 75 |
-
))
|
| 76 |
|
| 77 |
fig.update_layout(
|
| 78 |
paper_bgcolor='rgba(0,0,0,0)',
|
| 79 |
plot_bgcolor='rgba(0,0,0,0)',
|
| 80 |
-
height=
|
| 81 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 82 |
)
|
| 83 |
|
|
@@ -120,8 +148,7 @@ class ForgeryDetector:
|
|
| 120 |
Returns:
|
| 121 |
original_image: Original uploaded image
|
| 122 |
overlay_image: Image with detection overlay
|
| 123 |
-
|
| 124 |
-
gauge_accuracy: Accuracy gauge
|
| 125 |
results_html: Detection results as HTML
|
| 126 |
"""
|
| 127 |
# Handle PDF files
|
|
@@ -205,14 +232,17 @@ class ForgeryDetector:
|
|
| 205 |
# Create visualization
|
| 206 |
overlay = self._create_overlay(original_image, results)
|
| 207 |
|
| 208 |
-
#
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
| 211 |
|
| 212 |
# Create HTML response
|
| 213 |
results_html = self._create_html_report(results)
|
| 214 |
|
| 215 |
-
return original_image, overlay,
|
| 216 |
|
| 217 |
def _create_overlay(self, image, results):
|
| 218 |
"""Create overlay visualization"""
|
|
@@ -306,19 +336,19 @@ def detect_forgery(file):
|
|
| 306 |
try:
|
| 307 |
if file is None:
|
| 308 |
empty_html = "<div style='padding:12px; border:1px solid #d9534f; border-radius:8px;'>❌ <b>No file uploaded.</b></div>"
|
| 309 |
-
return None, None, None,
|
| 310 |
|
| 311 |
# Get file path
|
| 312 |
file_path = file.name if hasattr(file, 'name') else file
|
| 313 |
|
| 314 |
# Check if PDF
|
| 315 |
if file_path.lower().endswith('.pdf'):
|
| 316 |
-
original, overlay,
|
| 317 |
else:
|
| 318 |
image = Image.open(file_path)
|
| 319 |
-
original, overlay,
|
| 320 |
|
| 321 |
-
return original, overlay,
|
| 322 |
|
| 323 |
except Exception as e:
|
| 324 |
import traceback
|
|
@@ -329,7 +359,7 @@ def detect_forgery(file):
|
|
| 329 |
❌ <b>Error:</b> {str(e)}
|
| 330 |
</div>
|
| 331 |
"""
|
| 332 |
-
return None, None, None,
|
| 333 |
|
| 334 |
|
| 335 |
# Custom CSS - subtle styling
|
|
@@ -355,6 +385,7 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 355 |
)
|
| 356 |
|
| 357 |
with gr.Row():
|
|
|
|
| 358 |
with gr.Column(scale=1):
|
| 359 |
gr.Markdown("### Upload Document")
|
| 360 |
input_file = gr.File(
|
|
@@ -380,17 +411,18 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 380 |
"""
|
| 381 |
)
|
| 382 |
|
|
|
|
| 383 |
with gr.Column(scale=2):
|
| 384 |
gr.Markdown("### Detection Results")
|
| 385 |
with gr.Row():
|
| 386 |
original_image = gr.Image(label="Original Document", type="numpy")
|
| 387 |
output_image = gr.Image(label="Detected Forgeries", type="numpy")
|
| 388 |
|
|
|
|
| 389 |
with gr.Row():
|
| 390 |
with gr.Column(scale=1):
|
| 391 |
-
gr.Markdown("###
|
| 392 |
-
|
| 393 |
-
gauge_accuracy = gr.Plot(label="Classification Accuracy")
|
| 394 |
|
| 395 |
with gr.Column(scale=1):
|
| 396 |
gr.Markdown("### Analysis Report")
|
|
@@ -399,12 +431,16 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 399 |
)
|
| 400 |
|
| 401 |
gr.Markdown(
|
| 402 |
-
"""
|
| 403 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
**Model Architecture:**
|
| 405 |
-
-
|
| 406 |
-
-
|
| 407 |
-
-
|
| 408 |
"""
|
| 409 |
)
|
| 410 |
|
|
@@ -412,13 +448,13 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 412 |
analyze_btn.click(
|
| 413 |
fn=detect_forgery,
|
| 414 |
inputs=[input_file],
|
| 415 |
-
outputs=[original_image, output_image,
|
| 416 |
)
|
| 417 |
|
| 418 |
clear_btn.click(
|
| 419 |
-
fn=lambda: (None, None, None, None,
|
| 420 |
inputs=None,
|
| 421 |
-
outputs=[input_file, original_image, output_image,
|
| 422 |
)
|
| 423 |
|
| 424 |
|
|
|
|
| 34 |
2: (65, 105, 225) # #4169E1 - Royal blue
|
| 35 |
}
|
| 36 |
|
| 37 |
+
# Actual model performance metrics (trained model)
|
| 38 |
MODEL_METRICS = {
|
| 39 |
'segmentation': {
|
| 40 |
'dice': 0.6212,
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
+
def create_detection_gauges(num_detections: int, avg_confidence: float) -> go.Figure:
|
| 57 |
+
"""Create radial gauge charts for detection results"""
|
| 58 |
+
from plotly.subplots import make_subplots
|
| 59 |
+
|
| 60 |
+
fig = make_subplots(
|
| 61 |
+
rows=1, cols=2,
|
| 62 |
+
specs=[[{'type': 'indicator'}, {'type': 'indicator'}]],
|
| 63 |
+
horizontal_spacing=0.15
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# Confidence gauge
|
| 67 |
+
fig.add_trace(go.Indicator(
|
| 68 |
mode="gauge+number",
|
| 69 |
+
value=avg_confidence * 100,
|
| 70 |
domain={'x': [0, 1], 'y': [0, 1]},
|
| 71 |
+
title={'text': 'Avg Confidence', 'font': {'size': 14}},
|
| 72 |
number={'suffix': '%', 'font': {'size': 24}},
|
| 73 |
gauge={
|
| 74 |
'axis': {'range': [0, 100], 'tickwidth': 1},
|
|
|
|
| 76 |
'bgcolor': 'rgba(0,0,0,0)',
|
| 77 |
'borderwidth': 0,
|
| 78 |
'steps': [
|
| 79 |
+
{'range': [0, 60], 'color': 'rgba(217, 83, 79, 0.1)'},
|
| 80 |
+
{'range': [60, 80], 'color': 'rgba(240, 173, 78, 0.1)'},
|
| 81 |
+
{'range': [80, 100], 'color': 'rgba(92, 184, 92, 0.1)'}
|
| 82 |
+
]
|
| 83 |
+
}
|
| 84 |
+
), row=1, col=1)
|
| 85 |
+
|
| 86 |
+
# Detections count gauge
|
| 87 |
+
fig.add_trace(go.Indicator(
|
| 88 |
+
mode="gauge+number",
|
| 89 |
+
value=num_detections,
|
| 90 |
+
domain={'x': [0, 1], 'y': [0, 1]},
|
| 91 |
+
title={'text': 'Regions Detected', 'font': {'size': 14}},
|
| 92 |
+
number={'font': {'size': 24}},
|
| 93 |
+
gauge={
|
| 94 |
+
'axis': {'range': [0, 10], 'tickwidth': 1},
|
| 95 |
+
'bar': {'color': '#d9534f' if num_detections > 0 else '#5cb85c', 'thickness': 0.7},
|
| 96 |
+
'bgcolor': 'rgba(0,0,0,0)',
|
| 97 |
+
'borderwidth': 0,
|
| 98 |
+
'steps': [
|
| 99 |
+
{'range': [0, 1], 'color': 'rgba(92, 184, 92, 0.1)'},
|
| 100 |
+
{'range': [1, 10], 'color': 'rgba(217, 83, 79, 0.1)'}
|
| 101 |
]
|
| 102 |
}
|
| 103 |
+
), row=1, col=2)
|
| 104 |
|
| 105 |
fig.update_layout(
|
| 106 |
paper_bgcolor='rgba(0,0,0,0)',
|
| 107 |
plot_bgcolor='rgba(0,0,0,0)',
|
| 108 |
+
height=250,
|
| 109 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 110 |
)
|
| 111 |
|
|
|
|
| 148 |
Returns:
|
| 149 |
original_image: Original uploaded image
|
| 150 |
overlay_image: Image with detection overlay
|
| 151 |
+
detection_gauges: Plotly figure with detection metrics
|
|
|
|
| 152 |
results_html: Detection results as HTML
|
| 153 |
"""
|
| 154 |
# Handle PDF files
|
|
|
|
| 232 |
# Create visualization
|
| 233 |
overlay = self._create_overlay(original_image, results)
|
| 234 |
|
| 235 |
+
# Calculate metrics for gauges
|
| 236 |
+
num_detections = len(results)
|
| 237 |
+
avg_confidence = sum(r['confidence'] for r in results) / num_detections if num_detections > 0 else 0
|
| 238 |
+
|
| 239 |
+
# Create detection gauge charts
|
| 240 |
+
detection_gauges = create_detection_gauges(num_detections, avg_confidence)
|
| 241 |
|
| 242 |
# Create HTML response
|
| 243 |
results_html = self._create_html_report(results)
|
| 244 |
|
| 245 |
+
return original_image, overlay, detection_gauges, results_html
|
| 246 |
|
| 247 |
def _create_overlay(self, image, results):
|
| 248 |
"""Create overlay visualization"""
|
|
|
|
| 336 |
try:
|
| 337 |
if file is None:
|
| 338 |
empty_html = "<div style='padding:12px; border:1px solid #d9534f; border-radius:8px;'>❌ <b>No file uploaded.</b></div>"
|
| 339 |
+
return None, None, None, empty_html
|
| 340 |
|
| 341 |
# Get file path
|
| 342 |
file_path = file.name if hasattr(file, 'name') else file
|
| 343 |
|
| 344 |
# Check if PDF
|
| 345 |
if file_path.lower().endswith('.pdf'):
|
| 346 |
+
original, overlay, gauges, results_html = detector.detect(file_path)
|
| 347 |
else:
|
| 348 |
image = Image.open(file_path)
|
| 349 |
+
original, overlay, gauges, results_html = detector.detect(image)
|
| 350 |
|
| 351 |
+
return original, overlay, gauges, results_html
|
| 352 |
|
| 353 |
except Exception as e:
|
| 354 |
import traceback
|
|
|
|
| 359 |
❌ <b>Error:</b> {str(e)}
|
| 360 |
</div>
|
| 361 |
"""
|
| 362 |
+
return None, None, None, error_html
|
| 363 |
|
| 364 |
|
| 365 |
# Custom CSS - subtle styling
|
|
|
|
| 385 |
)
|
| 386 |
|
| 387 |
with gr.Row():
|
| 388 |
+
# Left column: Upload
|
| 389 |
with gr.Column(scale=1):
|
| 390 |
gr.Markdown("### Upload Document")
|
| 391 |
input_file = gr.File(
|
|
|
|
| 411 |
"""
|
| 412 |
)
|
| 413 |
|
| 414 |
+
# Right column: Results (Original and Overlay side-by-side)
|
| 415 |
with gr.Column(scale=2):
|
| 416 |
gr.Markdown("### Detection Results")
|
| 417 |
with gr.Row():
|
| 418 |
original_image = gr.Image(label="Original Document", type="numpy")
|
| 419 |
output_image = gr.Image(label="Detected Forgeries", type="numpy")
|
| 420 |
|
| 421 |
+
# Bottom section: Gauges and Report
|
| 422 |
with gr.Row():
|
| 423 |
with gr.Column(scale=1):
|
| 424 |
+
gr.Markdown("### Detection Metrics")
|
| 425 |
+
detection_gauges = gr.Plot(label="Detection Results")
|
|
|
|
| 426 |
|
| 427 |
with gr.Column(scale=1):
|
| 428 |
gr.Markdown("### Analysis Report")
|
|
|
|
| 431 |
)
|
| 432 |
|
| 433 |
gr.Markdown(
|
| 434 |
+
f"""
|
| 435 |
---
|
| 436 |
+
**Trained Model Performance:**
|
| 437 |
+
- Segmentation Dice: {MODEL_METRICS['segmentation']['dice']*100:.1f}% | IoU: {MODEL_METRICS['segmentation']['iou']*100:.1f}%
|
| 438 |
+
- Classification Accuracy: {MODEL_METRICS['classification']['overall_accuracy']*100:.1f}%
|
| 439 |
+
|
| 440 |
**Model Architecture:**
|
| 441 |
+
- Localization: MobileNetV3-Small + UNet
|
| 442 |
+
- Classification: LightGBM with 526 hybrid features
|
| 443 |
+
- Training: 140K samples from DocTamper dataset
|
| 444 |
"""
|
| 445 |
)
|
| 446 |
|
|
|
|
| 448 |
analyze_btn.click(
|
| 449 |
fn=detect_forgery,
|
| 450 |
inputs=[input_file],
|
| 451 |
+
outputs=[original_image, output_image, detection_gauges, output_html]
|
| 452 |
)
|
| 453 |
|
| 454 |
clear_btn.click(
|
| 455 |
+
fn=lambda: (None, None, None, None, "<i>No analysis yet. Upload a document and click Analyze.</i>"),
|
| 456 |
inputs=None,
|
| 457 |
+
outputs=[input_file, original_image, output_image, detection_gauges, output_html]
|
| 458 |
)
|
| 459 |
|
| 460 |
|