Hayley Roberts commited on
Commit
159bb46
·
1 Parent(s): fbc5032

Integrate Euclid galaxy images and dating descriptions from HuggingFace datasets

Browse files
images/galaxy_01.jpg CHANGED
images/galaxy_02.jpg CHANGED
images/galaxy_03.jpg CHANGED
images/galaxy_04.jpg CHANGED
images/galaxy_05.jpg CHANGED
images/galaxy_06.jpg CHANGED
images/galaxy_07.jpg CHANGED
images/galaxy_08.jpg CHANGED
images/galaxy_09.jpg CHANGED
images/galaxy_10.jpg CHANGED
images/galaxy_11.jpg CHANGED
images/galaxy_12.jpg CHANGED
images/galaxy_13.jpg CHANGED
images/galaxy_14.jpg CHANGED
images/galaxy_15.jpg CHANGED
images/galaxy_16.jpg CHANGED
images/galaxy_17.jpg CHANGED
images/galaxy_18.jpg CHANGED
images/galaxy_19.jpg CHANGED
images/galaxy_20.jpg CHANGED
images/galaxy_21.jpg CHANGED
images/galaxy_22.jpg CHANGED
images/galaxy_23.jpg CHANGED
requirements.txt CHANGED
@@ -1,4 +1,7 @@
1
  dash==2.14.1
2
  dash-bootstrap-components==1.5.0
3
  gunicorn==21.2.0
 
4
  python-dotenv==1.1.1
 
 
 
1
  dash==2.14.1
2
  dash-bootstrap-components==1.5.0
3
  gunicorn==21.2.0
4
+ huggingface-hub==0.20.1
5
  python-dotenv==1.1.1
6
+ datasets==2.16.1
7
+ pillow==10.1.0
src/components.py CHANGED
@@ -279,7 +279,7 @@ def _create_star_field(n=80):
279
 
280
 
281
  def create_galaxy_card(galaxy_id, side="left", is_champion=False):
282
- """Build a single galaxy profile card -- image + name only."""
283
  profile = GALAXY_PROFILES[galaxy_id]
284
  btn_id = f"{side}-card-btn"
285
 
@@ -297,6 +297,9 @@ def create_galaxy_card(galaxy_id, side="left", is_champion=False):
297
  card_style["borderRadius"] = "12px"
298
  card_style["animation"] = "galaxyWin 1.5s ease-in-out"
299
 
 
 
 
300
  card_contents = [
301
  html.Img(
302
  src=f"/galaxy-images/{galaxy_id}.jpg",
@@ -316,7 +319,22 @@ def create_galaxy_card(galaxy_id, side="left", is_champion=False):
316
  ],
317
  className="galaxy-card-name",
318
  ),
 
 
 
 
 
 
 
 
 
 
 
 
319
  ]
 
 
 
320
 
321
  return html.Button(
322
  card_contents,
 
279
 
280
 
281
  def create_galaxy_card(galaxy_id, side="left", is_champion=False):
282
+ """Build a single galaxy profile card -- image + name + description."""
283
  profile = GALAXY_PROFILES[galaxy_id]
284
  btn_id = f"{side}-card-btn"
285
 
 
297
  card_style["borderRadius"] = "12px"
298
  card_style["animation"] = "galaxyWin 1.5s ease-in-out"
299
 
300
+ # Get description text
301
+ description = profile.get('description', profile.get('bio', ''))
302
+
303
  card_contents = [
304
  html.Img(
305
  src=f"/galaxy-images/{galaxy_id}.jpg",
 
319
  ],
320
  className="galaxy-card-name",
321
  ),
322
+ # Add description below the name
323
+ html.Div(
324
+ description,
325
+ className="galaxy-card-description",
326
+ style={
327
+ "fontSize": "0.8rem",
328
+ "color": "rgba(255,255,255,0.7)",
329
+ "padding": "8px 16px 16px",
330
+ "lineHeight": "1.4",
331
+ "fontFamily": "'Outfit', sans-serif",
332
+ }
333
+ ) if description else None,
334
  ]
335
+
336
+ # Filter out None elements
337
+ card_contents = [item for item in card_contents if item is not None]
338
 
