"""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'
{content}
' HEADER = card("""
LTF / MODEL EXPLORER

Image Detection & Attention Analysis

Upload an original image to inspect model scores, tile attention, and patch details.

CLIP ViT-L/14CLIP block 24 features 224 × 224 tilesResearch demo
""") EMPTY_RESULT = card("""
ANALYSIS RESULT

Ready to analyze

Upload an image and select Run Analysis, or click a GenImage example.

The prediction, generated score, and branch comparison will appear here. Attention analysis opens below.

""") 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'
{label}' f"{value:.3f}
" ) return card(f"""
Model prediction · {branch}

{verdict}

{score:.3f}
Generated score · threshold {result['threshold']:.2f}
{bars}
{result['width']:,} × {result['height']:,} px · {result['views']-1} local tiles
Scores are uncalibrated model outputs, not definitive evidence of image origin.
""") 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( '
' + "".join( f'
{escape(value)}{label}
' for value, label in stats ) + "
" ) def scale_legend(): return ( '
≤0.25×0.5×' "1× uniform2×≥4×
" ) 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'', f'', ] 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'{labels[index]}', f'', f'' f"{labels[index]}: {value:.8f} · {value*n:.4f}× uniform", f'{value*100:.2f}%', ] ) svg.append( f'Dashed: uniform {100/n:.2f}% · Top 10 shown' ) 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'

{title}

Normalized attention weights sum to 100% across all items.

' + "".join(svg) + scale_legend() + f'
{message}
' ) 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'' f"Row {y+1}, column {x+1} · {value*100:.5f}% · {value*weights.size:.3f}× uniform" ) return card( f'

T{index+1} · Patch Attention Matrix

Hover to inspect exact weights. Each cell corresponds to 14 × 14 input pixels.

' f'' + "".join(cells) + "" + scale_legend() + '
Shared scale relative to uniform attention: gray is near uniform, blue is lower, and orange-red is higher.
' )