Alicia Zangger commited on
Commit
23a29ce
·
verified ·
1 Parent(s): c96095d

frontend: manage light/dark mode

Browse files
Files changed (4) hide show
  1. app.py +137 -87
  2. static/idiap-black.png +0 -0
  3. static/idiap-white.png +0 -0
  4. title.py +58 -0
app.py CHANGED
@@ -14,6 +14,7 @@ import torch.nn.functional as F
14
  from torchvision import transforms
15
 
16
  from utils import align_crop
 
17
 
18
  # ───────────────────────────────
19
  # Data & models
@@ -36,42 +37,33 @@ PRIMARY = "#F97316"
36
  PRIMARY_DARK = "#C2410C"
37
  ACCENT_LIGHT = "#FFEAD2"
38
  BG_LIGHT = "#FFFBF7"
 
 
39
  TEXT_DARK = "#0F172A"
 
40
 
41
  CSS = f"""
42
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
43
 
44
  /* ─── palette ───────────────────────────────────────────── */
45
- body {{
46
- font-family:'Inter',sans-serif;
47
- background:{BG_LIGHT};
48
- color:{TEXT_DARK};
49
  }}
50
 
51
  a {{
52
- color:{PRIMARY};
53
- text-decoration:none;
54
- font-weight:600;
55
  }}
56
- a:hover {{color:{PRIMARY_DARK}}}
57
 
58
  /* ─── headline ──────────────────────────────────────────── */
59
  #titlebar {{
60
- text-align:center;
61
- margin-top:2.4rem;
62
- margin-bottom:.9rem;
63
- }}
64
- #edgeface-title {{
65
- font-size:2.6rem;
66
- font-weight:800;
67
- margin:0;
68
- line-height:1.25;
69
- color: #0F172A;
70
- }}
71
- #edgeface-title .brand {{
72
- background:linear-gradient(90deg,{PRIMARY} 0%,{PRIMARY_DARK} 90%);
73
- -webkit-background-clip:text;
74
- color:transparent;
75
  }}
76
 
77
  /* ─── card look ─────────────────────────────────────────── */
@@ -79,94 +71,155 @@ a:hover {{color:{PRIMARY_DARK}}}
79
  .gr-box,
80
  .gr-row,
81
  #cite-wrapper {{
82
- border:1px solid #F8C89B;
83
- border-radius:10px;
84
- background:#fff;
85
- box-shadow:0 3px 6px rgba(0,0,0,.05);
86
  }}
87
- .gr-gallery-item {{background:#fff}}
 
88
 
89
  /* ─── controls / inputs ─────────────────────────────────── */
90
  .gr-button-primary,
91
  #copy-btn {{
92
- background:linear-gradient(90deg,{PRIMARY} 0%,{PRIMARY_DARK} 100%);
93
- border:none;
94
- color:#fff;
95
- border-radius:6px;
96
- font-weight:600;
97
- transition:transform .12s ease,box-shadow .12s ease;
98
  }}
 
99
  .gr-button-primary:hover,
100
  #copy-btn:hover {{
101
- transform:translateY(-2px);
102
- box-shadow:0 4px 12px rgba(249,115,22,.35);
 
 
 
 
103
  }}
104
- .gr-dropdown input {{border:1px solid {PRIMARY}99}}
105
 
106
  .preview img,
107
- .preview canvas {{object-fit:contain!important}}
108
 
109
  /* ─── hero section ─────────────────────────────────────── */
110
- #hero-wrapper {{text-align:center}}
111
-
112
  #hero-badge {{
113
- display:inline-block;
114
- padding:.85rem 1.2rem;
115
- border-radius:8px;
116
- background:{ACCENT_LIGHT};
117
- border:1px solid {PRIMARY}55;
118
- font-size:.95rem;
119
- font-weight:600;
120
- margin-bottom:.5rem;
121
  }}
122
 
123
  #hero-links {{
124
- font-size:.95rem;
125
- font-weight:600;
126
- margin-bottom:1.6rem;
127
  }}
 
128
  #hero-links img {{
129
- height:22px;
130
- vertical-align:middle;
131
- margin-left:.55rem;
132
  }}
133
 
134
  /* ─── score area ───────────────────────────────────────── */
135
  #score-area {{
136
- text-align:center; /* ← centres the badge */
 
 
 
 
 
 
 
 
137
  }}
138
-
139
  .match-badge {{
140
- display:inline-block;
141
- padding:.35rem .9rem;
142
- border-radius:9999px;
143
- font-weight:600;
144
- font-size:1.25rem; /* ← slightly larger */
145
  }}
146
 
147
  /* ─── citation card ���───────────────────────────────────── */
148
  #cite-wrapper {{
149
- position:relative;
150
- padding:.9rem 1rem;
151
- margin-top:2rem;
152
  }}
 
153
  #cite-wrapper code {{
154
  font-family: SFMono-Regular, Consolas, monospace;
155
  font-size: .84rem;
156
  white-space: pre-wrap;
157
- color: #0F172A;
158
  }}
 
159
  #copy-btn {{
160
- position:absolute;
161
- top:.55rem;
162
- right:.6rem;
163
- padding:.18rem .7rem;
164
- font-size:.72rem;
165
- line-height:1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  """
168
 
