Spaces:
Sleeping
Sleeping
| """ | |
| AsteroidNET SkyBoT Client (data_access.skybot_client) | |
| Queries the IMCCE SkyBoT service to find all known solar system objects | |
| in a given sky field at a given epoch. Used both for: | |
| 1. Removing known asteroids from candidate lists (catalog_matcher) | |
| 2. Labeling archival FITS frames for ML training (training.dataset_builder) | |
| SkyBoT covers ephemerides for all known SSOs from 1889 to 2060. | |
| Reference: https://ssp.imcce.fr/webservices/skybot/ | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import Optional | |
| import requests | |
| from astropy.coordinates import SkyCoord | |
| from astropy.table import Table | |
| from astropy.time import Time | |
| import astropy.units as u | |
| logger = logging.getLogger(__name__) | |
| _SKYBOT_URL = "https://ssp.imcce.fr/webservices/skybot/api/conesearch.php" | |
| def query_skybot( | |
| center: SkyCoord, | |
| radius: u.Quantity, | |
| epoch: Time, | |
| observer: str = "500", | |
| config: Optional[dict] = None, | |
| ) -> Table: | |
| """ | |
| Cone-search the SkyBoT service for known solar system objects. | |
| Parameters | |
| ---------- | |
| center : SkyCoord | |
| Field center. | |
| radius : Quantity | |
| Search radius (e.g. 30*u.arcmin). | |
| epoch : Time | |
| UTC observation epoch (use mid-exposure time). | |
| observer : str | |
| MPC observatory code. '500' = geocenter. | |
| Use 'F51' for Pan-STARRS, '695' for Palomar. | |
| config : dict, optional | |
| Pipeline configuration dict. | |
| Returns | |
| ------- | |
| Table | |
| Astropy Table with columns: Number, Name, RA(h), DE(deg), | |
| Type, Mv, posunc, ErrRA, ErrDE, d, dRA, dDE, Rgeo, Rhel | |
| Returns empty Table if no objects found or service unavailable. | |
| """ | |
| url = (config or {}).get("data_access", {}).get("skybot", {}).get("url", _SKYBOT_URL) | |
| observer = (config or {}).get("data_access", {}).get("skybot", {}).get( | |
| "default_observer", observer) | |
| # SkyBoT expects RA in degrees, Dec in degrees, epoch as JD (UTC) | |
| ra_deg = center.icrs.ra.deg | |
| dec_deg = center.icrs.dec.deg | |
| radius_deg = radius.to(u.deg).value | |
| jd_utc = epoch.utc.jd | |
| params = { | |
| "EPOCH": f"{jd_utc:.6f}", | |
| "-ra": f"{ra_deg:.6f}", | |
| "-dec": f"{dec_deg:.6f}", | |
| "-bd": f"{radius_deg:.4f}", | |
| "-loc": observer, | |
| "-mime": "votable", | |
| "-filter": "0", # 0 = all object types | |
| "-refsys": "EQJ2000", | |
| } | |
| logger.info( | |
| "SkyBoT query: RA=%.4f Dec=%.4f r=%.2f' epoch=%s obs=%s", | |
| ra_deg, dec_deg, radius.to(u.arcmin).value, epoch.utc.isot, observer | |
| ) | |
| try: | |
| resp = requests.get(url, params=params, timeout=30) | |
| resp.raise_for_status() | |
| if "No solar system object" in resp.text or len(resp.text) < 100: | |
| logger.debug("SkyBoT: no known SSOs in field") | |
| return Table() | |
| from astropy.io.votable import parse_single_table | |
| import io | |
| votable = parse_single_table(io.BytesIO(resp.content)) | |
| table = votable.to_table() | |
| logger.info("SkyBoT: found %d known SSOs in field", len(table)) | |
| return table | |
| except requests.exceptions.RequestException as exc: | |
| logger.warning("SkyBoT query failed (network): %s — proceeding without known-object removal", exc) | |
| return Table() | |
| except Exception as exc: | |
| logger.warning("SkyBoT parse error: %s — proceeding without known-object removal", exc) | |
| return Table() | |
| def skybot_table_to_skycoord(table: Table) -> Optional[SkyCoord]: | |
| """Convert SkyBoT result table to SkyCoord array for cross-matching.""" | |
| if len(table) == 0: | |
| return None | |
| ra_col = "RA(h)" if "RA(h)" in table.colnames else "RA" | |
| dec_col = "DE(deg)" if "DE(deg)" in table.colnames else "Dec" | |
| return SkyCoord( | |
| ra=table[ra_col], | |
| dec=table[dec_col], | |
| unit=(u.hourangle if "h" in ra_col else u.deg, u.deg), | |
| frame="icrs", | |
| ) | |