339
  return html.Button(
340
  card_contents,
src/galaxy_data_loader.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load galaxy data from HuggingFace datasets and update profiles."""
2
+
3
+ import os
4
+ import logging
5
+ from typing import Dict, Any
6
+ import requests
7
+ from PIL import Image
8
+ import io
9
+
10
+ try:
11
+ from datasets import load_dataset
12
+ DATASETS_AVAILABLE = True
13
+ except ImportError:
14
+ DATASETS_AVAILABLE = False
15
+ logging.warning("datasets library not available, using fallback data")
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def load_galaxy_data() -> Dict[str, Dict[str, Any]]:
20
+ """Load galaxy data from HuggingFace datasets and return structured profiles."""
21
+
22
+ if not DATASETS_AVAILABLE:
23
+ logger.warning("Using fallback galaxy data")
24
+ return get_fallback_galaxy_data()
25
+
26
+ try:
27
+ # Load both datasets
28
+ euclid_dataset = load_dataset("mwalmsley/gz_euclid", "tiny", split="test")
29
+ descriptions_dataset = load_dataset("Smith42/dating_pool_but_galaxies", split="train")
30
+
31
+ # Convert to dictionaries for easier access
32
+ euclid_data = {row['id_str']: row for row in euclid_dataset}
33
+ descriptions_data = {row['id_str']: row for row in descriptions_dataset}
34
+
35
+ galaxy_profiles = {}
36
+
37
+ # Match on id_str and create profiles with numbered galaxy IDs
38
+ matched_galaxies = []
39
+ for galaxy_id_str in euclid_data.keys():
40
+ if galaxy_id_str in descriptions_data:
41
+ matched_galaxies.append(galaxy_id_str)
42
+
43
+ # Sort for consistent ordering
44
+ matched_galaxies.sort()
45
+
46
+ # Create numbered galaxy profiles (galaxy_01, galaxy_02, etc.)
47
+ for i, galaxy_id_str in enumerate(matched_galaxies, 1):
48
+ numbered_id = f"galaxy_{i:02d}"
49
+ euclid_row = euclid_data[galaxy_id_str]
50
+ desc_row = descriptions_data[galaxy_id_str]
51
+
52
+ # Extract name and description from caption
53
+ caption = desc_row.get('caption', '')
54
+ bio = ""
55
+ description = ""
56
+ name = f"Galaxy {i}"
57
+
58
+ # Parse the caption to extract bio and name if available
59
+ if caption:
60
+ lines = caption.split('\\n')
61
+ if lines and lines[0].startswith('Bio:'):
62
+ bio_part = lines[0].replace('Bio:', '').strip()
63
+ bio = bio_part[:100] + "..." if len(bio_part) > 100 else bio_part
64
+ description = bio_part
65
+ else:
66
+ description = caption.strip()
67
+ bio = description[:100] + "..." if len(description) > 100 else description
68
+
69
+ galaxy_profiles[numbered_id] = {
70
+ "name": name,
71
+ "bio": bio,
72
+ "description": description,
73
+ "tags": ["Cosmic", "Mysterious"],
74
+ "color": generate_color_from_id(galaxy_id_str),
75
+ "id_str": galaxy_id_str,
76
+ "image_data": euclid_row.get('image'), # PIL Image object
77
+ "euclid_features": {k: v for k, v in euclid_row.items() if k not in ['image', 'id_str']}
78
+ }
79
+
80
+ logger.info(f"Loaded {len(galaxy_profiles)} galaxy profiles from HuggingFace datasets")
81
+ return galaxy_profiles
82
+
83
+ except Exception as e:
84
+ logger.error(f"Error loading from HuggingFace datasets: {e}")
85
+ return get_fallback_galaxy_data()
86
+
87
+ def generate_color_from_id(id_str: str) -> str:
88
+ """Generate a consistent color from galaxy ID string."""
89
+ colors = [
90
+ "#A688C9", "#D68B8B", "#E8A0D0", "#7BC9A0", "#E87D5A",
91
+ "#6B8DD6", "#D4A76A", "#C9A688", "#B8A0C9", "#A0D4E8",
92
+ "#E8D4A0", "#A0E8B8", "#E8A0A0", "#A0A0E8", "#E8E8A0",
93
+ "#D0A0E8", "#A0E8D0", "#E8C4A0", "#C4A0E8", "#A0C4E8",
94
+ "#E8A0C4", "#B4E8A0", "#A0E8E8"
95
+ ]
96
+ # Use hash to get consistent color index
97
+ hash_val = sum(ord(c) for c in id_str)
98
+ return colors[hash_val % len(colors)]
99
+
100
+ def get_fallback_galaxy_data() -> Dict[str, Dict[str, Any]]:
101
+ """Fallback galaxy data using original profiles with numbered IDs."""
102
+ fallback_profiles = {
103
+ "galaxy_01": {
104
+ "name": "Velvet Vortex",
105
+ "bio": "Smooth operator with a soft spiral glow.",
106
+ "description": "I keep my arms tight and my luminosity low-key. Looking for someone who appreciates subtlety over spectacle.",
107
+ "tags": ["Smooth Spiral", "Low-Key", "Gentle Glow"],
108
+ "color": "#A688C9",
109
+ "image_url": None,
110
+ "image_data": None
111
+ },
112
+ "galaxy_02": {
113
+ "name": "Crimson Drift",
114
+ "bio": "Redshifted and proud.",
115
+ "description": "I have been expanding away from everyone for billions of years and I am NOT slowing down. Commitment-phobic? Maybe. Mysterious? Definitely.",
116
+ "tags": ["High Redshift", "Distant", "Loner Vibes"],
117
+ "color": "#D68B8B",
118
+ "image_url": None,
119
+ "image_data": None
120
+ }
121
+ # Add more as needed...
122
+ }
123
+
124
+ # Generate numbered galaxy profiles if we have fewer than 23
125
+ for i in range(1, 24):
126
+ galaxy_id = f"galaxy_{i:02d}"
127
+ if galaxy_id not in fallback_profiles:
128
+ fallback_profiles[galaxy_id] = {
129
+ "name": f"Galaxy {i}",
130
+ "bio": "A mysterious galaxy in the cosmic dating scene.",
131
+ "description": "This galaxy is looking for its perfect cosmic match.",
132
+ "tags": ["Mysterious", "Cosmic"],
133
+ "color": "#A688C9",
134
+ "image_url": None,
135
+ "image_data": None
136
+ }
137
+
138
+ return fallback_profiles
139
+
140
+ def download_galaxy_image(image_data, galaxy_id: str, images_dir: str = "images") -> bool:
141
+ """Download and save a galaxy image from HuggingFace dataset."""
142
+ if not image_data:
143
+ return False
144
+
145
+ try:
146
+ # Ensure images directory exists
147
+ os.makedirs(images_dir, exist_ok=True)
148
+
149
+ # If image_data is a PIL Image (from datasets)
150
+ if hasattr(image_data, 'save') and hasattr(image_data, 'mode'):
151
+ image_path = os.path.join(images_dir, f"{galaxy_id}.jpg")
152
+ # Convert to RGB if needed (in case it's RGBA or other format)
153
+ if image_data.mode != 'RGB':
154
+ image_data = image_data.convert('RGB')
155
+ image_data.save(image_path, 'JPEG', quality=85)
156
+ logger.info(f"Saved image for {galaxy_id}")
157
+ return True
158
+
159
+ # If image_data has a URL (fallback)
160
+ elif isinstance(image_data, dict) and 'url' in image_data:
161
+ response = requests.get(image_data['url'])
162
+ if response.status_code == 200:
163
+ image = Image.open(io.BytesIO(response.content))
164
+ if image.mode != 'RGB':
165
+ image = image.convert('RGB')
166
+ image_path = os.path.join(images_dir, f"{galaxy_id}.jpg")
167
+ image.save(image_path, 'JPEG', quality=85)
168
+ logger.info(f"Downloaded and saved image for {galaxy_id}")
169
+ return True
170
+
171
+ return False
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error downloading image for {galaxy_id}: {e}")
175
+ return False
176
+
177
+ def update_galaxy_images():
178
+ """Download all galaxy images from the dataset."""
179
+ galaxy_data = load_galaxy_data()
180
+
181
+ for galaxy_id, profile in galaxy_data.items():
182
+ if profile.get('image_data'):
183
+ download_galaxy_image(profile['image_data'], galaxy_id)
184
+ elif profile.get('image_url'):
185
+ # Try to download from URL
186
+ try:
187
+ response = requests.get(profile['image_url'])
188
+ if response.status_code == 200:
189
+ image = Image.open(io.BytesIO(response.content))
190
+ download_galaxy_image(image, galaxy_id)
191
+ except Exception as e:
192
+ logger.error(f"Error downloading from URL for {galaxy_id}: {e}")
193
+
194
+ if __name__ == "__main__":
195
+ # Test the data loading
196
+ data = load_galaxy_data()
197
+ print(f"Loaded {len(data)} galaxies")
198
+ for galaxy_id, profile in list(data.items())[:3]:
199
+ print(f"{galaxy_id}: {profile['name']} - {profile['bio'][:50]}...")
200
+
201
+ # Update images
202
+ update_galaxy_images()
src/galaxy_profiles.py CHANGED
@@ -1,146 +1,17 @@
1
- """Hardcoded galaxy dating profiles for the 23 tournament galaxies."""
2
 
3
- GALAXY_PROFILES = {
4
- "galaxy_01": {
5
- "name": "Velvet Vortex",
6
- "bio": "Smooth operator with a soft spiral glow. I keep my arms tight and my luminosity low-key. Looking for someone who appreciates subtlety over spectacle.",
7
- "tags": ["Smooth Spiral", "Low-Key", "Gentle Glow"],
8
- "color": "#A688C9",
9
- },
10
- "galaxy_02": {
11
- "name": "Crimson Drift",
12
- "bio": "Redshifted and proud. I have been expanding away from everyone for billions of years and I am NOT slowing down. Commitment-phobic? Maybe. Mysterious? Definitely.",
13
- "tags": ["High Redshift", "Distant", "Loner Vibes"],
14
- "color": "#D68B8B",
15
- },
16
- "galaxy_03": {
17
- "name": "Stardust Siren",
18
- "bio": "Bright core, hypnotic arms, and enough HII regions to light up a whole cluster. Warning: I will outshine your ex.",
19
- "tags": ["Star Forming", "Bright Core", "Show-Off"],
20
- "color": "#E8A0D0",
21
- },
22
- "galaxy_04": {
23
- "name": "Phantom Edge",
24
- "bio": "Edge-on and enigmatic. You can see my dust lane but you will never see my full face. Some call it mysterious, I call it good angles.",
25
- "tags": ["Edge-On", "Dust Lane", "Mysterious"],
26
- "color": "#7BC9A0",
27
- },
28
- "galaxy_05": {
29
- "name": "Nova Charm",
30
- "bio": "Compact, intense, and full of surprises. My surface brightness might be modest but my personality is SUPERNOVA. Size is just a number.",
31
- "tags": ["Compact", "Intense", "Underdog"],
32
- "color": "#E87D5A",
33
- },
34
- "galaxy_06": {
35
- "name": "Cosmic Flirt",
36
- "bio": "Slightly tilted, slightly chaotic, totally charming. I have got asymmetric arms and I am not afraid to use them. Swipe right if you like a little imperfection.",
37
- "tags": ["Asymmetric", "Charming", "Tilted"],
38
- "color": "#6B8DD6",
39
- },
40
- "galaxy_07": {
41
- "name": "Nebula Crush",
42
- "bio": "Face-on perfection. My spiral arms are textbook and my bar is prominent. I know what I bring to the gravitational table.",
43
- "tags": ["Face-On", "Barred Spiral", "Confident"],
44
- "color": "#D4A76A",
45
- },
46
- "galaxy_08": {
47
- "name": "Twilight Ember",
48
- "bio": "Fading gracefully but still got it. My stellar population is old and wise. Looking for someone who values maturity over flashy star formation.",
49
- "tags": ["Evolved", "Mature", "Elegant"],
50
- "color": "#C9A688",
51
- },
52
- "galaxy_09": {
53
- "name": "Galactic Riot",
54
- "bio": "Currently mid-merger and THRIVING. Tidal tails everywhere, stars flying, gas colliding. If you cannot handle me at my most disturbed you do not deserve me at my relaxed.",
55
- "tags": ["Merger", "Chaotic", "Tidal Tails"],
56
- "color": "#E85A7D",
57
- },
58
- "galaxy_10": {
59
- "name": "Sapphire Haze",
60
- "bio": "Diffuse, dreamy, and delightfully low surface brightness. You will need dark skies and patience to appreciate me. Worth it though.",
61
- "tags": ["Low Surface Brightness", "Dreamy", "Patient"],
62
- "color": "#88A6D6",
63
- },
64
- "galaxy_11": {
65
- "name": "Blaze Runner",
66
- "bio": "Starburst energy that will not quit. Forming stars at a reckless pace and producing galactic superwinds. Not here for a long time, here for a BRIGHT time.",
67
- "tags": ["Starburst", "Superwind", "Intense"],
68
- "color": "#E8A05A",
69
- },
70
- "galaxy_12": {
71
- "name": "Silk Spiral",
72
- "bio": "Grand design spiral with arms so well-defined they could be on a textbook cover. Photogenic from every angle. Yes, I am always this pretty.",
73
- "tags": ["Grand Design", "Photogenic", "Classic"],
74
- "color": "#A78BFA",
75
- },
76
- "galaxy_13": {
77
- "name": "Orbit Tease",
78
- "bio": "I have got a companion galaxy and we are in a complicated situationship. Tidal interactions keep things spicy. Open to new gravitational bonds.",
79
- "tags": ["Interacting", "Situationship", "Spicy"],
80
- "color": "#F472B6",
81
- },
82
- "galaxy_14": {
83
- "name": "Celestial Sage",
84
- "bio": "Elliptical and proud. No flashy spiral arms, just a smooth, uniform glow built over billions of years of mergers. I contain multitudes (of old stars).",
85
- "tags": ["Elliptical", "Old Soul", "Smooth"],
86
- "color": "#C9B688",
87
- },
88
- "galaxy_15": {
89
- "name": "Pixel Punk",
90
- "bio": "Small, faint, but with ATTITUDE. I may be barely resolved but my irregular morphology says I play by my own gravitational rules.",
91
- "tags": ["Irregular", "Rebellious", "Compact"],
92
- "color": "#5AE8C8",
93
- },
94
- "galaxy_16": {
95
- "name": "Halo Queen",
96
- "bio": "Extended halo, dominant bulge, and a dark matter envelope that goes on for megaparsecs. I take up space and I am not apologizing for it.",
97
- "tags": ["Massive Halo", "Dominant", "Expansive"],
98
- "color": "#D6A66B",
99
- },
100
- "galaxy_17": {
101
- "name": "Aurora Wisp",
102
- "bio": "Delicate and ethereal. My faint tidal features hint at a turbulent past but I have learned to glow softly. Healing era galaxy.",
103
- "tags": ["Tidal Features", "Ethereal", "Healing Era"],
104
- "color": "#88D6C9",
105
- },
106
- "galaxy_18": {
107
- "name": "Midnight Mirage",
108
- "bio": "Is that a ring? A shell? A tidal stream? Nobody can quite figure me out and I prefer it that way. Morphologically ambiguous and thriving.",
109
- "tags": ["Peculiar", "Ambiguous", "Enigmatic"],
110
- "color": "#8B6BD6",
111
- },
112
- "galaxy_19": {
113
- "name": "Solar Flare",
114
- "bio": "Blazing nucleus with jets that could cross a cluster. My AGN is active and so is my social life. Warning: I radiate in ALL wavelengths.",
115
- "tags": ["Active Nucleus", "Jets", "Radiant"],
116
- "color": "#E8D45A",
117
- },
118
- "galaxy_20": {
119
- "name": "Glimmer Ghost",
120
- "bio": "Ultra-diffuse and loving it. You could look right through me and miss the magic. Only the most dedicated observers get to know the real me.",
121
- "tags": ["Ultra-Diffuse", "Elusive", "Hidden Gem"],
122
- "color": "#A0C9E8",
123
- },
124
- "galaxy_21": {
125
- "name": "Quasar Babe",
126
- "bio": "Bright, bold, and unapologetically luminous. My central engine outshines entire galaxies. If you need someone low-maintenance, keep swiping.",
127
- "tags": ["Luminous", "Bold", "High Maintenance"],
128
- "color": "#D65A8B",
129
- },
130
- "galaxy_22": {
131
- "name": "Lenticular Lux",
132
- "bio": "Not quite spiral, not quite elliptical. I exist in the elegant in-between. My dust disk is subtle and my bulge is refined. Classy, never trashy.",
133
- "tags": ["Lenticular", "Refined", "Classy"],
134
- "color": "#C9A0E8",
135
- },
136
- "galaxy_23": {
137
- "name": "Cluster Crush",
138
- "bio": "Living my best life in a dense galaxy cluster. Surrounded by neighbors, feeding off the intracluster medium, and thriving in the chaos. Extrovert energy.",
139
- "tags": ["Cluster Member", "Social", "Extrovert"],
140
- "color": "#5AE87D",
141
- },
142
- }
143
 
 
 
 
 
 
144
  GALAXY_IDS = list(GALAXY_PROFILES.keys())
145
  NUM_GALAXIES = len(GALAXY_IDS)
146
  TOTAL_PAIRS = NUM_GALAXIES * (NUM_GALAXIES - 1) // 2
 
 
 
 
1
+ """Galaxy profiles loaded from HuggingFace datasets."""
2
 
3
+ import logging
4
+ from typing import Dict, List
5
+ from .galaxy_data_loader import load_galaxy_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ logger = logging.getLogger(__name__)
8
+
9
+ # Load galaxy profiles from HuggingFace datasets
10
+ _galaxy_data = load_galaxy_data()
11
+ GALAXY_PROFILES = _galaxy_data
12
  GALAXY_IDS = list(GALAXY_PROFILES.keys())
13
  NUM_GALAXIES = len(GALAXY_IDS)
14
  TOTAL_PAIRS = NUM_GALAXIES * (NUM_GALAXIES - 1) // 2
15
+
16
+ logger.info(f"Loaded {NUM_GALAXIES} galaxies from datasets")
17
+ logger.info(f"Galaxy IDs: {GALAXY_IDS[:5]}{'...' if len(GALAXY_IDS) > 5 else ''}")