169
 
 
 
 
170
  # ───────────────────────────────
171
  # Torch / transforms
172
  # ───────────────────────────────
@@ -214,19 +267,14 @@ def compare(img_left,img_right,variant):
214
  # ───────────────────────────────
215
  # Static HTML
216
  # ───────────────────────────────
217
- TITLE_HTML = """
218
- <h1 id='edgeface-title'>
219
- <span class="brand">EdgeFace:</span> Efficient Face Recognition Model for Edge Devices
220
- </h1>
221
- """
222
 
223
  # <div id="hero-badge">
224
  # 🏆 Winner of IJCB 2023 Efficient Face Recognition Competition
225
  # </div><br/>
226
-
227
  HERO_HTML = f"""
228
  <div id="hero-wrapper">
229
-
230
  <div id="hero-links">
231
  <a href="https://www.idiap.ch/paper/edgeface/">Project</a>&nbsp;•&nbsp;
232
  <a href="https://publications.idiap.ch/attachments/papers/2024/George_IEEETBIOM_2024.pdf">Paper</a>&nbsp;•&nbsp;
@@ -255,26 +303,28 @@ CITATION_HTML = """
255
  # ───────────────────────────────
256
  # Gradio UI
257
  # ───────────────────────────────
258
- with gr.Blocks(css=CSS, title="EdgeFace Demo") as demo:
259
  gr.HTML(TITLE_HTML, elem_id="titlebar")
260
  gr.HTML(HERO_HTML)
261
 
262
  with gr.Row():
263
  gal_a = gr.Gallery(PRELOADED, columns=[5], height=120,
264
- label="Image A", object_fit="contain")
 
265
  gal_b = gr.Gallery(PRELOADED, columns=[5], height=120,
266
- label="Image B", object_fit="contain")
 
267
 
268
  with gr.Row():
269
  # img_a = gr.Image(type="numpy", height=300, label="Image A",
270
  # elem_classes="preview")
271
  # img_b = gr.Image(type="numpy", height=300, label="Image B",
272
  # elem_classes="preview")
273
-
274
  img_a = gr.Image(type="numpy", height=300, label="Image A (click or drag-drop)",
275
- interactive=True, elem_classes="preview")
276
  img_b = gr.Image(type="numpy", height=300, label="Image B (click or drag-drop)",
277
- interactive=True, elem_classes="preview")
278
 
279
  def _fill(evt: gr.SelectData):
280
  return _as_rgb(PRELOADED[evt.index]) if evt.index is not None else None
@@ -282,12 +332,12 @@ with gr.Blocks(css=CSS, title="EdgeFace Demo") as demo:
282
  gal_b.select(_fill, outputs=img_b)
283
 
