|
import gradio as gr |
|
import torch |
|
from transformers import pipeline, AutoFeatureExtractor, AutoModelForImageClassification |
|
from PIL import Image |
|
import requests |
|
import io |
|
import numpy as np |
|
import pandas as pd |
|
import matplotlib.pyplot as plt |
|
from matplotlib.colors import LogNorm |
|
import os |
|
from datetime import datetime, timedelta |
|
import json |
|
import google.generativeai as genai |
|
|
|
|
|
NASA_API_KEY = "DEMO_KEY" |
|
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") |
|
APOD_URL = "https://api.nasa.gov/planetary/apod" |
|
CELESTIAL_BODIES = ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto"] |
|
CELESTIAL_OBJECTS = ["Galaxy", "Nebula", "Star Cluster", "Supernova Remnant", "Black Hole", "Quasar", "Pulsar"] |
|
|
|
|
|
try: |
|
|
|
feature_extractor = AutoFeatureExtractor.from_pretrained("matthewberryman/astronomy-image-classifier") |
|
model = AutoModelForImageClassification.from_pretrained("matthewberryman/astronomy-image-classifier") |
|
|
|
|
|
caption_model = pipeline("image-to-text", model="Salesforce/blip-image-captioning-large") |
|
|
|
|
|
if GEMINI_API_KEY: |
|
genai.configure(api_key=GEMINI_API_KEY) |
|
|
|
gemini_model = genai.GenerativeModel('gemini-2.0-flash') |
|
gemini_text_model = genai.GenerativeModel('gemini-2.0-flash') |
|
print("Gemini models initialized successfully") |
|
else: |
|
gemini_model = None |
|
gemini_text_model = None |
|
print("Gemini API key not found. Advanced features will be disabled.") |
|
except Exception as e: |
|
print(f"Model loading error: {e}") |
|
|
|
caption_model = None |
|
gemini_model = None |
|
gemini_text_model = None |
|
|
|
|
|
def get_astronomy_picture_of_day(date=None): |
|
"""Fetch NASA's Astronomy Picture of the Day""" |
|
params = {'api_key': NASA_API_KEY} |
|
if date: |
|
params['date'] = date |
|
|
|
try: |
|
response = requests.get(APOD_URL, params=params) |
|
data = response.json() |
|
return data |
|
except Exception as e: |
|
return {"error": str(e), "title": "Error fetching APOD", "explanation": "Could not connect to NASA API"} |
|
|
|
def classify_astronomy_image(image): |
|
"""Classify an astronomy image using the pretrained model""" |
|
if feature_extractor is None or model is None: |
|
return {"error": "Model not loaded"} |
|
|
|
try: |
|
inputs = feature_extractor(images=image, return_tensors="pt") |
|
outputs = model(**inputs) |
|
probs = outputs.logits.softmax(1) |
|
pred_class = outputs.logits.argmax(-1).item() |
|
|
|
|
|
id2label = model.config.id2label |
|
prediction = id2label[pred_class] |
|
confidence = probs[0][pred_class].item() |
|
|
|
|
|
top_3_indices = probs[0].topk(3).indices |
|
top_3_preds = [(id2label[idx.item()], probs[0][idx].item()) for idx in top_3_indices] |
|
|
|
return { |
|
"prediction": prediction, |
|
"confidence": confidence, |
|
"top_3": top_3_preds |
|
} |
|
except Exception as e: |
|
return {"error": str(e)} |
|
|
|
def generate_image_caption(image): |
|
"""Generate a caption for the astronomy image""" |
|
if caption_model is None: |
|
return "Image captioning model not available" |
|
|
|
try: |
|
caption = caption_model(image)[0]['generated_text'] |
|
return caption |
|
except Exception as e: |
|
return f"Error generating caption: {str(e)}" |
|
|
|
def analyze_with_gemini(image, prompt=None): |
|
"""Analyze astronomy image with Gemini Pro Vision""" |
|
if gemini_model is None: |
|
return "Gemini API not configured. Please add your API key in the Space settings." |
|
|
|
try: |
|
|
|
img_byte_arr = io.BytesIO() |
|
image.save(img_byte_arr, format='PNG') |
|
img_byte_arr = img_byte_arr.getvalue() |
|
|
|
|
|
if not prompt: |
|
prompt = """ |
|
You are an expert astrophysicist. Analyze this astronomy image in detail. |
|
Include: |
|
1. Identification of the celestial object(s) |
|
2. Scientific explanation of what's visible |
|
3. Approximate distance from Earth (if applicable) |
|
4. Interesting scientific facts about this type of object |
|
5. Technological details about how such images are captured |
|
6. Research value of studying this object |
|
Format your analysis professionally as if for a scientific publication. |
|
""" |
|
|
|
|
|
response = gemini_model.generate_content([prompt, img_byte_arr]) |
|
return response.text |
|
except Exception as e: |
|
return f"Error analyzing with Gemini: {str(e)}" |
|
|
|
def get_professional_insights(query, context=None): |
|
"""Get professional astronomy insights using Gemini Pro""" |
|
if gemini_text_model is None: |
|
return "Gemini API not configured. Please add your API key in the Space settings." |
|
|
|
try: |
|
|
|
prompt = f""" |
|
You are a professional astrophysicist with expertise in observational astronomy, |
|
cosmology, planetary science, and stellar evolution. |
|
|
|
Please provide a comprehensive, scientifically accurate response to the following query: |
|
|
|
{query} |
|
""" |
|
|
|
if context: |
|
prompt += f"\n\nAdditional context: {context}" |
|
|
|
|
|
response = gemini_text_model.generate_content(prompt) |
|
return response.text |
|
except Exception as e: |
|
return f"Error getting insights: {str(e)}" |
|
|
|
def fetch_celestial_object_info(object_name): |
|
"""Fetch information about a celestial object""" |
|
|
|
if gemini_text_model is not None: |
|
try: |
|
|
|
prompt = f""" |
|
You are an astronomy database. Provide comprehensive, scientifically accurate information about {object_name}. |
|
Include these sections: |
|
- Type of object |
|
- Physical characteristics (size, mass, composition) |
|
- Distance from Earth |
|
- Formation and evolution |
|
- Notable features |
|
- Scientific significance |
|
- Recent discoveries (if applicable) |
|
|
|
Format this as structured data that can be parsed as JSON with the following fields: |
|
type, distance, diameter, mass, temperature, composition, age, notable_features, research_value, description |
|
|
|
Ensure all values are scientifically accurate and use appropriate units. |
|
""" |
|
|
|
response = gemini_text_model.generate_content(prompt) |
|
|
|
try: |
|
|
|
import re |
|
json_match = re.search(r'```json\n(.*?)```', response.text, re.DOTALL) |
|
if json_match: |
|
json_str = json_match.group(1) |
|
return json.loads(json_str) |
|
else: |
|
|
|
lines = response.text.split('\n') |
|
info = {"description": ""} |
|
current_key = None |
|
|
|
for line in lines: |
|
if ':' in line and not line.startswith(' '): |
|
parts = line.split(':', 1) |
|
key = parts[0].lower().strip().replace(' ', '_') |
|
value = parts[1].strip() |
|
info[key] = value |
|
current_key = key |
|
elif current_key and line.strip() and current_key == "description": |
|
info[current_key] += " " + line.strip() |
|
|
|
if "description" not in info or not info["description"]: |
|
info["description"] = f"Information about {object_name} generated using AI." |
|
|
|
return info |
|
except: |
|
|
|
pass |
|
except: |
|
|
|
pass |
|
|
|
|
|
info = { |
|
"Sun": { |
|
"type": "Star", |
|
"distance": "1 AU (149.6 million km)", |
|
"diameter": "1,391,000 km", |
|
"mass": "1.989 × 10^30 kg", |
|
"temperature": "5,778 K (surface)", |
|
"description": "The Sun is the star at the center of the Solar System. It is a nearly perfect sphere of hot plasma, heated to incandescence by nuclear fusion reactions in its core." |
|
}, |
|
"Moon": { |
|
"type": "Natural Satellite", |
|
"distance": "384,400 km from Earth", |
|
"diameter": "3,474 km", |
|
"mass": "7.342 × 10^22 kg", |
|
"temperature": "-173°C to 127°C", |
|
"description": "The Moon is Earth's only natural satellite. It is the fifth-largest satellite in the Solar System and the largest among planetary satellites relative to the size of the planet it orbits." |
|
}, |
|
"Mars": { |
|
"type": "Planet", |
|
"distance": "1.5 AU (227.9 million km)", |
|
"diameter": "6,779 km", |
|
"mass": "6.39 × 10^23 kg", |
|
"temperature": "-87°C to -5°C", |
|
"description": "Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System. Mars is often called the 'Red Planet' due to its reddish appearance." |
|
}, |
|
"Galaxy": { |
|
"type": "Galaxy", |
|
"description": "A galaxy is a gravitationally bound system of stars, stellar remnants, interstellar gas, dust, and dark matter. The Milky Way is the galaxy that contains our Solar System." |
|
}, |
|
"Nebula": { |
|
"type": "Nebula", |
|
"description": "A nebula is an interstellar cloud of dust, hydrogen, helium and other ionized gases. Many nebulae are regions where new stars are being formed." |
|
} |
|
} |
|
|
|
|
|
return info.get(object_name, {"description": f"Information about {object_name} is not available in the demo database."}) |
|
|
|
def generate_star_chart(latitude, longitude, date=None): |
|
"""Generate a simple star chart based on location and date""" |
|
|
|
|
|
|
|
|
|
np.random.seed(42) |
|
|
|
|
|
lat_factor = abs(latitude) / 90.0 |
|
if date: |
|
try: |
|
date_obj = datetime.strptime(date, "%Y-%m-%d") |
|
day_of_year = date_obj.timetuple().tm_yday |
|
season_factor = abs(((day_of_year + 10) % 365) - 182.5) / 182.5 |
|
except: |
|
season_factor = 0.5 |
|
else: |
|
season_factor = 0.5 |
|
|
|
num_stars = int(1000 + 2000 * lat_factor * season_factor) |
|
|
|
|
|
x = np.random.rand(num_stars) * 2 - 1 |
|
y = np.random.rand(num_stars) * 2 - 1 |
|
|
|
|
|
magnitudes = np.random.exponential(1, num_stars) * 5 |
|
|
|
|
|
horizon_mask = y > -0.2 |
|
x = x[horizon_mask] |
|
y = y[horizon_mask] |
|
magnitudes = magnitudes[horizon_mask] |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 10), facecolor='black') |
|
ax.set_facecolor('black') |
|
|
|
|
|
sizes = 50 * np.exp(-magnitudes/2) |
|
ax.scatter(x, y, s=sizes, color='white', alpha=0.8) |
|
|
|
|
|
|
|
moon_x = 0.7 * np.cos(latitude/30) |
|
moon_y = 0.6 * np.sin(longitude/30) |
|
ax.scatter(moon_x, moon_y, s=300, color='lightgray', alpha=0.9) |
|
ax.text(moon_x + 0.05, moon_y, 'Moon', color='white', fontsize=12) |
|
|
|
|
|
planet_x = -0.5 * np.sin(latitude/20) |
|
planet_y = 0.4 * np.cos(longitude/20) |
|
ax.scatter(planet_x, planet_y, s=120, color='orange', alpha=0.9) |
|
ax.text(planet_x + 0.05, planet_y, 'Jupiter', color='white', fontsize=12) |
|
|
|
|
|
constellations = [ |
|
{"name": "Big Dipper", "stars": [(0.2, 0.5), (0.3, 0.55), (0.4, 0.6), |
|
(0.5, 0.62), (0.55, 0.5), (0.5, 0.4), (0.4, 0.45)]}, |
|
{"name": "Orion", "stars": [(-0.3, -0.1), (-0.25, 0), (-0.2, 0.1), |
|
(-0.15, 0), (-0.35, -0.15), (-0.25, -0.15), (-0.15, -0.15)]} |
|
] |
|
|
|
for constellation in constellations: |
|
|
|
points = np.array(constellation["stars"]) |
|
ax.plot(points[:,0], points[:,1], 'white', alpha=0.3, linestyle='-', linewidth=1) |
|
|
|
|
|
for x, y in constellation["stars"]: |
|
ax.scatter(x, y, s=100, color='white', alpha=0.9) |
|
|
|
|
|
center_x = np.mean([p[0] for p in constellation["stars"]]) |
|
center_y = np.mean([p[1] for p in constellation["stars"]]) |
|
ax.text(center_x, center_y + 0.1, constellation["name"], color='white', fontsize=12, ha='center') |
|
|
|
|
|
ax.set_xlim(-1, 1) |
|
ax.set_ylim(-1, 1) |
|
ax.set_aspect('equal') |
|
ax.axis('off') |
|
|
|
|
|
location_str = f"Lat: {latitude:.1f}°, Long: {longitude:.1f}°" |
|
date_str = date if date else datetime.now().strftime("%Y-%m-%d") |
|
ax.set_title(f"Star Chart for {location_str} on {date_str}", color='white', fontsize=14) |
|
|
|
|
|
buf = io.BytesIO() |
|
plt.savefig(buf, format='png', facecolor='black') |
|
buf.seek(0) |
|
plt.close(fig) |
|
|
|
return buf |
|
|
|
def predict_space_weather(date=None): |
|
"""Predict space weather conditions (solar flares, aurora activity)""" |
|
|
|
|
|
|
|
if date: |
|
try: |
|
target_date = datetime.strptime(date, "%Y-%m-%d") |
|
except: |
|
target_date = datetime.now() |
|
else: |
|
target_date = datetime.now() |
|
|
|
|
|
dates = [(target_date + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)] |
|
|
|
|
|
np.random.seed(int(target_date.timestamp()) % 1000) |
|
solar_activity = np.clip(5 + np.cumsum(np.random.normal(0, 1, 7)) * 0.5, 0, 10) |
|
|
|
|
|
geomagnetic_activity = np.clip(np.round(4 + np.cumsum(np.random.normal(0, 0.8, 7)) * 0.3), 0, 9) |
|
|
|
|
|
aurora_visibility = np.clip(geomagnetic_activity * 1.1 + np.random.normal(0, 1, 7), 0, 10) |
|
|
|
|
|
flare_probability = np.clip(solar_activity * 10 + np.random.normal(0, 5, 7), 0, 100) |
|
|
|
|
|
weather_df = pd.DataFrame({ |
|
'Date': dates, |
|
'Solar Activity': [f"{x:.1f}/10" for x in solar_activity], |
|
'Geomagnetic Activity': [f"Kp {int(x)}" for x in geomagnetic_activity], |
|
'Aurora Visibility': [f"{x:.1f}/10" for x in aurora_visibility], |
|
'Solar Flare Probability': [f"{int(x)}%" for x in flare_probability] |
|
}) |
|
|
|
return weather_df |
|
|
|
|
|
def build_ui(): |
|
with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo")) as app: |
|
gr.Markdown( |
|
""" |
|
# 🌌 Professional AI Astronomy Explorer |
|
|
|
Explore the universe with the power of AI and Gemini Pro! Upload your astronomy images for classification, |
|
get the latest astronomy picture of the day, generate star charts based on your location, |
|
and access professional-grade astronomical analysis powered by Google's Gemini API. |
|
""" |
|
) |
|
|
|
with gr.Tab("📸 Professional Image Analysis"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
input_image = gr.Image(type="pil", label="Upload Astronomy Image") |
|
with gr.Row(): |
|
classify_btn = gr.Button("Basic Analysis", variant="secondary", scale=1) |
|
gemini_btn = gr.Button("Professional Analysis (Gemini)", variant="primary", scale=1) |
|
|
|
gemini_prompt = gr.Textbox( |
|
label="Customize Gemini Analysis Prompt (Optional)", |
|
placeholder="Leave blank for default professional analysis", |
|
lines=3, |
|
visible=True |
|
) |
|
|
|
with gr.Column(scale=1): |
|
with gr.Tabs(): |
|
with gr.TabItem("Basic Results"): |
|
prediction_output = gr.Textbox(label="Predicted Object Type") |
|
confidence_output = gr.Textbox(label="Confidence") |
|
top3_output = gr.JSON(label="Top 3 Predictions") |
|
caption_output = gr.Textbox(label="AI-Generated Caption", lines=3) |
|
|
|
with gr.TabItem("Professional Analysis"): |
|
gemini_output = gr.Markdown(label="Gemini Pro Analysis") |
|
|
|
classify_btn.click( |
|
fn=lambda img: { |
|
prediction_output: classify_astronomy_image(img).get("prediction", "Unknown"), |
|
confidence_output: f"{classify_astronomy_image(img).get('confidence', 0) * 100:.2f}%", |
|
top3_output: [{"class": c, "probability": f"{p*100:.2f}%"} for c, p in classify_astronomy_image(img).get("top_3", [])], |
|
caption_output: generate_image_caption(img) |
|
}, |
|
inputs=input_image, |
|
outputs=[prediction_output, confidence_output, top3_output, caption_output] |
|
) |
|
|
|
gemini_btn.click( |
|
fn=lambda img, prompt: analyze_with_gemini(img, prompt), |
|
inputs=[input_image, gemini_prompt], |
|
outputs=gemini_output |
|
) |
|
|
|
with gr.Tab("🔭 Astronomy Picture of the Day"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
apod_date = gr.Date(label="Select Date (or leave blank for today)") |
|
apod_btn = gr.Button("Get Astronomy Picture of the Day", variant="primary") |
|
|
|
with gr.Column(scale=2): |
|
apod_image = gr.Image(label="APOD Image", interactive=False) |
|
apod_title = gr.Textbox(label="Title") |
|
apod_desc = gr.Textbox(label="Description", lines=5) |
|
|
|
apod_btn.click( |
|
fn=lambda date: { |
|
apod_image: requests.get(get_astronomy_picture_of_day(date).get("url", "")).content if "url" in get_astronomy_picture_of_day(date) else None, |
|
apod_title: get_astronomy_picture_of_day(date).get("title", "Error fetching APOD"), |
|
apod_desc: get_astronomy_picture_of_day(date).get("explanation", "No description available") |
|
}, |
|
inputs=apod_date, |
|
outputs=[apod_image, apod_title, apod_desc] |
|
) |
|
|
|
with gr.Tab("🌠 Star Chart Generator"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
latitude = gr.Slider(minimum=-90, maximum=90, value=40, step=0.1, label="Latitude") |
|
longitude = gr.Slider(minimum=-180, maximum=180, value=-75, step=0.1, label="Longitude") |
|
chart_date = gr.Date(label="Date (leave blank for today)") |
|
chart_btn = gr.Button("Generate Star Chart", variant="primary") |
|
|
|
with gr.Column(scale=2): |
|
star_chart = gr.Image(label="Generated Star Chart", interactive=False) |
|
|
|
chart_btn.click( |
|
fn=lambda lat, long, date: star_chart.update(generate_star_chart(lat, long, date)), |
|
inputs=[latitude, longitude, chart_date], |
|
outputs=star_chart |
|
) |
|
|
|
with gr.Tab("☀️ Space Weather"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
weather_date = gr.Date(label="Start Date (leave blank for today)") |
|
weather_btn = gr.Button("Predict Space Weather", variant="primary") |
|
|
|
with gr.Column(scale=2): |
|
weather_output = gr.Dataframe(label="7-Day Space Weather Forecast") |
|
|
|
weather_btn.click( |
|
fn=lambda date: predict_space_weather(date), |
|
inputs=weather_date, |
|
outputs=weather_output |
|
) |
|
|
|
with gr.Tab("🪐 Professional Astronomy Knowledge Base"): |
|
with gr.Tabs(): |
|
with gr.TabItem("Celestial Object Database"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
object_selector = gr.Dropdown( |
|
choices=CELESTIAL_BODIES + CELESTIAL_OBJECTS, |
|
label="Select Celestial Object" |
|
) |
|
object_btn = gr.Button("Get Information", variant="primary") |
|
|
|
with gr.Column(scale=2): |
|
object_info = gr.JSON(label="Object Information") |
|
object_desc = gr.Textbox(label="Description", lines=4) |
|
|
|
object_btn.click( |
|
fn=lambda obj: { |
|
object_info: {k: v for k, v in fetch_celestial_object_info(obj).items() if k != "description"}, |
|
object_desc: fetch_celestial_object_info(obj).get("description", "No description available") |
|
}, |
|
inputs=object_selector, |
|
outputs=[object_info, object_desc] |
|
) |
|
|
|
with gr.TabItem("Ask a Professional Astronomer"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
astro_query = gr.Textbox( |
|
label="Your Astronomy Question", |
|
placeholder="Ask about celestial objects, phenomena, theories, or observational techniques...", |
|
lines=3 |
|
) |
|
astro_context = gr.Textbox( |
|
label="Additional Context (Optional)", |
|
placeholder="Add any relevant context or background to your question", |
|
lines=2 |
|
) |
|
ask_btn = gr.Button("Get Professional Insights", variant="primary") |
|
|
|
with gr.Column(scale=1): |
|
pro_insights = gr.Markdown(label="Professional Insights") |
|
|
|
ask_btn.click( |
|
fn=lambda query, context: get_professional_insights(query, context), |
|
inputs=[astro_query, astro_context], |
|
outputs=pro_insights |
|
) |
|
|
|
gr.Markdown( |
|
""" |
|
### About This Professional Astronomy App |
|
|
|
This AI Astronomy Explorer combines advanced machine learning models with Google's Gemini AI to provide professional-grade astronomical analysis: |
|
|
|
- **Professional Image Analysis**: |
|
- Basic classification with standard ML models |
|
- Advanced analysis with Gemini Pro Vision providing expert-level insights |
|
- Customizable analysis prompts for specific research questions |
|
|
|
- **Research-Grade Tools**: |
|
- NASA APOD integration for daily astronomical phenomena |
|
- Interactive star chart generation with astronomical calculations |
|
- Space weather forecasting for observational planning |
|
|
|
- **Professional Knowledge Base**: |
|
- Comprehensive celestial object database enhanced by Gemini Pro |
|
- "Ask a Professional Astronomer" feature for research questions |
|
- Scientifically accurate information suitable for educational and research purposes |
|
|
|
Developed with ❤️ for astronomy professionals, researchers, educators, and enthusiasts. |
|
|
|
*Note: The full functionality of this app requires a valid Google Gemini API key to be configured in the Space settings.* |
|
""" |
|
) |
|
|
|
return app |
|
|
|
|
|
app = build_ui() |
|
|
|
|
|
if __name__ == "__main__": |
|
app.launch() |