LTF-Demo / presentation.py
MuskAI's picture
Use English throughout the demo and all built-in controls
0d312b8 verified
Raw History Blame Contribute Delete
10.8 kB
"""Readable presentation of model scores and quantitative attention weights."""
from html import escape
import math
import numpy as np
CARD_CSS = """
.ltf-card{font-family:ui-sans-serif,system-ui,sans-serif;color:#15243c;line-height:1.5}
.ltf-card *{box-sizing:border-box}.ltf-eyebrow{font-size:12px;font-weight:750;letter-spacing:.11em;color:#60748c;text-transform:uppercase}
.ltf-decision{border:1px solid #dce5ef;border-radius:20px;padding:25px;background:#fff;min-height:325px}
.ltf-decision h2{font-size:25px;line-height:1.25;margin:10px 0 20px;letter-spacing:-.03em}
.ltf-score{font-size:58px;line-height:1;font-weight:800;letter-spacing:-.06em;color:var(--accent,#087f85)}
.ltf-score-label{font-size:13px;color:#60748c;margin:8px 0 18px}.ltf-chip{display:inline-block;padding:5px 10px;border-radius:8px;background:#eef3f8;color:#42566e;font-size:12px;font-weight:650;margin-right:5px}
.ltf-mini{display:flex;align-items:center;gap:8px;margin:12px 0;font-size:13px}.ltf-mini b{width:57px}.ltf-mini>span{flex:1;background:#edf1f6;height:7px;border-radius:5px;overflow:hidden}.ltf-mini i{display:block;background:#14888e;height:100%;border-radius:5px}.ltf-mini em{width:45px;text-align:right;font-style:normal;font-variant-numeric:tabular-nums}
.ltf-note{font-size:12px;color:#60748c;margin-top:18px}.ltf-wait{color:#405772;font-size:16px;margin:30px 0 10px}
.ltf-stat-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:2px 0 20px}.ltf-stat{background:#fff;border:1px solid #dce5ef;padding:17px 20px;border-radius:13px}.ltf-stat strong{display:block;font-size:27px;letter-spacing:-.04em}.ltf-stat span{font-size:12px;color:#60748c}
.ltf-chart{background:#fff;border:1px solid #dce5ef;border-radius:14px;padding:18px 16px}.ltf-chart h3{font-size:17px;margin:0 0 5px}.ltf-chart p{font-size:12px;color:#60748c;margin:0 0 12px}.ltf-scale{display:flex;justify-content:space-between;font-size:11px;color:#52687e;margin-top:5px}.ltf-colorbar{height:11px;border-radius:5px;background:linear-gradient(90deg,#23395d,#6886ab,#b4bfcb,#f7a342,#ee503c);margin-top:12px}
.ltf-callout{padding:11px 13px;background:#edf5f8;border-left:3px solid #14888e;border-radius:6px;font-size:12px;color:#33506b;margin-top:12px}
.ltf-hero{font-family:ui-sans-serif,system-ui,sans-serif;padding:29px 32px;border-radius:20px;background:linear-gradient(110deg,#10243e,#153c51);color:white;margin:0 0 24px}.ltf-card .ltf-hero h1{font-size:31px;margin:10px 0;letter-spacing:-.04em;color:#fff!important}.ltf-card .ltf-hero p{font-size:14px;color:#bed2e3!important;margin:7px 0}.ltf-hero .ltf-eyebrow{color:#75dcd2}.ltf-hero .ltf-chip{background:#ffffff12;color:#d9e9f1;border:1px solid #ffffff18;margin-top:12px}
@media(max-width:650px){.ltf-stat-grid{grid-template-columns:repeat(2,1fr)}.ltf-hero{padding:22px}.ltf-hero h1{font-size:25px}.ltf-decision{min-height:250px}.ltf-score{font-size:47px}}
@media(max-width:760px){
.gradio-container{padding:14px 12px 35px!important;min-width:0!important;width:100%!important;max-width:100vw!important}
.gradio-container .main{padding:0!important;min-width:0!important;width:100%!important}
.gradio-container .wrap,.gradio-container .contain{min-width:0!important;max-width:100%!important}
.gradio-container .row,.gradio-container .column,.gradio-container .block{min-width:0!important;max-width:100%!important}
}
"""
def card(content):
return f'<style>{CARD_CSS}</style><div class="ltf-card">{content}</div>'
HEADER = card("""<div class="ltf-hero"><div class="ltf-eyebrow">LTF / MODEL EXPLORER</div>
<h1>Image Detection & Attention Analysis</h1><p>Upload an original image to inspect model scores, tile attention, and patch details.</p>
<span class="ltf-chip">CLIP ViT-L/14</span><span class="ltf-chip">CLIP block 24 features</span>
<span class="ltf-chip">224 × 224 tiles</span><span class="ltf-chip">Research demo</span></div>""")
EMPTY_RESULT = card("""<div class="ltf-decision"><div class="ltf-eyebrow">ANALYSIS RESULT</div>
<h2>Ready to analyze</h2><div class="ltf-wait">Upload an image and select Run Analysis, or click a GenImage example.</div>
<p class="ltf-note">The prediction, generated score, and branch comparison will appear here. Attention analysis opens below.</p></div>""")
def result_card(result):
score = float(result["score_fake"])
generated = result["label"] == 1
accent = "#d75536" if generated else "#087f85"
verdict = "AI-generated image" if generated else "Real image"
branch = {
"fusion": "Fusion",
"level1": "L1 · Patch-level",
"level2": "L2 · View-level",
}[result["branch"]]
bars = ""
for key, label in (("fusion", "Fusion"), ("level1", "L1"), ("level2", "L2")):
value = result["probabilities"][key]["fake"]
bars += (
f'<div class="ltf-mini"><b>{label}</b><span><i style="width:{value*100:.3f}%"></i></span>'
f"<em>{value:.3f}</em></div>"
)
return card(f"""<div class="ltf-decision" style="--accent:{accent};border-top:4px solid {accent}">
<div class="ltf-eyebrow">Model prediction · {branch}</div><h2>{verdict}</h2>
<div class="ltf-score">{score:.3f}</div><div class="ltf-score-label">Generated score · threshold {result['threshold']:.2f}</div>
{bars}<div class="ltf-note">{result['width']:,} × {result['height']:,} px · {result['views']-1} local tiles
<br>Scores are uncalibrated model outputs, not definitive evidence of image origin.</div></div>""")
def stats_card(state):
if not state:
return ""
attention = state["attention"]
layer = next(
(row[2] for row in state["parameters"] if row[1] == "Feature extraction layer"),
"24",
)
stats = [
(str(len(state["boxes"])), "Local tiles + 1 global view"),
(str(attention["l1_heads"].shape[0]), "L1 aggregation heads"),
(
f"{attention['patch'].shape[1]} × {attention['patch'].shape[2]}",
"Patch grid per tile",
),
(str(layer), "CLIP feature block"),
]
return card(
'<div class="ltf-stat-grid">'
+ "".join(
f'<div class="ltf-stat"><strong>{escape(value)}</strong><span>{label}</span></div>'
for value, label in stats
)
+ "</div>"
)
def scale_legend():
return (
'<div class="ltf-colorbar"></div><div class="ltf-scale"><span>≤0.25×</span><span>0.5×</span>'
"<span>1× uniform</span><span>2×</span><span>≥4×</span></div>"
)
def weight_chart(state, selection="mean", level="l1"):
if not state:
return ""
attention = state["attention"]
if level == "l2":
weights = attention["l2"]
labels = ["Global"] + [f"T{i+1}" for i in range(len(weights) - 1)]
title = "L2 View Weights"
else:
weights = (
attention["l1"]
if selection == "mean"
else attention["l1_heads"][int(selection.split("_")[-1]) - 1]
)
labels = [f"T{i+1}" for i in range(len(weights))]
title = "L1 Tile Weights · " + (
"Mean of 8 heads" if selection == "mean" else selection.replace("_", " ")
)
n = len(weights)
order = np.argsort(-weights, kind="stable")[:10]
maximum = max(float(weights.max()), 1 / n) * 1.12
width, left, track, row_height = 430, 63, 290, 32
height = 38 + row_height * len(order)
baseline = left + track * (1 / n) / maximum
svg = [
f'<svg viewBox="0 0 {width} {height}" role="img" aria-label="{escape(title)}" style="width:100%;font-family:system-ui,sans-serif">',
f'<line x1="{baseline:.2f}" y1="8" x2="{baseline:.2f}" y2="{height-22}" stroke="#8394a8" stroke-dasharray="3 4"/>',
]
for rank, index in enumerate(order):
y, value = 12 + rank * row_height, float(weights[index])
fill = (
"#102e4b"
if labels[index] == "Global"
else ("#078b93" if rank == 0 else "#66aab1")
)
svg.extend(
[
f'<text x="0" y="{y+15}" fill="#30465e" font-size="13" font-weight="650">{labels[index]}</text>',
f'<rect x="{left}" y="{y}" width="{track}" height="21" rx="4" fill="#edf2f7"/>',
f'<rect x="{left}" y="{y}" width="{track*value/maximum:.3f}" height="21" rx="4" fill="{fill}">'
f"<title>{labels[index]}: {value:.8f} · {value*n:.4f}× uniform</title></rect>",
f'<text x="{left+track+8}" y="{y+15}" fill="#30465e" font-size="12">{value*100:.2f}%</text>',
]
)
svg.append(
f'<text x="{left}" y="{height-4}" fill="#60748c" font-size="10">Dashed: uniform {100/n:.2f}% · Top 10 shown</text></svg>'
)
ratio = float(weights.max()) * n
message = (
"Attention is nearly uniform, with no pronounced concentration. The heatmap does not exaggerate small differences."
if float(np.ptp(weights)) * n < 0.10
else f"The largest weight is {ratio:.2f}× uniform. Rankings show attention, not evidence of forgery."
)
if level == "l2":
message = (
f"The global view receives {float(weights[0])*100:.2f}%; the overlay shows local tiles only. "
+ message
)
return card(
f'<div class="ltf-chart"><h3>{title}</h3><p>Normalized attention weights sum to 100% across all items.</p>'
+ "".join(svg)
+ scale_legend()
+ f'<div class="ltf-callout">{message}</div></div>'
)
def patch_matrix(state, tile_number):
if not state:
return ""
from attention import attention_levels, colors
index = int(tile_number) - 1
weights = state["attention"]["patch"][index]
palette = colors(attention_levels(weights, weights.size))
h, w = weights.shape
cells = []
for y in range(h):
for x in range(w):
color = "#%02x%02x%02x" % tuple(palette[y, x])
value = float(weights[y, x])
cells.append(
f'<rect x="{x*20}" y="{y*20}" width="20" height="20" fill="{color}" stroke="#ffffff40" stroke-width=".6">'
f"<title>Row {y+1}, column {x+1} · {value*100:.5f}% · {value*weights.size:.3f}× uniform</title></rect>"
)
return card(
f'<div class="ltf-chart"><h3>T{index+1} · Patch Attention Matrix</h3><p>Hover to inspect exact weights. Each cell corresponds to 14 × 14 input pixels.</p>'
f'<svg viewBox="0 0 {w*20} {h*20}" role="img" aria-label="Patch attention matrix" style="width:100%;max-height:420px">'
+ "".join(cells)
+ "</svg>"
+ scale_legend()
+ '<div class="ltf-callout">Shared scale relative to uniform attention: gray is near uniform, blue is lower, and orange-red is higher.</div></div>'
)