284
  variant_dd = gr.Dropdown(EDGE_MODELS, value="edgeface_base",
285
- label="Model variant")
286
  btn = gr.Button("Compare", variant="primary")
287
 
288
  with gr.Row():
289
- out_a = gr.Image(label="Aligned A (112×112)")
290
- out_b = gr.Image(label="Aligned B (112×112)")
291
  score_html = gr.HTML(elem_id="score-area")
292
 
293
  btn.click(compare, [img_a, img_b, variant_dd],
 
14
  from torchvision import transforms
15
 
16
  from utils import align_crop
17
+ from title import title_css, title_with_logo
18
 
19
  # ───────────────────────────────
20
  # Data & models
 
37
  PRIMARY_DARK = "#C2410C"
38
  ACCENT_LIGHT = "#FFEAD2"
39
  BG_LIGHT = "#FFFBF7"
40
+ CARD_BG_DARK = "#473f38"
41
+ BG_DARK = "#332a22"
42
  TEXT_DARK = "#0F172A"
43
+ TEXT_LIGHT = "#f8fafc"
44
 
45
  CSS = f"""
46
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
47
 
48
  /* ─── palette ───────────────────────────────────────────── */
49
+ body, .gradio-container {{
50
+ font-family: 'Inter', sans-serif;
51
+ background: {BG_LIGHT};
52
+ color: {TEXT_DARK};
53
  }}
54
 
55
  a {{
56
+ color: {PRIMARY};
57
+ text-decoration: none;
58
+ font-weight: 600;
59
  }}
60
+ a:hover {{ color: {PRIMARY_DARK}; }}
61
 
62
  /* ─── headline ──────────────────────────────────────────── */
63
  #titlebar {{
64
+ text-align: center;
65
+ margin-top: 2.4rem;
66
+ margin-bottom: .9rem;
 
 
 
 
 
 
 
 
 
 
 
 
67
  }}
68
 
69
  /* ─── card look ─────────────────────────────────────────── */
 
71
  .gr-box,
72
  .gr-row,
73
  #cite-wrapper {{
74
+ border: 1px solid #F8C89B;
75
+ border-radius: 10px;
76
+ background: #fff;
77
+ box-shadow: 0 3px 6px rgba(0, 0, 0, .05);
78
  }}
79
+
80
+ .gr-gallery-item {{ background: #fff; }}
81
 
82
  /* ─── controls / inputs ─────────────────────────────────── */
83
  .gr-button-primary,
84
  #copy-btn {{
85
+ background: linear-gradient(90deg, {PRIMARY} 0%, {PRIMARY_DARK} 100%);
86
+ border: none;
87
+ color: #fff;
88
+ border-radius: 6px;
89
+ font-weight: 600;
90
+ transition: transform .12s ease, box-shadow .12s ease;
91
  }}
92
+
93
  .gr-button-primary:hover,
94
  #copy-btn:hover {{
95
+ transform: translateY(-2px);
96
+ box-shadow: 0 4px 12px rgba(249, 115, 22, .35);
97
+ }}
98
+
99
+ .gr-dropdown input {{
100
+ border: 1px solid {PRIMARY}99;
101
  }}
 
102
 
103
  .preview img,
104
+ .preview canvas {{ object-fit: contain !important; }}
105
 
106
  /* ─── hero section ─────────────────────────────────────── */
107
+ #hero-wrapper {{ text-align: center; }}
 
108
  #hero-badge {{
109
+ display: inline-block;
110
+ padding: .85rem 1.2rem;
111
+ border-radius: 8px;
112
+ background: {ACCENT_LIGHT};
113
+ border: 1px solid {PRIMARY}55;
114
+ font-size: .95rem;
115
+ font-weight: 600;
116
+ margin-bottom: .5rem;
117
  }}
118
 
119
  #hero-links {{
120
+ font-size: .95rem;
121
+ font-weight: 600;
122
+ margin-bottom: 1.6rem;
123
  }}
124
+
125
  #hero-links img {{
126
+ height: 22px;
127
+ vertical-align: middle;
128
+ margin-left: .55rem;
129
  }}
130
 
131
  /* ─── score area ───────────────────────────────────────── */
132
  #score-area {{
133
+ text-align: center;
134
+ }}
135
+ .title-container {{
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 12px;
139
+ justify-content: center;
140
+ margin-bottom: 10px;
141
+ text-align: center;
142
  }}
 
143
  .match-badge {{
144
+ display: inline-block;
145
+ padding: .35rem .9rem;
146
+ border-radius: 9999px;
147
+ font-weight: 600;
148
+ font-size: 1.25rem;
149
  }}
150
 
151
  /* ─── citation card ���───────────────────────────────────── */
152
  #cite-wrapper {{
153
+ position: relative;
154
+ padding: .9rem 1rem;
155
+ margin-top: 2rem;
156
  }}
157
+
158
  #cite-wrapper code {{
159
  font-family: SFMono-Regular, Consolas, monospace;
160
  font-size: .84rem;
161
  white-space: pre-wrap;
162
+ color: {TEXT_DARK};
163
  }}
164
+
165
  #copy-btn {{
166
+ position: absolute;
167
+ top: .55rem;
168
+ right: .6rem;
169
+ padding: .18rem .7rem;
170
+ font-size: .72rem;
171
+ line-height: 1;
172
+ }}
173
+
174
+ /* ─── dark mode ────────────────────────────────────── */
175
+ .dark body,
176
+ .dark .gradio-container {{
177
+ background-color: {BG_DARK};
178
+ color: #e5e7eb;
179
+ }}
180
+
181
+ .dark .gr-block,
182
+ .dark .gr-box,
183
+ .dark .gr-row {{
184
+ background-color: {BG_DARK};
185
+ border: 1px solid #4b5563;
186
+ }}
187
+
188
+ .dark .gr-dropdown input {{
189
+ background-color: {BG_DARK};
190
+ color: #f1f5f9;
191
+ border: 1px solid {PRIMARY}aa;
192
+ }}
193
+
194
+ .dark #hero-badge {{
195
+ background: #334155;
196
+ border: 1px solid {PRIMARY}55;
197
+ color: #fefefe;
198
  }}
199
+
200
+ .dark #cite-wrapper {{
201
+ background-color: {CARD_BG_DARK};
202
+
203
+ }}
204
+
205
+ .dark #bibtex {{
206
+ color: {TEXT_LIGHT} !important;
207
+ }}
208
+
209
+ .dark .card {{
210
+ background-color: {CARD_BG_DARK};
211
+ }}
212
+
213
+ /* ─── switch logo for light/dark theme ─────────────── */
214
+ .logo-dark {{ display: none; }}
215
+ .dark .logo-light {{ display: none; }}
216
+ .dark .logo-dark {{ display: inline; }}
217
  """
218
 
219
 
220
+ FULL_CSS = CSS + title_css(TEXT_DARK, PRIMARY, PRIMARY_DARK, TEXT_LIGHT)
221
+
222
+
223
  # ───────────────────────────────
224
  # Torch / transforms
225
  # ───────────────────────────────
 
267
  # ───────────────────────────────
268
  # Static HTML
269
  # ───────────────────────────────
270
+ TITLE_HTML = title_with_logo("""<span class="brand">EdgeFace:</span> Efficient Face Recognition Model for Edge Devices""")
 
 
 
 
271
 
272
  # <div id="hero-badge">
273
  # 🏆 Winner of IJCB 2023 Efficient Face Recognition Competition
274
  # </div><br/>
275
+
276
  HERO_HTML = f"""
277
  <div id="hero-wrapper">
 
278
  <div id="hero-links">
279
  <a href="https://www.idiap.ch/paper/edgeface/">Project</a>&nbsp;•&nbsp;
280
  <a href="https://publications.idiap.ch/attachments/papers/2024/George_IEEETBIOM_2024.pdf">Paper</a>&nbsp;•&nbsp;
 
303
  # ───────────────────────────────
304
  # Gradio UI
305
  # ───────────────────────────────
306
+ with gr.Blocks(css=FULL_CSS, title="EdgeFace Demo") as demo:
307
  gr.HTML(TITLE_HTML, elem_id="titlebar")
308
  gr.HTML(HERO_HTML)
309
 
310
  with gr.Row():
311
  gal_a = gr.Gallery(PRELOADED, columns=[5], height=120,
312
+ label="Image A", object_fit="contain",
313
+ elem_classes="card")
314
  gal_b = gr.Gallery(PRELOADED, columns=[5], height=120,
315
+ label="Image B", object_fit="contain",
316
+ elem_classes="card")
317
 
318
  with gr.Row():
319
  # img_a = gr.Image(type="numpy", height=300, label="Image A",
320
  # elem_classes="preview")
321
  # img_b = gr.Image(type="numpy", height=300, label="Image B",
322
  # elem_classes="preview")
323
+
324
  img_a = gr.Image(type="numpy", height=300, label="Image A (click or drag-drop)",
325
+ interactive=True, elem_classes="preview card")
326
  img_b = gr.Image(type="numpy", height=300, label="Image B (click or drag-drop)",
327
+ interactive=True, elem_classes="preview card")
328
 
329
  def _fill(evt: gr.SelectData):
330
  return _as_rgb(PRELOADED[evt.index]) if evt.index is not None else None
 
332
  gal_b.select(_fill, outputs=img_b)
333
 
334
  variant_dd = gr.Dropdown(EDGE_MODELS, value="edgeface_base",
335
+ label="Model variant", elem_classes="card")
336
  btn = gr.Button("Compare", variant="primary")
337
 
338
  with gr.Row():
339
+ out_a = gr.Image(label="Aligned A (112×112)", elem_classes="card")
340
+ out_b = gr.Image(label="Aligned B (112×112)", elem_classes="card")
341
  score_html = gr.HTML(elem_id="score-area")
342
 
343
  btn.click(compare, [img_a, img_b, variant_dd],
static/idiap-black.png ADDED
static/idiap-white.png ADDED
title.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2025 Idiap Research Institute
2
+ # SPDX-FileContributor: Zangger Alicia
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ import base64
6
+
7
+
8
+ def encode_image(path):
9
+ with open(path, "rb") as img:
10
+ return base64.b64encode(img.read()).decode("utf-8")
11
+
12
+
13
+ light_logo_b64 = encode_image("static/idiap-black.png")
14
+ dark_logo_b64 = encode_image("static/idiap-white.png")
15
+
16
+
17
+ def title_css(TEXT_DARK, PRIMARY, PRIMARY_DARK, TEXT_LIGHT):
18
+ return f"""
19
+ #title {{
20
+ font-size: 2.6rem;
21
+ font-weight: 800;
22
+ margin: 0;
23
+ line-height: 1.25;
24
+ color: {TEXT_DARK};
25
+ }}
26
+
27
+ /* brand class is passed in title parameter */
28
+ #title .brand {{
29
+ background: linear-gradient(90deg, {PRIMARY} 0%, {PRIMARY_DARK} 90%);
30
+ -webkit-background-clip: text;
31
+ color: transparent;
32
+ }}
33
+
34
+ .dark #title {{
35
+ color: {TEXT_LIGHT};
36
+ }}
37
+
38
+ .title-container {{
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 12px;
42
+ justify-content: center;
43
+ margin-bottom: 10px;
44
+ text-align: center;
45
+ }}
46
+ """
47
+
48
+
49
+ def title_with_logo(title):
50
+ return f"""
51
+ <div class="title-container">
52
+ <img class="logo-light" src="data:image/png;base64,{light_logo_b64}" alt="EdgeFace Logo Light" style="height: 50px;">
53
+ <img class="logo-dark" src="data:image/png;base64,{dark_logo_b64}" alt="EdgeFace Logo Dark" style="height: 50px;">
54
+ <h1 id="title" style="margin: 0;">
55
+ {title}
56
+ </h1>
57
+ </div>
58
+ """