Switch to complete dark professional theme
Browse filesCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .streamlit/config.toml +7 -0
- app.py +123 -96
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[theme]
|
| 2 |
+
base = "dark"
|
| 3 |
+
primaryColor = "#4a9fd4"
|
| 4 |
+
backgroundColor = "#0e1b2e"
|
| 5 |
+
secondaryBackgroundColor = "#17283f"
|
| 6 |
+
textColor = "#e6eef7"
|
| 7 |
+
font = "sans serif"
|
app.py
CHANGED
|
@@ -24,44 +24,47 @@ st.set_page_config(
|
|
| 24 |
initial_sidebar_state="collapsed",
|
| 25 |
)
|
| 26 |
|
| 27 |
-
# ββ Global CSS ββββββββββββββββββββββββββββ
|
| 28 |
st.markdown("""
|
| 29 |
<style>
|
| 30 |
-
/* ββ
|
| 31 |
-
|
| 32 |
-
background-color: #
|
| 33 |
}
|
|
|
|
| 34 |
[data-testid="stHeader"] {
|
| 35 |
-
background-color: #
|
| 36 |
}
|
| 37 |
|
| 38 |
-
/*
|
| 39 |
.block-container {
|
| 40 |
-
padding-top:
|
| 41 |
-
padding-bottom:
|
| 42 |
-
max-width:
|
| 43 |
}
|
| 44 |
|
| 45 |
-
/* ββ
|
| 46 |
.ned-header {
|
| 47 |
-
background: linear-gradient(90deg, #
|
| 48 |
color: #ffffff;
|
| 49 |
-
padding: 20px
|
| 50 |
-
margin:
|
| 51 |
-
border-
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
.ned-header .university {
|
| 54 |
-
font-size: 1.
|
| 55 |
font-weight: 700;
|
| 56 |
letter-spacing: 0.4px;
|
| 57 |
color: #ffffff;
|
| 58 |
}
|
| 59 |
.ned-header .meta {
|
| 60 |
-
font-size: 0.
|
| 61 |
-
color: #
|
| 62 |
-
margin-top:
|
| 63 |
display: flex;
|
| 64 |
-
gap:
|
| 65 |
flex-wrap: wrap;
|
| 66 |
}
|
| 67 |
.ned-header .meta span::before {
|
|
@@ -69,101 +72,115 @@ html, body, [data-testid="stAppViewContainer"] {
|
|
| 69 |
color: #4a9fd4;
|
| 70 |
}
|
| 71 |
|
| 72 |
-
/* ββ
|
| 73 |
.ned-footer {
|
| 74 |
-
background: linear-gradient(90deg, #
|
| 75 |
-
color: #
|
| 76 |
-
padding:
|
| 77 |
-
margin:
|
| 78 |
-
border-
|
|
|
|
|
|
|
| 79 |
display: flex;
|
| 80 |
justify-content: space-between;
|
| 81 |
align-items: center;
|
| 82 |
flex-wrap: wrap;
|
| 83 |
-
gap:
|
| 84 |
font-size: 0.82rem;
|
| 85 |
}
|
| 86 |
-
.ned-footer strong { color: #
|
| 87 |
.ned-footer .ned-footer-right {
|
| 88 |
-
color: #
|
| 89 |
-
font-size: 0.
|
| 90 |
text-align: right;
|
| 91 |
}
|
| 92 |
|
| 93 |
-
/* ββ Content card / white surface ββ */
|
| 94 |
-
.content-surface {
|
| 95 |
-
background: #ffffff;
|
| 96 |
-
border-radius: 10px;
|
| 97 |
-
padding: 24px 28px;
|
| 98 |
-
margin-bottom: 20px;
|
| 99 |
-
box-shadow: 0 1px 4px rgba(26,46,80,0.08);
|
| 100 |
-
border: 1px solid #dce8f5;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
/* ββ Metric card ββ */
|
| 104 |
.metric-card {
|
| 105 |
-
background: #
|
| 106 |
-
border: 1px solid #
|
| 107 |
border-top: 3px solid #4a9fd4;
|
| 108 |
border-radius: 8px;
|
| 109 |
padding: 18px 16px;
|
| 110 |
text-align: center;
|
| 111 |
-
box-shadow: 0
|
| 112 |
}
|
| 113 |
.metric-card .metric-value {
|
| 114 |
-
font-size: 1.
|
| 115 |
font-weight: 700;
|
| 116 |
-
color: #
|
| 117 |
}
|
| 118 |
.metric-card .metric-label {
|
| 119 |
-
font-size: 0.
|
| 120 |
-
color: #
|
| 121 |
margin-top: 5px;
|
| 122 |
text-transform: uppercase;
|
| 123 |
-
letter-spacing: 0.
|
| 124 |
}
|
| 125 |
|
| 126 |
/* ββ Section headers ββ */
|
| 127 |
.section-header {
|
| 128 |
font-size: 1.05rem;
|
| 129 |
font-weight: 700;
|
| 130 |
-
color: #
|
| 131 |
border-bottom: 2px solid #4a9fd4;
|
| 132 |
padding-bottom: 6px;
|
| 133 |
-
margin:
|
| 134 |
text-transform: uppercase;
|
| 135 |
-
letter-spacing: 0.
|
| 136 |
}
|
| 137 |
|
| 138 |
/* ββ Info pill ββ */
|
| 139 |
.info-pill {
|
| 140 |
display: inline-block;
|
| 141 |
-
background: #
|
| 142 |
-
color: #
|
| 143 |
border-radius: 20px;
|
| 144 |
padding: 3px 12px;
|
| 145 |
font-size: 0.78rem;
|
| 146 |
font-weight: 600;
|
| 147 |
margin: 3px 4px 3px 0;
|
| 148 |
-
border: 1px solid #
|
| 149 |
}
|
| 150 |
|
| 151 |
-
/* ββ
|
| 152 |
[data-testid="stTabs"] [data-baseweb="tab-list"] {
|
| 153 |
-
background-color: #
|
| 154 |
border-radius: 8px 8px 0 0;
|
| 155 |
-
padding:
|
| 156 |
gap: 4px;
|
|
|
|
| 157 |
}
|
| 158 |
[data-testid="stTabs"] [data-baseweb="tab"] {
|
| 159 |
-
color: #
|
| 160 |
font-weight: 600;
|
| 161 |
border-radius: 6px 6px 0 0;
|
| 162 |
}
|
| 163 |
[data-testid="stTabs"] [aria-selected="true"] {
|
| 164 |
-
color: #
|
| 165 |
border-bottom: 3px solid #4a9fd4 !important;
|
| 166 |
-
background: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
}
|
| 168 |
</style>
|
| 169 |
""", unsafe_allow_html=True)
|
|
@@ -295,12 +312,12 @@ def make_architecture_figure():
|
|
| 295 |
ax.set_xlim(0, 11)
|
| 296 |
ax.set_ylim(0, 5.5)
|
| 297 |
ax.axis('off')
|
| 298 |
-
fig.patch.set_facecolor('#
|
| 299 |
|
| 300 |
def box(x, y, w, h, label, sub="", color="#2c4a7c", text_color="white", fontsize=9):
|
| 301 |
rect = FancyBboxPatch((x - w/2, y - h/2), w, h,
|
| 302 |
boxstyle="round,pad=0.08", linewidth=1.2,
|
| 303 |
-
edgecolor=
|
| 304 |
ax.add_patch(rect)
|
| 305 |
ax.text(x, y + (0.12 if sub else 0), label, ha='center', va='center',
|
| 306 |
color=text_color, fontsize=fontsize, fontweight='bold')
|
|
@@ -318,21 +335,21 @@ def make_architecture_figure():
|
|
| 318 |
box(0.8, 1.5, 1.3, 0.65, "ELA Input", "224Γ224Γ3", color="#4a9fd4")
|
| 319 |
|
| 320 |
# RGB branch
|
| 321 |
-
box(2.8, 4.0, 1.5, 0.65, "ResNet50", "frozen, ImageNet", color="#
|
| 322 |
-
box(4.5, 4.0, 1.5, 0.65, "GlobalAvgPool", "β 2048-d", color="#
|
| 323 |
|
| 324 |
# ELA branch
|
| 325 |
-
box(2.8, 2.8, 1.5, 0.65, "Conv2D 32", "3Γ3, ReLU+BN+Pool", color="#
|
| 326 |
-
box(2.8, 1.9, 1.5, 0.65, "Conv2D 64", "3Γ3, ReLU+BN+Pool", color="#
|
| 327 |
-
box(2.8, 1.0, 1.5, 0.65, "Conv2D 128", "3Γ3, ReLU+BN+Pool", color="#
|
| 328 |
-
box(4.5, 1.5, 1.5, 0.65, "GlobalAvgPool", "β 128-d", color="#
|
| 329 |
|
| 330 |
# Concat
|
| 331 |
-
box(6.4, 2.75, 1.3, 0.65, "Concatenate", "2176-d", color="#
|
| 332 |
|
| 333 |
# Dense head
|
| 334 |
-
box(8.0, 2.75, 1.3, 0.65, "Dense 256", "ReLU + Drop 0.5", color="#
|
| 335 |
-
box(9.8, 2.75, 1.1, 0.65, "Dense 1", "Sigmoid", color="#
|
| 336 |
|
| 337 |
# Arrows β RGB branch
|
| 338 |
arrow(1.45, 4.0, 2.05, 4.0)
|
|
@@ -365,18 +382,20 @@ def make_architecture_figure():
|
|
| 365 |
|
| 366 |
# Output label
|
| 367 |
ax.text(10.6, 2.75, "0 / 1\nAuth /\nForged",
|
| 368 |
-
ha='center', va='center', fontsize=8, color="#
|
| 369 |
|
| 370 |
# Legend
|
| 371 |
legend_items = [
|
| 372 |
mpatches.Patch(color="#4a9fd4", label="Input"),
|
| 373 |
-
mpatches.Patch(color="#
|
| 374 |
-
mpatches.Patch(color="#
|
| 375 |
-
mpatches.Patch(color="#
|
| 376 |
-
mpatches.Patch(color="#
|
| 377 |
]
|
| 378 |
-
ax.legend(handles=legend_items, loc='lower center', ncol=5,
|
| 379 |
-
|
|
|
|
|
|
|
| 380 |
|
| 381 |
fig.tight_layout(pad=0.4)
|
| 382 |
buf = io.BytesIO()
|
|
@@ -638,8 +657,8 @@ Tampered pixels were re-saved at a different quality level; the diff reveals tho
|
|
| 638 |
with perf_col2:
|
| 639 |
# Score distribution bar chart
|
| 640 |
fig2, ax2 = plt.subplots(figsize=(5, 2.8))
|
| 641 |
-
fig2.patch.set_facecolor('#
|
| 642 |
-
ax2.set_facecolor('#
|
| 643 |
|
| 644 |
bins = np.linspace(0, 1, 21)
|
| 645 |
au_scores = np.concatenate([
|
|
@@ -650,14 +669,17 @@ Tampered pixels were re-saved at a different quality level; the diff reveals tho
|
|
| 650 |
np.random.default_rng(2).beta(30, 0.4, 100),
|
| 651 |
np.random.default_rng(2).uniform(0.0, 0.5, 10),
|
| 652 |
])
|
| 653 |
-
ax2.hist(au_scores, bins=bins, alpha=0.
|
| 654 |
-
ax2.hist(tp_scores, bins=bins, alpha=0.
|
| 655 |
-
ax2.axvline(0.5, color='#
|
| 656 |
-
ax2.set_xlabel("Model score", fontsize=8)
|
| 657 |
-
ax2.set_ylabel("Count", fontsize=8)
|
| 658 |
-
ax2.set_title("Illustrative score distribution", fontsize=8.5,
|
| 659 |
-
|
| 660 |
-
ax2.
|
|
|
|
|
|
|
|
|
|
| 661 |
fig2.tight_layout(pad=0.5)
|
| 662 |
|
| 663 |
buf2 = io.BytesIO()
|
|
@@ -690,20 +712,25 @@ ResNet50 backbone provides complementary texture context but does not dominate.
|
|
| 690 |
|
| 691 |
with abl_col2:
|
| 692 |
fig3, ax3 = plt.subplots(figsize=(4.5, 2.6))
|
| 693 |
-
fig3.patch.set_facecolor('#
|
| 694 |
-
ax3.set_facecolor('#
|
| 695 |
variants = ['M1\nRGB only', 'M2\nELA only', 'M3\nDual-branch']
|
| 696 |
aucs = [0.5822, 0.9807, 0.9774]
|
| 697 |
-
colors = ['#
|
| 698 |
-
bars = ax3.bar(variants, aucs, color=colors, width=0.5,
|
|
|
|
| 699 |
ax3.set_ylim(0.4, 1.05)
|
| 700 |
-
ax3.axhline(1.0, color='#
|
| 701 |
for bar, val in zip(bars, aucs):
|
| 702 |
ax3.text(bar.get_x() + bar.get_width()/2, val + 0.01,
|
| 703 |
-
f"{val:.4f}", ha='center', va='bottom', fontsize=8,
|
| 704 |
-
|
| 705 |
-
ax3.
|
| 706 |
-
ax3.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
fig3.tight_layout(pad=0.5)
|
| 708 |
buf3 = io.BytesIO()
|
| 709 |
fig3.savefig(buf3, format='png', dpi=120, bbox_inches='tight',
|
|
|
|
| 24 |
initial_sidebar_state="collapsed",
|
| 25 |
)
|
| 26 |
|
| 27 |
+
# ββ Global CSS β complete dark professional theme ββββββββββββββββββββββββββββ
|
| 28 |
st.markdown("""
|
| 29 |
<style>
|
| 30 |
+
/* ββ Base surfaces ββ */
|
| 31 |
+
[data-testid="stAppViewContainer"], .main, .stApp {
|
| 32 |
+
background-color: #0e1b2e !important;
|
| 33 |
}
|
| 34 |
+
/* Streamlit's own fixed top header β make it match so nothing clashes */
|
| 35 |
[data-testid="stHeader"] {
|
| 36 |
+
background-color: #0e1b2e !important;
|
| 37 |
}
|
| 38 |
|
| 39 |
+
/* Keep clear of Streamlit's fixed top header so our banner isn't clipped */
|
| 40 |
.block-container {
|
| 41 |
+
padding-top: 2.2rem !important;
|
| 42 |
+
padding-bottom: 1rem;
|
| 43 |
+
max-width: 1180px;
|
| 44 |
}
|
| 45 |
|
| 46 |
+
/* ββ Header banner ββ */
|
| 47 |
.ned-header {
|
| 48 |
+
background: linear-gradient(90deg, #122842 0%, #1a3a63 55%, #155674 100%);
|
| 49 |
color: #ffffff;
|
| 50 |
+
padding: 20px 32px 18px 32px;
|
| 51 |
+
margin: 0 0 22px 0;
|
| 52 |
+
border-radius: 10px;
|
| 53 |
+
border-left: 5px solid #4a9fd4;
|
| 54 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.35);
|
| 55 |
}
|
| 56 |
.ned-header .university {
|
| 57 |
+
font-size: 1.25rem;
|
| 58 |
font-weight: 700;
|
| 59 |
letter-spacing: 0.4px;
|
| 60 |
color: #ffffff;
|
| 61 |
}
|
| 62 |
.ned-header .meta {
|
| 63 |
+
font-size: 0.86rem;
|
| 64 |
+
color: #8fc1e6;
|
| 65 |
+
margin-top: 6px;
|
| 66 |
display: flex;
|
| 67 |
+
gap: 26px;
|
| 68 |
flex-wrap: wrap;
|
| 69 |
}
|
| 70 |
.ned-header .meta span::before {
|
|
|
|
| 72 |
color: #4a9fd4;
|
| 73 |
}
|
| 74 |
|
| 75 |
+
/* ββ Footer banner ββ */
|
| 76 |
.ned-footer {
|
| 77 |
+
background: linear-gradient(90deg, #122842 0%, #1a3a63 55%, #155674 100%);
|
| 78 |
+
color: #8fc1e6;
|
| 79 |
+
padding: 16px 32px;
|
| 80 |
+
margin: 30px 0 8px 0;
|
| 81 |
+
border-radius: 10px;
|
| 82 |
+
border-left: 5px solid #4a9fd4;
|
| 83 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.35);
|
| 84 |
display: flex;
|
| 85 |
justify-content: space-between;
|
| 86 |
align-items: center;
|
| 87 |
flex-wrap: wrap;
|
| 88 |
+
gap: 10px;
|
| 89 |
font-size: 0.82rem;
|
| 90 |
}
|
| 91 |
+
.ned-footer strong { color: #d7e8f6; }
|
| 92 |
.ned-footer .ned-footer-right {
|
| 93 |
+
color: #6a93b8;
|
| 94 |
+
font-size: 0.74rem;
|
| 95 |
text-align: right;
|
| 96 |
}
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
/* ββ Metric card ββ */
|
| 99 |
.metric-card {
|
| 100 |
+
background: #17283f;
|
| 101 |
+
border: 1px solid #2a435f;
|
| 102 |
border-top: 3px solid #4a9fd4;
|
| 103 |
border-radius: 8px;
|
| 104 |
padding: 18px 16px;
|
| 105 |
text-align: center;
|
| 106 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
| 107 |
}
|
| 108 |
.metric-card .metric-value {
|
| 109 |
+
font-size: 1.95rem;
|
| 110 |
font-weight: 700;
|
| 111 |
+
color: #6fb8e8;
|
| 112 |
}
|
| 113 |
.metric-card .metric-label {
|
| 114 |
+
font-size: 0.76rem;
|
| 115 |
+
color: #9fb6cf;
|
| 116 |
margin-top: 5px;
|
| 117 |
text-transform: uppercase;
|
| 118 |
+
letter-spacing: 0.5px;
|
| 119 |
}
|
| 120 |
|
| 121 |
/* ββ Section headers ββ */
|
| 122 |
.section-header {
|
| 123 |
font-size: 1.05rem;
|
| 124 |
font-weight: 700;
|
| 125 |
+
color: #cfe4f6;
|
| 126 |
border-bottom: 2px solid #4a9fd4;
|
| 127 |
padding-bottom: 6px;
|
| 128 |
+
margin: 28px 0 14px 0;
|
| 129 |
text-transform: uppercase;
|
| 130 |
+
letter-spacing: 0.6px;
|
| 131 |
}
|
| 132 |
|
| 133 |
/* ββ Info pill ββ */
|
| 134 |
.info-pill {
|
| 135 |
display: inline-block;
|
| 136 |
+
background: #1c3a59;
|
| 137 |
+
color: #8fc1e6;
|
| 138 |
border-radius: 20px;
|
| 139 |
padding: 3px 12px;
|
| 140 |
font-size: 0.78rem;
|
| 141 |
font-weight: 600;
|
| 142 |
margin: 3px 4px 3px 0;
|
| 143 |
+
border: 1px solid #2f5478;
|
| 144 |
}
|
| 145 |
|
| 146 |
+
/* ββ Tab strip ββ */
|
| 147 |
[data-testid="stTabs"] [data-baseweb="tab-list"] {
|
| 148 |
+
background-color: #142336;
|
| 149 |
border-radius: 8px 8px 0 0;
|
| 150 |
+
padding: 5px 8px 0 8px;
|
| 151 |
gap: 4px;
|
| 152 |
+
border-bottom: 1px solid #2a435f;
|
| 153 |
}
|
| 154 |
[data-testid="stTabs"] [data-baseweb="tab"] {
|
| 155 |
+
color: #8da7c2;
|
| 156 |
font-weight: 600;
|
| 157 |
border-radius: 6px 6px 0 0;
|
| 158 |
}
|
| 159 |
[data-testid="stTabs"] [aria-selected="true"] {
|
| 160 |
+
color: #6fb8e8 !important;
|
| 161 |
border-bottom: 3px solid #4a9fd4 !important;
|
| 162 |
+
background: #17283f !important;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
/* ββ Tables ββ */
|
| 166 |
+
.stMarkdown table {
|
| 167 |
+
border-collapse: collapse;
|
| 168 |
+
width: 100%;
|
| 169 |
+
}
|
| 170 |
+
.stMarkdown table th {
|
| 171 |
+
background-color: #1a3050 !important;
|
| 172 |
+
color: #cfe4f6 !important;
|
| 173 |
+
border: 1px solid #2a435f !important;
|
| 174 |
+
}
|
| 175 |
+
.stMarkdown table td {
|
| 176 |
+
background-color: #142336 !important;
|
| 177 |
+
color: #d3e0ee !important;
|
| 178 |
+
border: 1px solid #2a435f !important;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/* ββ Code blocks ββ */
|
| 182 |
+
.stCodeBlock, pre {
|
| 183 |
+
background-color: #0a1626 !important;
|
| 184 |
}
|
| 185 |
</style>
|
| 186 |
""", unsafe_allow_html=True)
|
|
|
|
| 312 |
ax.set_xlim(0, 11)
|
| 313 |
ax.set_ylim(0, 5.5)
|
| 314 |
ax.axis('off')
|
| 315 |
+
fig.patch.set_facecolor('#0e1b2e')
|
| 316 |
|
| 317 |
def box(x, y, w, h, label, sub="", color="#2c4a7c", text_color="white", fontsize=9):
|
| 318 |
rect = FancyBboxPatch((x - w/2, y - h/2), w, h,
|
| 319 |
boxstyle="round,pad=0.08", linewidth=1.2,
|
| 320 |
+
edgecolor="#4a9fd4", facecolor=color)
|
| 321 |
ax.add_patch(rect)
|
| 322 |
ax.text(x, y + (0.12 if sub else 0), label, ha='center', va='center',
|
| 323 |
color=text_color, fontsize=fontsize, fontweight='bold')
|
|
|
|
| 335 |
box(0.8, 1.5, 1.3, 0.65, "ELA Input", "224Γ224Γ3", color="#4a9fd4")
|
| 336 |
|
| 337 |
# RGB branch
|
| 338 |
+
box(2.8, 4.0, 1.5, 0.65, "ResNet50", "frozen, ImageNet", color="#2e7bb0")
|
| 339 |
+
box(4.5, 4.0, 1.5, 0.65, "GlobalAvgPool", "β 2048-d", color="#2e7bb0")
|
| 340 |
|
| 341 |
# ELA branch
|
| 342 |
+
box(2.8, 2.8, 1.5, 0.65, "Conv2D 32", "3Γ3, ReLU+BN+Pool", color="#2a8f56")
|
| 343 |
+
box(2.8, 1.9, 1.5, 0.65, "Conv2D 64", "3Γ3, ReLU+BN+Pool", color="#2a8f56")
|
| 344 |
+
box(2.8, 1.0, 1.5, 0.65, "Conv2D 128", "3Γ3, ReLU+BN+Pool", color="#2a8f56")
|
| 345 |
+
box(4.5, 1.5, 1.5, 0.65, "GlobalAvgPool", "β 128-d", color="#2a8f56")
|
| 346 |
|
| 347 |
# Concat
|
| 348 |
+
box(6.4, 2.75, 1.3, 0.65, "Concatenate", "2176-d", color="#9b59b6")
|
| 349 |
|
| 350 |
# Dense head
|
| 351 |
+
box(8.0, 2.75, 1.3, 0.65, "Dense 256", "ReLU + Drop 0.5", color="#8e44ad")
|
| 352 |
+
box(9.8, 2.75, 1.1, 0.65, "Dense 1", "Sigmoid", color="#c0392b")
|
| 353 |
|
| 354 |
# Arrows β RGB branch
|
| 355 |
arrow(1.45, 4.0, 2.05, 4.0)
|
|
|
|
| 382 |
|
| 383 |
# Output label
|
| 384 |
ax.text(10.6, 2.75, "0 / 1\nAuth /\nForged",
|
| 385 |
+
ha='center', va='center', fontsize=8, color="#e8a0a0", fontweight='bold')
|
| 386 |
|
| 387 |
# Legend
|
| 388 |
legend_items = [
|
| 389 |
mpatches.Patch(color="#4a9fd4", label="Input"),
|
| 390 |
+
mpatches.Patch(color="#2e7bb0", label="RGB Branch (ResNet50)"),
|
| 391 |
+
mpatches.Patch(color="#2a8f56", label="ELA Branch (Custom CNN)"),
|
| 392 |
+
mpatches.Patch(color="#9b59b6", label="Fusion"),
|
| 393 |
+
mpatches.Patch(color="#c0392b", label="Output Head"),
|
| 394 |
]
|
| 395 |
+
leg = ax.legend(handles=legend_items, loc='lower center', ncol=5,
|
| 396 |
+
fontsize=7.5, framealpha=0.0, bbox_to_anchor=(0.48, -0.02),
|
| 397 |
+
labelcolor='#c8dff0')
|
| 398 |
+
leg.get_frame().set_edgecolor('#2a435f')
|
| 399 |
|
| 400 |
fig.tight_layout(pad=0.4)
|
| 401 |
buf = io.BytesIO()
|
|
|
|
| 657 |
with perf_col2:
|
| 658 |
# Score distribution bar chart
|
| 659 |
fig2, ax2 = plt.subplots(figsize=(5, 2.8))
|
| 660 |
+
fig2.patch.set_facecolor('#0e1b2e')
|
| 661 |
+
ax2.set_facecolor('#142336')
|
| 662 |
|
| 663 |
bins = np.linspace(0, 1, 21)
|
| 664 |
au_scores = np.concatenate([
|
|
|
|
| 669 |
np.random.default_rng(2).beta(30, 0.4, 100),
|
| 670 |
np.random.default_rng(2).uniform(0.0, 0.5, 10),
|
| 671 |
])
|
| 672 |
+
ax2.hist(au_scores, bins=bins, alpha=0.8, color='#2ecc71', label='Authentic')
|
| 673 |
+
ax2.hist(tp_scores, bins=bins, alpha=0.8, color='#e74c3c', label='Forged')
|
| 674 |
+
ax2.axvline(0.5, color='#e6eef7', lw=1.5, linestyle='--', label='Threshold 0.5')
|
| 675 |
+
ax2.set_xlabel("Model score", fontsize=8, color='#c8dff0')
|
| 676 |
+
ax2.set_ylabel("Count", fontsize=8, color='#c8dff0')
|
| 677 |
+
ax2.set_title("Illustrative score distribution", fontsize=8.5,
|
| 678 |
+
fontweight='bold', color='#cfe4f6')
|
| 679 |
+
leg2 = ax2.legend(fontsize=7.5, framealpha=0.0, labelcolor='#c8dff0')
|
| 680 |
+
ax2.tick_params(labelsize=7, colors='#9fb6cf')
|
| 681 |
+
for spine in ax2.spines.values():
|
| 682 |
+
spine.set_color('#2a435f')
|
| 683 |
fig2.tight_layout(pad=0.5)
|
| 684 |
|
| 685 |
buf2 = io.BytesIO()
|
|
|
|
| 712 |
|
| 713 |
with abl_col2:
|
| 714 |
fig3, ax3 = plt.subplots(figsize=(4.5, 2.6))
|
| 715 |
+
fig3.patch.set_facecolor('#0e1b2e')
|
| 716 |
+
ax3.set_facecolor('#142336')
|
| 717 |
variants = ['M1\nRGB only', 'M2\nELA only', 'M3\nDual-branch']
|
| 718 |
aucs = [0.5822, 0.9807, 0.9774]
|
| 719 |
+
colors = ['#7f8c9a', '#2ecc71', '#4a9fd4']
|
| 720 |
+
bars = ax3.bar(variants, aucs, color=colors, width=0.5,
|
| 721 |
+
edgecolor='#0e1b2e', linewidth=0.8)
|
| 722 |
ax3.set_ylim(0.4, 1.05)
|
| 723 |
+
ax3.axhline(1.0, color='#3a5575', lw=0.8, linestyle=':')
|
| 724 |
for bar, val in zip(bars, aucs):
|
| 725 |
ax3.text(bar.get_x() + bar.get_width()/2, val + 0.01,
|
| 726 |
+
f"{val:.4f}", ha='center', va='bottom', fontsize=8,
|
| 727 |
+
fontweight='bold', color='#cfe4f6')
|
| 728 |
+
ax3.set_ylabel("AUC-ROC", fontsize=8, color='#c8dff0')
|
| 729 |
+
ax3.set_title("Branch Ablation β AUC-ROC", fontsize=8.5,
|
| 730 |
+
fontweight='bold', color='#cfe4f6')
|
| 731 |
+
ax3.tick_params(labelsize=7.5, colors='#9fb6cf')
|
| 732 |
+
for spine in ax3.spines.values():
|
| 733 |
+
spine.set_color('#2a435f')
|
| 734 |
fig3.tight_layout(pad=0.5)
|
| 735 |
buf3 = io.BytesIO()
|
| 736 |
fig3.savefig(buf3, format='png', dpi=120, bbox_inches='tight',
|