Spaces:
Sleeping
Sleeping
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,22 +53,13 @@ MODEL_METRICS = {
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
-
def
|
| 57 |
-
"""Create radial gauge
|
| 58 |
-
|
| 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=
|
| 70 |
domain={'x': [0, 1], 'y': [0, 1]},
|
| 71 |
-
title={'text':
|
| 72 |
number={'suffix': '%', 'font': {'size': 24}},
|
| 73 |
gauge={
|
| 74 |
'axis': {'range': [0, 100], 'tickwidth': 1},
|
|
@@ -76,36 +67,17 @@ def create_detection_gauges(num_detections: int, avg_confidence: float) -> go.Fi
|
|
| 76 |
'bgcolor': 'rgba(0,0,0,0)',
|
| 77 |
'borderwidth': 0,
|
| 78 |
'steps': [
|
| 79 |
-
{'range': [0,
|
| 80 |
-
{'range': [
|
| 81 |
-
{'range': [
|
| 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 |
-
)
|
| 104 |
|
| 105 |
fig.update_layout(
|
| 106 |
paper_bgcolor='rgba(0,0,0,0)',
|
| 107 |
plot_bgcolor='rgba(0,0,0,0)',
|
| 108 |
-
height=
|
| 109 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 110 |
)
|
| 111 |
|
|
@@ -148,7 +120,8 @@ class ForgeryDetector:
|
|
| 148 |
Returns:
|
| 149 |
original_image: Original uploaded image
|
| 150 |
overlay_image: Image with detection overlay
|
| 151 |
-
|
|
|
|
| 152 |
results_html: Detection results as HTML
|
| 153 |
"""
|
| 154 |
# Handle PDF files
|
|
@@ -232,17 +205,14 @@ class ForgeryDetector:
|
|
| 232 |
# Create visualization
|
| 233 |
overlay = self._create_overlay(original_image, results)
|
| 234 |
|
| 235 |
-
#
|
| 236 |
-
|
| 237 |
-
|
| 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,
|
| 246 |
|
| 247 |
def _create_overlay(self, image, results):
|
| 248 |
"""Create overlay visualization"""
|
|
@@ -336,19 +306,19 @@ def detect_forgery(file):
|
|
| 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,
|
| 347 |
else:
|
| 348 |
image = Image.open(file_path)
|
| 349 |
-
original, overlay,
|
| 350 |
|
| 351 |
-
return original, overlay,
|
| 352 |
|
| 353 |
except Exception as e:
|
| 354 |
import traceback
|
|
@@ -359,7 +329,7 @@ def detect_forgery(file):
|
|
| 359 |
❌ <b>Error:</b> {str(e)}
|
| 360 |
</div>
|
| 361 |
"""
|
| 362 |
-
return None, None, None, error_html
|
| 363 |
|
| 364 |
|
| 365 |
# Custom CSS - subtle styling
|
|
@@ -385,7 +355,6 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 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,18 +380,17 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 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("###
|
| 425 |
-
|
|
|
|
| 426 |
|
| 427 |
with gr.Column(scale=1):
|
| 428 |
gr.Markdown("### Analysis Report")
|
|
@@ -431,16 +399,12 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 431 |
)
|
| 432 |
|
| 433 |
gr.Markdown(
|
| 434 |
-
|
| 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
|
| 443 |
-
- Training: 140K samples
|
| 444 |
"""
|
| 445 |
)
|
| 446 |
|
|
@@ -448,13 +412,13 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 448 |
analyze_btn.click(
|
| 449 |
fn=detect_forgery,
|
| 450 |
inputs=[input_file],
|
| 451 |
-
outputs=[original_image, output_image,
|
| 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,
|
| 458 |
)
|
| 459 |
|
| 460 |
|
|
|
|
| 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 |
}
|
| 54 |
|
| 55 |
|
| 56 |
+
def create_gauge_chart(value: float, title: str, max_value: float = 1.0) -> go.Figure:
|
| 57 |
+
"""Create a subtle radial gauge chart"""
|
| 58 |
+
fig = go.Figure(go.Indicator(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
mode="gauge+number",
|
| 60 |
+
value=value * 100,
|
| 61 |
domain={'x': [0, 1], 'y': [0, 1]},
|
| 62 |
+
title={'text': title, 'font': {'size': 14}},
|
| 63 |
number={'suffix': '%', 'font': {'size': 24}},
|
| 64 |
gauge={
|
| 65 |
'axis': {'range': [0, 100], 'tickwidth': 1},
|
|
|
|
| 67 |
'bgcolor': 'rgba(0,0,0,0)',
|
| 68 |
'borderwidth': 0,
|
| 69 |
'steps': [
|
| 70 |
+
{'range': [0, 50], 'color': 'rgba(217, 83, 79, 0.1)'},
|
| 71 |
+
{'range': [50, 75], 'color': 'rgba(240, 173, 78, 0.1)'},
|
| 72 |
+
{'range': [75, 100], 'color': 'rgba(92, 184, 92, 0.1)'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=200,
|
| 81 |
margin=dict(l=20, r=20, t=40, b=20)
|
| 82 |
)
|
| 83 |
|
|
|
|
| 120 |
Returns:
|
| 121 |
original_image: Original uploaded image
|
| 122 |
overlay_image: Image with detection overlay
|
| 123 |
+
gauge_dice: Dice score gauge
|
| 124 |
+
gauge_accuracy: Accuracy gauge
|
| 125 |
results_html: Detection results as HTML
|
| 126 |
"""
|
| 127 |
# Handle PDF files
|
|
|
|
| 205 |
# Create visualization
|
| 206 |
overlay = self._create_overlay(original_image, results)
|
| 207 |
|
| 208 |
+
# Create gauge charts
|
| 209 |
+
gauge_dice = create_gauge_chart(MODEL_METRICS['segmentation']['dice'], 'Segmentation Dice')
|
| 210 |
+
gauge_accuracy = create_gauge_chart(MODEL_METRICS['classification']['overall_accuracy'], 'Classification Accuracy')
|
|
|
|
|
|
|
|
|
|
| 211 |
|
| 212 |
# Create HTML response
|
| 213 |
results_html = self._create_html_report(results)
|
| 214 |
|
| 215 |
+
return original_image, overlay, gauge_dice, gauge_accuracy, results_html
|
| 216 |
|
| 217 |
def _create_overlay(self, image, results):
|
| 218 |
"""Create overlay visualization"""
|
|
|
|
| 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, None, empty_html
|
| 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, gauge_dice, gauge_acc, results_html = detector.detect(file_path)
|
| 317 |
else:
|
| 318 |
image = Image.open(file_path)
|
| 319 |
+
original, overlay, gauge_dice, gauge_acc, results_html = detector.detect(image)
|
| 320 |
|
| 321 |
+
return original, overlay, gauge_dice, gauge_acc, results_html
|
| 322 |
|
| 323 |
except Exception as e:
|
| 324 |
import traceback
|
|
|
|
| 329 |
❌ <b>Error:</b> {str(e)}
|
| 330 |
</div>
|
| 331 |
"""
|
| 332 |
+
return None, None, None, None, error_html
|
| 333 |
|
| 334 |
|
| 335 |
# Custom CSS - subtle styling
|
|
|
|
| 355 |
)
|
| 356 |
|
| 357 |
with gr.Row():
|
|
|
|
| 358 |
with gr.Column(scale=1):
|
| 359 |
gr.Markdown("### Upload Document")
|
| 360 |
input_file = gr.File(
|
|
|
|
| 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("### Model Performance")
|
| 392 |
+
gauge_dice = gr.Plot(label="Segmentation Dice Score")
|
| 393 |
+
gauge_accuracy = gr.Plot(label="Classification Accuracy")
|
| 394 |
|
| 395 |
with gr.Column(scale=1):
|
| 396 |
gr.Markdown("### Analysis Report")
|
|
|
|
| 399 |
)
|
| 400 |
|
| 401 |
gr.Markdown(
|
| 402 |
+
"""
|
| 403 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
**Model Architecture:**
|
| 405 |
+
- **Localization:** MobileNetV3-Small + UNet (Dice: 62.1%, IoU: 45.1%)
|
| 406 |
+
- **Classification:** LightGBM with 526 features (Accuracy: 88.97%)
|
| 407 |
+
- **Training:** 140K samples (DocTamper + SCD + FCD datasets)
|
| 408 |
"""
|
| 409 |
)
|
| 410 |
|
|
|
|
| 412 |
analyze_btn.click(
|
| 413 |
fn=detect_forgery,
|
| 414 |
inputs=[input_file],
|
| 415 |
+
outputs=[original_image, output_image, gauge_dice, gauge_accuracy, output_html]
|
| 416 |
)
|
| 417 |
|
| 418 |
clear_btn.click(
|
| 419 |
+
fn=lambda: (None, None, None, None, None, "<i>No analysis yet. Upload a document and click Analyze.</i>"),
|
| 420 |
inputs=None,
|
| 421 |
+
outputs=[input_file, original_image, output_image, gauge_dice, gauge_accuracy, output_html]
|
| 422 |
)
|
| 423 |
|
| 424 |
|