| 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_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: |
| |
| 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: |
| |
| 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."} |
|
|
| |
| lc = lc.remove_nans() |
| |
| |
| time = lc.time.value |
| flux = lc.flux.value |
| flux_err = lc.flux_err.value |
| |
| |
| obs_count = len(time) |
| obs_span = time[-1] - time[0] if obs_count > 0 else 0 |
| |
| rel_std = np.std(flux) / np.median(flux) |
| signal_quality = max(0.0, 100.0 - (rel_std * 1000.0)) |
|
|
| |
| |
| teff = lc.meta.get("TEFF") |
| r_star = lc.meta.get("RADIUS") |
| m_star = lc.meta.get("MASS") |
| |
| |
| if teff is None: |
| teff = 5778.0 |
| if r_star is None: |
| r_star = 1.0 |
| if m_star is None: |
| |
| 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_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)}"} |
|
|