EXONYX / app /engine /data_hub.py
Aditya-Jadhav150
Deploy clean EXONYX Backend
8f0e1cb
Raw
History Blame Contribute Delete
4.55 kB
import os
import lightkurve as lk
import numpy as np
CACHE_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "data_cache")
os.makedirs(CACHE_DIR, exist_ok=True)
def fetch_lightcurve(target_name: str, mission: str = "Kepler", quarter: int = None, sector: int = None, deep_recovery_mode: bool = False):
"""
Fetch a light curve from MAST using lightkurve.
Downloads are cached locally to save bandwidth.
"""
search_kwargs = {"target": target_name}
if mission.lower() == "kepler":
search_kwargs["mission"] = "Kepler"
if quarter is not None:
search_kwargs["quarter"] = quarter
elif mission.lower() == "tess":
search_kwargs["mission"] = "TESS"
if sector is not None:
search_kwargs["sector"] = sector
elif mission.lower() == "k2":
search_kwargs["mission"] = "K2"
# Search for light curve files
search_result = lk.search_lightcurve(**search_kwargs)
if len(search_result) == 0:
return {"status": "error", "message": f"No light curves found for {target_name} ({mission})."}
try:
if deep_recovery_mode:
# Deep Recovery: stitch all available quarters together
lc_collection = search_result.download_all(download_dir=CACHE_DIR)
if lc_collection is None or len(lc_collection) == 0:
return {"status": "error", "message": "Failed to load deep recovery light curves."}
lc = lc_collection.stitch()
else:
# Fast Survey Mode: grab the first quarter/sector to avoid 60-second downloads
lc = search_result[0].download(download_dir=CACHE_DIR)
if lc is None:
return {"status": "error", "message": "Failed to load light curve."}
except Exception as e:
return {"status": "error", "message": f"Error downloading data: {str(e)}"}
if lc is None:
return {"status": "error", "message": "Failed to load light curve."}
# Clean the light curve (remove NaNs)
lc = lc.remove_nans()
# Extract arrays
time = lc.time.value
flux = lc.flux.value
flux_err = lc.flux_err.value
# Calculate basic metrics
obs_count = len(time)
obs_span = time[-1] - time[0] if obs_count > 0 else 0
# Simple relative standard deviation as a proxy for inverse signal quality (lower std = better quality)
rel_std = np.std(flux) / np.median(flux)
signal_quality = max(0.0, 100.0 - (rel_std * 1000.0)) # Rough heuristic
# Extract Stellar Parameters from FITS headers (or default to Solar values if missing)
teff = lc.meta.get("TEFF")
r_star = lc.meta.get("RADIUS")
m_star = lc.meta.get("MASS")
# Fallback to Solar values (1.0 R_sun, 1.0 M_sun, 5778 K) if missing from FITS header
if teff is None:
teff = 5778.0
if r_star is None:
r_star = 1.0
if m_star is None:
# Simple estimation: for main sequence stars near solar mass, M ~ R
m_star = r_star if r_star else 1.0
meta = {
"targetid": lc.targetid,
"label": lc.label,
"mission": lc.mission,
"ra": lc.ra,
"dec": lc.dec,
"teff": float(teff),
"radius": float(r_star),
"mass": float(m_star),
"obs_count": int(obs_count),
"obs_span_days": float(obs_span),
"signal_quality": float(signal_quality)
}
return {
"status": "success",
"time": time.tolist(),
"flux": flux.tolist(),
"flux_err": flux_err.tolist(),
"metadata": meta
}
def detrend_lightcurve(time: list, flux: list, window_length: float = 0.5):
"""
Detrend a light curve using Wōtan.
"""
try:
import wotan
time_np = np.array(time)
flux_np = np.array(flux)
pre_std = np.std(flux_np)
# Flatten using biweight method (robust to outliers/transits)
flatten_lc, trend_lc = wotan.flatten(
time_np, flux_np, window_length=window_length, return_trend=True, method='biweight'
)
post_std = np.std(flatten_lc)
noise_reduction_pct = ((pre_std - post_std) / pre_std * 100.0) if pre_std > 0 else 0.0
return {
"status": "success",
"clean_flux": flatten_lc.tolist(),
"trend": trend_lc.tolist(),
"noise_reduction_pct": float(noise_reduction_pct)
}
except Exception as e:
return {"status": "error", "message": f"Wotan detrending failed: {str(e)}"}