lgoncalo commited on
Commit
5258014
·
1 Parent(s): b0876f8

Initial commit

Browse files
Files changed (3) hide show
  1. README.md +5 -5
  2. app.py +309 -0
  3. requirements.txt +4 -0
README.md CHANGED
@@ -1,12 +1,12 @@
1
  ---
2
- title: Improved Mood Palette Generator
3
- emoji: 🔥
4
- colorFrom: indigo
5
- colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 5.44.1
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Mood Palette Generator
3
+ emoji: 🎨
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 5.44.1
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Mood Palette Generator
11
  ---
12
 
 
app.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import os
4
+ from huggingface_hub import login
5
+ from sentence_transformers import SentenceTransformer, util
6
+ import pandas as pd
7
+ from datasets import load_dataset
8
+
9
+
10
+ # --- 1. CONFIGURATION ---
11
+ # Centralized place for all settings and constants.
12
+
13
+ class Config:
14
+ """Configuration settings for the application."""
15
+ EMBEDDING_MODEL_ID = "google/embeddinggemma-300M"
16
+ PROMPT_NAME = "STS"
17
+ TOP_K = 5
18
+ HF_TOKEN = os.getenv('HF_TOKEN')
19
+
20
+
21
+ # --- 2. COLOR DATA ---
22
+ # The color palette data is kept separate for clarity and easy modification.
23
+
24
+ ds = load_dataset("burkelibbey/colors")
25
+ df = pd.DataFrame(ds['train'])
26
+
27
+ df = df.rename(columns={"color": "hex"})
28
+
29
+ # Split column into two new ones
30
+ df[["name", "description"]] = df["description"].str.split(":", n=1, expand=True)
31
+ df["name"] = df["name"].str.title()
32
+
33
+ df = df.drop_duplicates(subset="name")
34
+
35
+ COLOR_DATA = df.to_dict(orient="records")
36
+
37
+
38
+ # --- 3. CORE LOGIC ---
39
+ # Encapsulated in a class to manage state (model, embeddings) cleanly.
40
+
41
+ class MoodPaletteGenerator:
42
+ """Handles model loading, embedding generation, and palette creation."""
43
+
44
+ def __init__(self, config: Config, color_data: list[dict[str, any]]):
45
+ """Initializes the generator, logs in, and loads necessary assets."""
46
+ self.config = config
47
+ self.color_data = color_data
48
+ self._login_to_hf()
49
+ self.embedding_model = self._load_model()
50
+ self.color_embeddings = self._precompute_color_embeddings()
51
+
52
+ def _login_to_hf(self):
53
+ """Logs into Hugging Face Hub if a token is provided."""
54
+ if self.config.HF_TOKEN:
55
+ print("Logging into Hugging Face Hub...")
56
+ login(token=self.config.HF_TOKEN)
57
+ else:
58
+ print("HF_TOKEN not found. Proceeding without login.")
59
+ print("Note: This may fail if the model is gated.")
60
+
61
+ def _load_model(self) -> SentenceTransformer:
62
+ """Loads the Sentence Transformer model."""
63
+ print(f"Initializing embedding model: {self.config.EMBEDDING_MODEL_ID}...")
64
+ try:
65
+ return SentenceTransformer(self.config.EMBEDDING_MODEL_ID)
66
+ except Exception as e:
67
+ print(f"Error loading model: {e}")
68
+ raise
69
+
70
+ def _precompute_color_embeddings(self) -> np.ndarray:
71
+ """Generates and stores embeddings for the color descriptions."""
72
+ print("Pre-computing embeddings for color palette...")
73
+ color_texts = [
74
+ f"{color['name']}, {color['description']}"
75
+ for color in self.color_data
76
+ ]
77
+ embeddings = self.embedding_model.encode(
78
+ color_texts,
79
+ prompt_name=self.config.PROMPT_NAME,
80
+ show_progress_bar=True
81
+ )
82
+ print("Embeddings computed successfully.")
83
+ return embeddings
84
+
85
+ def _get_text_color_for_bg(self, hex_color: str) -> str:
86
+ """
87
+ Calculates the luminance of a hex color and returns black ('#000000')
88
+ or white ('#FFFFFF') for the best text contrast.
89
+ """
90
+ hex_color = hex_color.lstrip('#')
91
+ try:
92
+ r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
93
+ luminance = (0.299 * r + 0.587 * g + 0.114 * b)
94
+ return '#000000' if luminance > 150 else '#FFFFFF'
95
+ except (ValueError, IndexError):
96
+ return '#000000' # Default to black on invalid hex
97
+
98
+ def _format_palette_as_html(self, top_hits: list[dict[str, any]]) -> str:
99
+ """Formats the top color hits into a displayable HTML string."""
100
+ if not top_hits:
101
+ return "<p>Could not generate a palette. Please try another mood.</p>"
102
+
103
+ cards_html = ""
104
+ for hit in top_hits:
105
+ color_info = self.color_data[hit['corpus_id']]
106
+ hex_code = color_info['hex']
107
+ name = color_info['name']
108
+ score = hit['score']
109
+ text_color = self._get_text_color_for_bg(hex_code)
110
+
111
+ cards_html += f"""
112
+ <div class="color-card" style="background-color: {hex_code}; color: {text_color};">
113
+ {name} | {hex_code} | Score: {score:.2f}
114
+ </div>
115
+ """
116
+ return f"<div class='palette-container'>{cards_html}</div>"
117
+
118
+ def _create_dynamic_theme_css(self, top_hits: list[dict[str, any]]) -> str:
119
+ """Generates a <style> block to override Gradio theme variables."""
120
+ theme_colors = []
121
+
122
+ if top_hits:
123
+ theme_colors = [
124
+ {
125
+ "bg": self.color_data[hit['corpus_id']]['hex'],
126
+ "txt": self._get_text_color_for_bg(self.color_data[hit['corpus_id']]['hex'])
127
+ }
128
+ for hit in top_hits
129
+ ]
130
+
131
+ css_map = {
132
+ "button-primary-background-fill": (0, 'bg'),
133
+ "button-primary-text-color": (0, 'txt'),
134
+ "button-secondary-background-fill": (1, 'bg'),
135
+ "button-secondary-text-color": (1, 'txt'),
136
+ "block-background-fill": (2, 'bg'),
137
+ "block-info-text-color": (2, 'txt'),
138
+ "block-title-background-fill": (3, 'bg'),
139
+ "button-primary-background-fill-hover": (3, 'bg'),
140
+ "block-title-text-color": (3, 'txt'),
141
+ "button-primary-text-color-hover": (3, 'txt'),
142
+ "button-secondary-background-fill-hover": (4, 'bg'),
143
+ "button-secondary-text-color-hover": (4, 'txt'),
144
+ }
145
+
146
+ css_rules = []
147
+ num_available_colors = len(theme_colors)
148
+
149
+ for var_suffix, (index, key) in css_map.items():
150
+ if index < num_available_colors:
151
+ color_value = theme_colors[index][key]
152
+ css_rules.append(f"--{var_suffix}: {color_value};")
153
+
154
+ css_rules_str = "\n".join(css_rules)
155
+
156
+ # Create CSS variables to inject
157
+ css = f"""
158
+ <style>
159
+ :root {{
160
+ {css_rules_str}
161
+ }}
162
+
163
+ :root .dark {{
164
+ {css_rules_str}
165
+ }}
166
+
167
+ .gallery-item .gallery {{
168
+ background: {theme_colors[4]['bg']};
169
+ color: {theme_colors[4]['txt']};
170
+ }}
171
+ </style>
172
+ """
173
+
174
+ return css
175
+
176
+ def generate_palette_and_theme(self, mood_text: str) -> tuple[str, str]:
177
+ """
178
+ Generates a color palette HTML and a dynamic theme CSS string.
179
+ """
180
+ if not mood_text or not mood_text.strip():
181
+ return "<p>Please enter a mood or a description.</p>", ""
182
+
183
+ mood_embedding = self.embedding_model.encode(
184
+ mood_text,
185
+ prompt_name=self.config.PROMPT_NAME
186
+ )
187
+ top_hits = util.semantic_search(
188
+ mood_embedding, self.color_embeddings, top_k=self.config.TOP_K
189
+ )[0]
190
+
191
+ palette_html = self._format_palette_as_html(top_hits)
192
+ theme_css = self._create_dynamic_theme_css(top_hits)
193
+
194
+ return palette_html, theme_css
195
+
196
+ def clear_theme(self) -> tuple[str, str]:
197
+ return "", ""
198
+
199
+
200
+ # --- 4. GRADIO UI ---
201
+ # Defines and launches the web interface.
202
+
203
+ def create_ui(generator: MoodPaletteGenerator):
204
+ """Creates the Gradio web interface."""
205
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
206
+ # This invisible component will hold our dynamic CSS
207
+ dynamic_css_output = gr.HTML()
208
+
209
+ gr.Markdown("""
210
+ # 🎨 Mood Palette Generator
211
+ Describe a mood, a scene, or a feeling, and get a matching color palette.<br>
212
+ **The UI theme will update to match your mood!**
213
+ """)
214
+
215
+ with gr.Row():
216
+ with gr.Column(scale=4):
217
+ mood_input = gr.Textbox(
218
+ value="Strawberry ice cream",
219
+ label="Enter Your Mood or Scene",
220
+ info="Be as descriptive as you like!"
221
+ )
222
+ with gr.Column(scale=1, min_width=150):
223
+ submit_button = gr.Button("Generate Palette", variant="primary")
224
+ clear_button = gr.Button("Clear", variant="secondary")
225
+
226
+ palette_output = gr.HTML(label="Your Generated Palette")
227
+
228
+ # Define CSS for palette cards here once, instead of in every update
229
+ gr.HTML("""
230
+ <style>
231
+ .palette-container {
232
+ display: flex; flex-direction: column; gap: 10px;
233
+ align-items: center; width: 100%;
234
+ q}
235
+ .color-card {
236
+ border-radius: 10px; text-align: center; padding: 15px 10px;
237
+ width: 90%; max-width: 400px;
238
+ box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
239
+ font-family: sans-serif; font-size: 16px; font-weight: bold;
240
+ transition: transform 0.2s;
241
+ }
242
+ .color-card:hover { transform: scale(1.05); }
243
+ </style>
244
+ """)
245
+
246
+ # Define the function to be called by events
247
+ event_handler = generator.generate_palette_and_theme
248
+ outputs_list = [palette_output, dynamic_css_output]
249
+
250
+ gr.Examples(
251
+ [
252
+ "A futuristic city at night, slick with rain",
253
+ "The feeling of a cozy cabin during a blizzard",
254
+ "Joyful chaos at a summer music festival",
255
+ "Beach sunset with the palm tree",
256
+ "A calm and lonely winter morning",
257
+ "Vintage romance in a dusty library",
258
+ "Cyberpunk alleyway with neon signs",
259
+ ],
260
+ inputs=mood_input,
261
+ outputs=outputs_list,
262
+ fn=event_handler,
263
+ run_on_click=True,
264
+ )
265
+
266
+ submit_button.click(
267
+ fn=event_handler,
268
+ inputs=mood_input,
269
+ outputs=outputs_list,
270
+ )
271
+ clear_button.click(
272
+ fn=generator.clear_theme,
273
+ outputs=outputs_list,
274
+ )
275
+ # Also allow submitting by pressing Enter in the textbox
276
+ mood_input.submit(
277
+ fn=event_handler,
278
+ inputs=mood_input,
279
+ outputs=outputs_list,
280
+ )
281
+
282
+ gr.Markdown("""
283
+ ----
284
+
285
+ ## What is this?
286
+
287
+ This interactive application, the **Mood Palette Generator**, transforms your words into a vibrant color palette. Simply describe a mood, a scene, or a feeling and the app will generate a set of matching colors. As a unique touch, the entire user interface dynamically updates its theme to reflect the generated palette, immersing you in your chosen mood.
288
+
289
+ ## How It Works?
290
+
291
+ At its core, this tool is powered by [**EmbeddingGemma**](http://huggingface.co/google/embeddinggemma-300M), a state-of-the-art text embedding model. The process works in a few simple steps:
292
+
293
+ 1. **Text to Vector**: When you enter a description, EmbeddingGemma converts your text into a numerical representation called an **embedding**. This embedding captures the semantic essence, or the "vibe" of your words.
294
+ 2. **Semantic Color Search**: The application has a pre-defined library of colors, where each color is associated with its own descriptive text and a pre-computed embedding.
295
+ 3. **Finding the Match**: Your input embedding is compared against the entire library of color embeddings to find the closest matches based on a similarity score.
296
+ 4. **Palette Creation**: The colors with the highest similarity scores are selected and presented to you as a complete palette.
297
+
298
+ The Mood Palette Generator is a perfect example of how embeddings can be used for creative applications beyond simple text search.
299
+ """)
300
+
301
+ return demo
302
+
303
+ if __name__ == "__main__":
304
+ # Initialize application components
305
+ generator = MoodPaletteGenerator(config=Config(), color_data=COLOR_DATA)
306
+
307
+ demo = create_ui(generator)
308
+ demo.launch()
309
+
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ sentence-transformers
2
+ datasets
3
+ pandas
4
+ git+https://github.com/huggingface/[email protected]