Spaces:
Sleeping
Sleeping
File size: 16,498 Bytes
f9ad2ef edb680d f9ad2ef 0ea5dea |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
import os
from dotenv import load_dotenv
import gradio as gr
import joblib
import pandas as pd
import numpy as np
from openai import OpenAI
# Load .env from project root
load_dotenv() # <-- must be called before reading os.getenv
API_KEY = os.getenv("API_KEY") # matches the name in .env
BASE_URL = os.getenv("BASE_URL", "https://api.aimlapi.com/v1")
if not API_KEY:
raise RuntimeError("API_KEY not found in environment. Did you create a .env and call load_dotenv()?")
client = OpenAI(
base_url=BASE_URL,
api_key=API_KEY,
)
# Load the trained model and preprocessing objects
model = joblib.load('exoplanet_model.pkl')
scaler = joblib.load('scaler.pkl')
feature_names = joblib.load('feature_names.pkl')
def generate_llm_explanation(prediction, input_data, engineered_features, probabilities):
"""
Generate natural language explanation using LLM
"""
try:
# Prepare context for the LLM
result_type = "an exoplanet" if prediction == 1 else "a non-exoplanet"
confidence = probabilities[prediction] if probabilities is not None else "N/A"
prompt = f"""You are an expert astronomer analyzing exoplanet detection data. The Machine learning model predicts how much it is exoplanet and now you need to explain why the machine learning model classified this as {result_type}.
Prediction: {result_type.upper()}
Confidence: {confidence:.2%} if isinstance(confidence, (int, float)) else confidence
Input Parameters
- Orbital Period: {input_data['tce_period']:.2f} days
- Transit Epoch: {input_data['tce_time0bk']:.2f} BJD
- Impact Parameter: {input_data['tce_impact']:.3f} (0=center, 1=grazing)
- Transit Duration: {input_data['tce_duration']:.2f} hours
- Transit Depth: {input_data['tce_depth']:.1f} ppm
- Signal-to-Noise Ratio: {input_data['tce_model_snr']:.2f}
- Planet Radius: {input_data['tce_prad']:.2f} Earth radii
- Equilibrium Temperature: {input_data['tce_eqt']:.1f} K
- Insolation: {input_data['tce_insol']:.2f} Earth flux
- Stellar Temperature: {input_data['tce_steff']:.1f} K
- Stellar Surface Gravity: {input_data['tce_slogg']:.2f} log(g)
- Stellar Radius: {input_data['tce_sradius']:.2f} Solar radii
Explanation Guidelines:
- Use the input parameters to explain the classification.
- How often the dip in starlight repeats: every {input_data['tce_period']:.2f} days
- How much the star dims during transit: {input_data['tce_depth']:.1f} parts per million
- How long each dimming event lasts: {input_data['tce_duration']:.2f} hours
- Signal quality/clarity: {input_data['tce_model_snr']:.2f} (higher = clearer signal)
- Where the object crosses the star: {input_data['tce_impact']:.3f} (0 = dead center, 1 = barely grazing edge)
- Estimated size of the object: {input_data['tce_prad']:.2f} times Earth's size
- Temperature of the object: {input_data['tce_eqt']:.1f} Kelvin
- How much starlight hits it: {input_data['tce_insol']:.2f} times what Earth gets
- Temperature of the host star: {input_data['tce_steff']:.1f} Kelvin
- Size of the host star: {input_data['tce_sradius']:.2f} times our Sun
- Star's surface gravity: {input_data['tce_slogg']:.2f} (tells us if it's a normal star or something else)
Engineered Features:
- Temperature/Radius Ratio: {engineered_features['temp_radius_ratio']:.2f}
- Period/Duration Ratio: {engineered_features['period_duration_ratio']:.2f}
- SNR Γ Depth Product: {engineered_features['snr_depth_product']:.2f}
YOUR TASK: Write a clear, conversational explanation (5-7 sentences) that a middle schooler could understand. Explain:
1. WHAT we're looking at: Describe what causes the star to dim (don't just say "transit" - explain it's like an object passing in front)
2. WHY the model thinks it's {result_type}:
- If EXOPLANET: Explain what specific measurements look RIGHT for a planet (e.g., "The size is reasonable for a planet", "It crosses the star regularly like a planet in orbit would", "The signal is strong and clear, not messy like false alarms")
- If FALSE POSITIVE: Explain what looks WRONG (e.g., "The object is way too big to be a planet - it's probably a small star", "The signal is too weak and noisy", "The way it crosses the star is weird for a planet")
3. THE KEY EVIDENCE: Pick 2-3 specific measurements that most strongly support this conclusion and explain WHY they matter in plain English
4. THE VERDICT: Wrap up by saying whether this is likely a real planet or not and why we can/can't trust this
DO NOT:
- Use technical jargon without explaining it
- Just list numbers - explain what they MEAN
- Say "the model detected" - explain the actual physics/reasons
- Use bullet points or lists
BE CONVERSATIONAL and use analogies where helpful (e.g., "imagine Earth passing in front of the Sun from far away")
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=300
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"LLM explanation unavailable: {str(e)}"
def predict_exoplanet(period, time0bk, impact, duration, depth, snr, prad,
eqt, insol, steff, slogg, sradius):
"""
Predict if the input represents an exoplanet or false positive
"""
try:
# Create input dictionary
input_data = {
'tce_period': float(period),
'tce_time0bk': float(time0bk),
'tce_impact': float(impact),
'tce_duration': float(duration),
'tce_depth': float(depth),
'tce_model_snr': float(snr),
'tce_prad': float(prad),
'tce_eqt': float(eqt),
'tce_insol': float(insol),
'tce_steff': float(steff),
'tce_slogg': float(slogg),
'tce_sradius': float(sradius)
}
# Create DataFrame
df = pd.DataFrame([input_data])
# Add engineered features
df['temp_radius_ratio'] = df['tce_steff'] / (df['tce_sradius'] + 1e-6)
df['period_duration_ratio'] = df['tce_period'] / (df['tce_duration'] + 1e-6)
df['snr_depth_product'] = df['tce_model_snr'] * df['tce_depth']
engineered_features = {
'temp_radius_ratio': df['temp_radius_ratio'].values[0],
'period_duration_ratio': df['period_duration_ratio'].values[0],
'snr_depth_product': df['snr_depth_product'].values[0]
}
# Ensure correct feature order
df = df[feature_names]
# Scale features
df_scaled = scaler.transform(df)
# Make prediction
prediction = model.predict(df_scaled)[0]
# Get probability if available
if hasattr(model, 'predict_proba'):
probabilities = model.predict_proba(df_scaled)[0]
confidence = probabilities[prediction]
prob_exoplanet = probabilities[1]
prob_false_positive = probabilities[0]
else:
confidence = None
prob_exoplanet = prediction
prob_false_positive = 1 - prediction
probabilities = None
# Generate LLM explanation
llm_explanation = generate_llm_explanation(
prediction, input_data, engineered_features, probabilities
)
# Format result
result = "πͺ EXOPLANET DETECTED" if prediction == 1 else "β FALSE POSITIVE"
# Create detailed output
details = f"""
### Prediction: {result}
**Confidence:** {f"{confidence:.2%}" if confidence else 'N/A'}
**Probability Breakdown:**
- Exoplanet: {prob_exoplanet:.2%}
- False Positive: {prob_false_positive:.2%}
---
### π€ AI Explanation:
{llm_explanation}
---
### π Input Summary:
- **Orbital Period:** {period} days
- **Transit Depth:** {depth} ppm
- **Signal-to-Noise Ratio:** {snr}
- **Planet Radius:** {prad} Earth radii
- **Stellar Temperature:** {steff} K
- **Impact Parameter:** {impact}
### π§ Engineered Features:
- **Temp/Radius Ratio:** {engineered_features['temp_radius_ratio']:.2f}
- **Period/Duration Ratio:** {engineered_features['period_duration_ratio']:.2f}
- **SNR Γ Depth:** {engineered_features['snr_depth_product']:.2f}
"""
return details
except Exception as e:
return f"Error: {str(e)}\n\nPlease check your input values."
# Example datasets for quick testing
examples = [
# Confirmed exoplanet-like parameters
[3.5, 135.0, 0.3, 2.5, 800, 20.0, 2.0, 450, 100, 5800, 4.5, 1.0],
# False positive-like parameters
[50.0, 200.0, 0.9, 15.0, 50, 5.0, 15.0, 300, 5, 4500, 4.8, 0.5],
# Hot Jupiter-like
[0.8, 140.0, 0.1, 1.2, 15000, 50.0, 11.0, 1500, 5000, 6200, 4.3, 1.2],
]
# Create Gradio interface
with gr.Blocks(title="Exoplanet Detection System", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# π Exoplanet Detection System with AI Explanations
This AI-powered system predicts whether a transit signal represents a genuine exoplanet
or a false positive based on Kepler space telescope data, with intelligent explanations.
### How to use:
1. Enter the transit and stellar parameters below
2. Click "Detect Exoplanet" to get prediction
3. Read the AI-generated explanation of why this classification was made
4. Or try example cases for quick testing
"""
)
with gr.Row():
with gr.Column():
gr.Markdown("### Transit Parameters")
period = gr.Number(label="Orbital Period (days)", value=10.0,
info="Time between successive transits")
time0bk = gr.Number(label="Transit Epoch (BJD - 2454833)", value=135.0,
info="Time of first transit")
impact = gr.Slider(0, 1, value=0.5, label="Impact Parameter",
info="0 = center crossing, 1 = grazing")
duration = gr.Number(label="Transit Duration (hours)", value=3.0,
info="How long the transit lasts")
depth = gr.Number(label="Transit Depth (ppm)", value=500.0,
info="Fractional decrease in brightness")
snr = gr.Number(label="Signal-to-Noise Ratio", value=15.0,
info="Quality of the detection")
with gr.Column():
gr.Markdown("### Planet & Stellar Parameters")
prad = gr.Number(label="Planet Radius (Earth radii)", value=2.5,
info="Size relative to Earth")
eqt = gr.Number(label="Equilibrium Temperature (K)", value=400.0,
info="Estimated planet temperature")
insol = gr.Number(label="Insolation (Earth flux)", value=50.0,
info="Stellar flux received")
steff = gr.Number(label="Stellar Temperature (K)", value=5500.0,
info="Host star temperature")
slogg = gr.Slider(2, 5, value=4.5, label="Stellar Surface Gravity (log g)",
info="Indicates star type")
sradius = gr.Number(label="Stellar Radius (Solar radii)", value=1.0,
info="Size of host star")
detect_btn = gr.Button("π Detect Exoplanet", variant="primary", size="lg")
output = gr.Markdown(label="Prediction Results")
detect_btn.click(
fn=predict_exoplanet,
inputs=[period, time0bk, impact, duration, depth, snr, prad,
eqt, insol, steff, slogg, sradius],
outputs=output
)
gr.Markdown("### π Example Cases")
gr.Examples(
examples=examples,
inputs=[period, time0bk, impact, duration, depth, snr, prad,
eqt, insol, steff, slogg, sradius],
label="Try these example transit signals"
)
gr.Markdown(
"""
---
**Model Information:**
- Trained on Kepler mission data
- Uses ensemble machine learning techniques
- Features: Transit timing, depth, stellar parameters, and more
- AI explanations powered by GPT-4o
**Note:** This is a demonstration tool. Real exoplanet confirmation requires
extensive follow-up observations and validation.
"""
)
gr.Markdown("### Data Visualization of Kepler Dataset")
# with gr.Row():
gr.Image(value="planet_radius.png", show_label=False)
gr.Markdown("""
**Explanation:**
The visualization above shows the **distribution of planet sizes** found by the *Kepler mission*, measured in Earth's radii.
It reveals that most candidate planets are **quite small, close in size to Earth**, while there are **very few extremely large planets detected**, creating a sharp drop-off on the right side of the graph.
""")
gr.Image(value="orbital_period.png", show_label=False)
gr.Markdown("""
This graph displays data from the Kepler mission, showing the relationship between a planet's size (radius in Earth radii) and its orbital period (the time it takes to complete one orbit around its star, in days). It indicates there is a wide range of planet sizes found at various orbital periods, with many small planets orbiting both quickly and slowly, while larger planets are also found across different orbital durations. The plot uses logarithmic scales to show this broad distribution clearly.
""")
gr.Image(value="pie_chart.png", show_label=False)
gr.Markdown("""
This pie chart shows the average distribution of different false positive scenarios in the Kepler dataset for candidate planets.
Explanation of the terms:
fpp_prob_ueb (37.8%): Probability the signal comes from an Unblended Eclipsing Binary, where two stars orbit each other without blending.
fpp_prob_beb (16.6%): Probability of a Blended Eclipsing Binary, where light from two stars blends and mimics a planetary signal.
fpp_prob_beb_dbl (23.7%): Similar to beb but with double orbital periods involved.
fpp_prob_heb (9.2%): Probability of a Hierarchical Eclipsing Binary, a star orbiting another star in the same system.
fpp_prob_ueb_dbl (10.9%): Double period case for Unblended Eclipsing Binary.
fpp_prob_heb_dbl (1.7%): Double period case for Hierarchical Eclipsing Binary.
These categories represent different ways that signals mimicking planets can actually be caused by binary star systems or their complex interactions, helping scientists distinguish real planets from false alarms in the Kepler data.
""")
gr.Image(value="stellar_radius.png", show_label=False)
gr.Markdown("""
Most stars are small, with sizes close to the Sun's. However, the chart highlights that as stars get heavier (more massive), their sizes can become dramatically larger, showing a significant scatter for the heaviest stars. This data comes from the Kepler telescope observations.
""")
gr.Image(value="likelihood.png", show_label=False)
gr.Markdown("""
It shows that most "planet candidates" (the big yellow/green dots) are large and have a high chance of being a false alarm (high false positive probability). The smaller, darker dots are less likely to be false alarms. The plot shows that planet size and how long they take to orbit their star are highly variable.
""")
gr.Image(value="stellar_temp.png", show_label=False)
gr.Markdown("""
The vast majority of the observed stars have a temperature around 5,700 to 6,000 Kelvin, which is very similar to the temperature of our own Sun. The count quickly drops off for stars that are either much cooler or much hotter than this average.
""")
if __name__ == "__main__":
demo.launch(share=True) |