Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,10 +8,17 @@ from huggingface_hub import snapshot_download
|
|
| 8 |
import pytesseract
|
| 9 |
import io
|
| 10 |
import math
|
|
|
|
|
|
|
|
|
|
| 11 |
from collections import defaultdict
|
| 12 |
import matplotlib
|
| 13 |
matplotlib.use("Agg")
|
| 14 |
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
_msi_model = None
|
| 17 |
|
|
@@ -240,6 +247,8 @@ def identify_hotspots(saliency_map, img_h, img_w, top_n=4):
|
|
| 240 |
"intensity": round(avg_intensity * 100, 1), "cx": int(cx), "cy": int(cy)})
|
| 241 |
zones.sort(key=lambda z: -z["intensity"])
|
| 242 |
top_zones = zones[:top_n]
|
|
|
|
|
|
|
| 243 |
top_zones.sort(key=lambda z: (z["cy"], z["cx"]))
|
| 244 |
return top_zones
|
| 245 |
|
|
@@ -397,6 +406,76 @@ def draw_issue_markers(overlay_bgr, issue_boxes):
|
|
| 397 |
return img
|
| 398 |
|
| 399 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
def draw_zone_labels(overlay_bgr, zones):
|
| 401 |
img = overlay_bgr.copy()
|
| 402 |
for i, z in enumerate(zones, 1):
|
|
@@ -429,6 +508,7 @@ def analyze(pil_image, overlay_strength):
|
|
| 429 |
tips = generate_recommendations(saliency_map, scores, read, h, w)
|
| 430 |
chart_image = create_score_chart(scores['focus_score'], scores['spread_score'], readability_score)
|
| 431 |
|
|
|
|
| 432 |
overlay_bgr = draw_zone_labels(overlay_bgr, zones)
|
| 433 |
overlay_bgr = draw_issue_markers(overlay_bgr, read.get("issue_boxes", []))
|
| 434 |
overlay_rgb = cv2.cvtColor(overlay_bgr, cv2.COLOR_BGR2RGB)
|
|
@@ -499,9 +579,10 @@ with gr.Blocks(title="Design Analyzer") as demo:
|
|
| 499 |
"screens, banner ads, print, packaging.\n"
|
| 500 |
"- **Attention + readability in one pass**, not two separate tools.\n"
|
| 501 |
"- **No inflated claims.** A cited, transparent process instead "
|
| 502 |
-
"of a marketing number.\n
|
| 503 |
-
"
|
| 504 |
-
"
|
|
|
|
| 505 |
)
|
| 506 |
with gr.Row():
|
| 507 |
with gr.Column():
|
|
@@ -512,6 +593,9 @@ with gr.Blocks(title="Design Analyzer") as demo:
|
|
| 512 |
image_output = gr.Image(label="Heatmap result")
|
| 513 |
chart_output = gr.Image(label="Scores at a glance")
|
| 514 |
text_output = gr.Markdown()
|
|
|
|
|
|
|
| 515 |
analyze_btn.click(fn=analyze, inputs=[image_input, strength_slider], outputs=[image_output, chart_output, text_output])
|
|
|
|
| 516 |
|
| 517 |
demo.launch()
|
|
|
|
| 8 |
import pytesseract
|
| 9 |
import io
|
| 10 |
import math
|
| 11 |
+
import os
|
| 12 |
+
import re
|
| 13 |
+
import tempfile
|
| 14 |
from collections import defaultdict
|
| 15 |
import matplotlib
|
| 16 |
matplotlib.use("Agg")
|
| 17 |
import matplotlib.pyplot as plt
|
| 18 |
+
from reportlab.lib.pagesizes import letter
|
| 19 |
+
from reportlab.lib.units import inch
|
| 20 |
+
from reportlab.lib.styles import getSampleStyleSheet
|
| 21 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage
|
| 22 |
|
| 23 |
_msi_model = None
|
| 24 |
|
|
|
|
| 247 |
"intensity": round(avg_intensity * 100, 1), "cx": int(cx), "cy": int(cy)})
|
| 248 |
zones.sort(key=lambda z: -z["intensity"])
|
| 249 |
top_zones = zones[:top_n]
|
| 250 |
+
for idx, z in enumerate(top_zones):
|
| 251 |
+
z["intensity_rank"] = idx + 1
|
| 252 |
top_zones.sort(key=lambda z: (z["cy"], z["cx"]))
|
| 253 |
return top_zones
|
| 254 |
|
|
|
|
| 406 |
return img
|
| 407 |
|
| 408 |
|
| 409 |
+
def markdown_to_paragraphs(md_text, styles):
|
| 410 |
+
story = []
|
| 411 |
+
for raw_line in md_text.split("\n"):
|
| 412 |
+
line = raw_line.strip()
|
| 413 |
+
if not line:
|
| 414 |
+
story.append(Spacer(1, 6))
|
| 415 |
+
continue
|
| 416 |
+
if line.startswith("## "):
|
| 417 |
+
story.append(Paragraph(line[3:], styles['Heading1']))
|
| 418 |
+
elif line.startswith("### "):
|
| 419 |
+
story.append(Paragraph(line[4:], styles['Heading2']))
|
| 420 |
+
elif line.startswith("---"):
|
| 421 |
+
story.append(Spacer(1, 10))
|
| 422 |
+
elif line.startswith("- ") or line.startswith("* "):
|
| 423 |
+
text = line[2:]
|
| 424 |
+
text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
|
| 425 |
+
text = re.sub(r'\*(.*?)\*', r'<i>\1</i>', text)
|
| 426 |
+
story.append(Paragraph("• " + text, styles['Normal']))
|
| 427 |
+
elif re.match(r'^\d+\.\s', line):
|
| 428 |
+
num, text = line.split('.', 1)
|
| 429 |
+
text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text.strip())
|
| 430 |
+
story.append(Paragraph(f"{num}. {text}", styles['Normal']))
|
| 431 |
+
else:
|
| 432 |
+
text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', line)
|
| 433 |
+
text = re.sub(r'\*(.*?)\*', r'<i>\1</i>', text)
|
| 434 |
+
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<link href="\2"><u>\1</u></link>', text)
|
| 435 |
+
story.append(Paragraph(text, styles['Normal']))
|
| 436 |
+
return story
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
def export_pdf(heatmap_img, chart_img, summary_md):
|
| 440 |
+
if heatmap_img is None or not summary_md:
|
| 441 |
+
return None
|
| 442 |
+
styles = getSampleStyleSheet()
|
| 443 |
+
tmp_dir = tempfile.gettempdir()
|
| 444 |
+
pdf_path = os.path.join(tmp_dir, "design_analysis_report.pdf")
|
| 445 |
+
doc = SimpleDocTemplate(pdf_path, pagesize=letter, topMargin=0.6 * inch, bottomMargin=0.6 * inch,
|
| 446 |
+
leftMargin=0.7 * inch, rightMargin=0.7 * inch)
|
| 447 |
+
story = [Paragraph("Design Analyzer Report", styles['Title']), Spacer(1, 12)]
|
| 448 |
+
|
| 449 |
+
max_width = 6.1 * inch
|
| 450 |
+
heatmap_path = os.path.join(tmp_dir, "heatmap_export_temp.png")
|
| 451 |
+
heatmap_img.save(heatmap_path)
|
| 452 |
+
ratio = heatmap_img.height / heatmap_img.width
|
| 453 |
+
story.append(RLImage(heatmap_path, width=max_width, height=max_width * ratio))
|
| 454 |
+
story.append(Spacer(1, 14))
|
| 455 |
+
|
| 456 |
+
if chart_img is not None:
|
| 457 |
+
chart_path = os.path.join(tmp_dir, "chart_export_temp.png")
|
| 458 |
+
chart_img.save(chart_path)
|
| 459 |
+
chart_ratio = chart_img.height / chart_img.width
|
| 460 |
+
story.append(RLImage(chart_path, width=max_width, height=max_width * chart_ratio))
|
| 461 |
+
story.append(Spacer(1, 14))
|
| 462 |
+
|
| 463 |
+
story.extend(markdown_to_paragraphs(summary_md, styles))
|
| 464 |
+
doc.build(story)
|
| 465 |
+
return pdf_path
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
def draw_scan_path(overlay_bgr, zones):
|
| 469 |
+
img = overlay_bgr.copy()
|
| 470 |
+
ordered = sorted(zones, key=lambda z: z["intensity_rank"])
|
| 471 |
+
for i in range(len(ordered) - 1):
|
| 472 |
+
pt1 = (ordered[i]["cx"], ordered[i]["cy"])
|
| 473 |
+
pt2 = (ordered[i + 1]["cx"], ordered[i + 1]["cy"])
|
| 474 |
+
cv2.arrowedLine(img, pt1, pt2, (255, 255, 255), 4, tipLength=0.06, line_type=cv2.LINE_AA)
|
| 475 |
+
cv2.arrowedLine(img, pt1, pt2, (20, 20, 20), 2, tipLength=0.06, line_type=cv2.LINE_AA)
|
| 476 |
+
return img
|
| 477 |
+
|
| 478 |
+
|
| 479 |
def draw_zone_labels(overlay_bgr, zones):
|
| 480 |
img = overlay_bgr.copy()
|
| 481 |
for i, z in enumerate(zones, 1):
|
|
|
|
| 508 |
tips = generate_recommendations(saliency_map, scores, read, h, w)
|
| 509 |
chart_image = create_score_chart(scores['focus_score'], scores['spread_score'], readability_score)
|
| 510 |
|
| 511 |
+
overlay_bgr = draw_scan_path(overlay_bgr, zones)
|
| 512 |
overlay_bgr = draw_zone_labels(overlay_bgr, zones)
|
| 513 |
overlay_bgr = draw_issue_markers(overlay_bgr, read.get("issue_boxes", []))
|
| 514 |
overlay_rgb = cv2.cvtColor(overlay_bgr, cv2.COLOR_BGR2RGB)
|
|
|
|
| 579 |
"screens, banner ads, print, packaging.\n"
|
| 580 |
"- **Attention + readability in one pass**, not two separate tools.\n"
|
| 581 |
"- **No inflated claims.** A cited, transparent process instead "
|
| 582 |
+
"of a marketing number.\n"
|
| 583 |
+
"- **Scan-path arrows** show the predicted order attention flows, "
|
| 584 |
+
"not just isolated hotspots.\n\n"
|
| 585 |
+
"Coming soon: before/after comparisons."
|
| 586 |
)
|
| 587 |
with gr.Row():
|
| 588 |
with gr.Column():
|
|
|
|
| 593 |
image_output = gr.Image(label="Heatmap result")
|
| 594 |
chart_output = gr.Image(label="Scores at a glance")
|
| 595 |
text_output = gr.Markdown()
|
| 596 |
+
pdf_btn = gr.Button("Download PDF Report")
|
| 597 |
+
pdf_output = gr.File(label="PDF Report")
|
| 598 |
analyze_btn.click(fn=analyze, inputs=[image_input, strength_slider], outputs=[image_output, chart_output, text_output])
|
| 599 |
+
pdf_btn.click(fn=export_pdf, inputs=[image_output, chart_output, text_output], outputs=pdf_output)
|
| 600 |
|
| 601 |
demo.launch()
|