| 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() |