exoscout: TESS planet-candidate scorer
An ensemble of five small convolutional networks and five gradient-boosted tree models that reads the folded brightness curve of a NASA TESS signal and returns a planet probability and how much the ten models disagree. It is meant to order follow-up of the thousands of unconfirmed TESS candidates, not to confirm planets.
- Input: three folded curves (whole orbit 201 bins, close-up of the dip 61 bins, close-up of the far side of the orbit 61 bins) plus 13 summary numbers.
- Output per signal: planet probability (0 to 1) and the standard deviation of the ten members' votes.
- Trained on 2,477 settled signals (1,375 planets, 1,102 false positives).
- Cross-validated AUC of the ensemble: 0.9310, with all signals of a star kept on one side of each split (details and stress tests below). The ± 0.0004 in the table is the spread over three seeds, not the uncertainty that comes from having only 2,477 signals: resampling stars puts the 95% interval at about ± 0.009. On a test split of 619 signals held out from every choice (October 2026 audit, below), the same recipe scores 0.929 (95% interval 0.907 to 0.947).
- Weights are
safetensors(networks) andskops(trees); neither runs code when loaded, and the loader refuses any type not on a short allow-list.
Data to run it on
The inputs are published as Kaggle datasets, built with the same code:
- Open candidates (3,871) with curves: https://www.kaggle.com/datasets/shaikizaaz/tess-candidates-with-light-curves
- Settled planets vs false positives (2,477), the training signals: https://www.kaggle.com/datasets/shaikizaaz/tess-planets-vs-false-positives
- The ranking this model produced: https://www.kaggle.com/datasets/shaikizaaz/tess-planet-candidate-ranking
Use it
Get the repository (the code and the released weights are in it), then install the requirements. requirements.txt only sets minimum versions, so afterwards check the tested versions below: the tree files load only with compatible ones.
hf download 9mark9/exoscout-tess-planet-scorer --local-dir exoscout && cd exoscout
python -m venv .venv && .venv/bin/pip install -r requirements.txt
Tested with Python 3.12, torch 2.14.1, scikit-learn 1.9.1, skops 0.16.0, safetensors 0.8.0, numpy 2.5.4. The tree files only load with compatible library versions: with scikit-learn 1.7.2 and skops 0.14.0 loading fails with "holds types that are not allowed", so use the versions above.
from pathlib import Path
from exoscout import model as m, release
ensemble = release.Ensemble(Path("runs/release"))
data = m.load(Path("folder_with_candidates_with_curves_parquet")) # the Kaggle file, in a folder
probability, disagreement = ensemble.predict(data)
Run from the downloaded folder, which is the repository root. On the 3,871 open candidates this reproduces the published ranking to within 0.00005 (rounding).
Limits
- Not a confirmation of anything: confirming a planet needs follow-up (spectra, high-resolution imaging, checks for nearby stars).
- The settled signals it learned from are the easiest to confirm or refute; the open candidates are on the whole harder, so cross-validated scores are likely optimistic for them.
- Where the ten members disagree a lot, read the score as "unsure". For 723 of the 3,871 candidates the disagreement is above 0.2.
- Trained on light curves only; larger efforts such as Astronet-Triage and ExoMiner use more inputs.
Background
Is that dip a planet? A small open model that reads NASA TESS brightness measurements and separates planets from impostors, with its weak points measured and shown.
When a planet passes in front of its star, the star dims by a fraction of a percent, once per orbit. So do many things that are not planets, most often two stars eclipsing each other. NASA's TESS mission has flagged thousands of such repeating dips; about 1,400 have been confirmed as planets, about 1,400 shown to be something else, and several thousand are still waiting.
exoscout learns from the settled cases and ranks the open ones.
What has been measured
Trained and tested on 2,477 settled signals from 2,336 stars (1,375 planets, 1,102 false positives). Scores are from five-fold cross-validation repeated with three seeds, with every signal of a star kept on the same side of each split, so no model is ever tested on a star it has seen.
| Model | AUC | Average precision | Accuracy |
|---|---|---|---|
| Baseline: boosted trees on 13 summary numbers | 0.9125 ± 0.0024 | 0.922 | 83.7% |
| Convolutional network on the folded curves | 0.9064 ± 0.0028 | 0.918 | 83.4% |
| Average of the two | 0.9310 ± 0.0004 | 0.941 | 85.5% |
The network on its own does not beat the baseline. Thirteen well-chosen numbers (depth, duration, whether odd and even dips differ, whether there is a second dip on the far side of the orbit, and so on) do as well as a network looking at the whole curve. What the network adds is different mistakes: averaging the two, with no fitted weight, gains 0.018 AUC over the baseline (bootstrap 95% interval 0.013 to 0.024). That average is the model released here. (On the held-out test split of the October 2026 audit the order of the two flips, network 0.921 against trees 0.902, so which is stronger alone is not settled; that the average beats both holds in both.)
Two stress tests, both in the figure:
- Less data. With a tenth of the training signals the network falls to 0.76 while the trees hold 0.82. Every curve is still rising at 100%, so more labelled signals would help all three.
- Noisier curves. The network degrades fast as noise is added to the folded curves. Once the added noise reaches half the depth of the dip, the average (0.897) is worse than the trees alone (0.9125). This test only disturbs the curves and leaves the summary numbers untouched, so it flatters the trees; in real noisier data they would suffer too.
Audit, October 2026
The repository was checked for leaks and protocol errors, and a version 2 was tried. The released model is unchanged: version 2 did not beat it by more than the noise. The release before this audit is tagged v1.
What was checked, and what was found:
- Label leakage: none found. No input is derived from the disposition. The inputs taken from the catalogue are the period, crossing time and duration needed to fold the curve, the TESS magnitude, and the star's temperature and radius; the catalogue's own depth and planet radius are not used.
- Same star on both sides of a split: none. Folds are grouped by star (TIC), and no TOI or star appears twice across the 2,477 rows except as signals of one star.
- Same dip on two stars: 7 pairs. Seven pairs of neighbouring stars (3 to 10 arcseconds apart) show the same period and timing: the light of one eclipsing system seen in two stars' pixels. Five of the seven pairs have opposite labels (the planet host and its contaminated neighbour), so they make the task harder, not easier. Grouping them together moves the baseline's AUC by 0.002, within the noise from fold assignment. The version 2 evaluation groups them.
- Missing star properties. The star's radius is missing for 8.6% of false positives but 0.7% of planets (7.5% of open candidates), and the trees treat a gap as information. Filling every gap with the median instead costs 0.004 AUC, so little of the score rests on it.
- Thresholds and weights: nothing is tuned on test rows. Accuracy uses a fixed 0.5 cut; the average has no fitted weight; the network's stopping epoch is chosen on held-back training stars.
- Reported uncertainty: understated. The ± figures in the table above are spreads over seeds. Intervals over stars are about twenty times wider (first bullet at the top).
- The released files load in a fresh environment with the versions listed under "Use it" and reproduce the published ranking to within 0.00005. Each tree file holds only one type beyond skops' safe set (
sklearn.ensemble._hist_gradient_boosting.predictor.TreePredictor), and the loader refuses any other.
Version 2, tried and not adopted. Nine more numbers for the trees, each a check an astronomer makes by eye: the planet radius implied by the depth and the star's radius, the dip's length against what that period and star would give, signal-to-noise, how V-shaped and how lopsided the dip is, the noise level, the deepest point on the far side of the orbit and the deepest dip anywhere else in the orbit. Counts of how much data a star has were left out on purpose: they track which stars were followed up, not what the dip is. Tree settings were chosen by a grid over grouped cross-validation, and trees and network are joined by a logistic fit on their log-odds (trees 0.66, network 0.29, offset 0.04), both on the development split only. The test split (619 signals, 583 star groups, a quarter of the groups) was scored once, with five members per recipe trained on the development split. Intervals are 95%, from 2,000 resamples of test stars; the difference is resampled in pairs.
| On the test split, scored once | AUC | Average precision | Calibration gap | Brier | Planets in top 100 |
|---|---|---|---|---|---|
| Version 1 trees | 0.902 (0.876 to 0.926) | 0.913 (0.885 to 0.937) | 0.045 | 0.124 | 98 |
| Version 1 network | 0.921 (0.899 to 0.941) | 0.936 (0.912 to 0.955) | 0.049 | 0.112 | 98 |
| Version 1 average (released) | 0.929 (0.907 to 0.947) | 0.939 (0.917 to 0.958) | 0.035 | 0.106 | 98 |
| Version 2 trees | 0.923 (0.901 to 0.943) | 0.932 (0.908 to 0.952) | 0.041 | 0.111 | 99 |
| Version 2 (trees and network joined) | 0.934 (0.913 to 0.952) | 0.942 (0.920 to 0.960) | 0.022 | 0.101 | 99 |
| Version 2 minus version 1 | +0.005 (-0.005 to +0.014) | +0.003 (-0.004 to +0.011) | -0.004 | -0.004 |
The new numbers lift the trees by 0.020 AUC, most of it from the two that use the star's radius, and the gain survives with every gap median-filled (0.904 to 0.927 in development cross-validation). Joined with the network, the gain over the released average shrinks to +0.005 AUC and +0.003 average precision, and both intervals include zero. It is better calibrated (gap 0.022 against 0.035). The bar for replacing the release was a gain in both AUC and average precision outside the noise, so the release stays as it was. As a secondary check, five-fold cross-validation over all 2,477 signals, reusing the version 1 networks' held-out votes, gives +0.007 AUC (0.003 to 0.011) and +0.005 average precision (0.001 to 0.009); its settings were chosen on three quarters of those same rows, so it favours version 2 and does not decide anything.
python -m exoscout.evaluate_v2 runs the whole check and writes results/results_v2.json. The loader also accepts a version 2 release (trees that take the extra numbers, and a combiner.json with the three join numbers); none is published.
What this is not
- Not a confirmation of anything. Confirming a planet takes follow-up that a brightness curve cannot replace: spectra, higher-resolution imaging, checks for nearby stars.
- Not state of the art. Astronet-Triage and ExoMiner are larger efforts with more inputs than a light curve. This is a small, fully reproducible model with its limits measured.
- Not trained on a fair sample. The settled signals are the ones that were easiest to confirm or to refute. The open candidates are, on the whole, harder, so the scores above are likely optimistic for them.
The candidate ranking
results/candidates_ranked.csv scores 3,871 unconfirmed TESS candidates with an ensemble of five networks and five tree models trained on all settled signals. 468 score above 0.9 and 452 below 0.1. Each row also carries how much the ten models disagree; for 723 candidates that disagreement is large, and their score should be read as "unsure" whatever it says. Use it to decide what to look at first.
Run it
Requires Python 3.12 and about 400 MB of free disk for the catalogue files.
python -m venv .venv && .venv/bin/pip install -r requirements.txt
# 1. catalogue and file index (about 330 MB of file listings, kept in data/raw)
.venv/bin/python -m exoscout.catalogue
# 2. build the dataset: downloads about 7,000 files, keeps 7 MB
.venv/bin/python -m exoscout.build --dispositions CP,KP,FP,FA --sectors 3
.venv/bin/python -m exoscout.build --dispositions PC --sectors 3 --out data/candidates
# 3. score the models and stress them (about 40 minutes on a laptop CPU)
.venv/bin/python -m exoscout.evaluate
# 3b. the October 2026 audit: a test split scored once (networks train side by side)
.venv/bin/python -m exoscout.evaluate_v2
# 4. train the release ensemble and rank the candidates
.venv/bin/python -m exoscout.rank
.venv/bin/python -m pytest tests # 16 tests, on made-up stars with known answers
Pick a star
.venv/bin/python -m exoscout.score 1055.01 6978.01 # scores, plus a picture of what the model saw in runs/scored
.venv/bin/python app.py # the same as a web page on http://127.0.0.1:7860
The scorer fetches that star's measurements from the archive, folds them and asks the released ensemble. For a signal that was one of the settled cases the model learned from, it says so instead of presenting the score as a test.
How it works
Views (exoscout/views.py). For each signal the catalogue gives a period, the time of one crossing and the length of a crossing. The measurements are cleaned of slow drifts with the dips masked out, so the dips are not mistaken for drift and erased. They are then folded on the period and binned into three curves: the whole orbit, a close-up of the dip, and a close-up of the far side of the orbit, where an eclipsing pair of stars shows a second dip and a planet does not.
One step turned out to matter more than expected: a catalogued crossing time, projected over hundreds of orbits to the date of an observation, drifts. On a first check against twelve confirmed planets, two had vanished for that reason. The builder now uses the observations closest in time to the catalogued crossing and re-centres on the dip within a small window. After that, the dip depth measured here is typically 97% of the catalogue's value for planets (80% of them between 76% and 115%).
Models (exoscout/model.py). The baseline is gradient-boosted trees on thirteen summary numbers. The network has three small convolutional towers, one per curve, joined with the same numbers; about 150,000 weights. It trains in under a minute on a laptop CPU.
Data
- Labels and orbit timing: the TESS Objects of Interest table from the NASA Exoplanet Archive (table
toi, fetched 12 October 2026). "CP" and "KP" count as planets, "FP" and "FA" as false positives, "PC" as open candidates. - Brightness measurements: two-minute SPOC light curves from the MAST archive at STScI, up to three sectors per star. Raw files are processed in memory and not kept.
This work makes use of data collected by the TESS mission, funded by NASA's Explorer Program, and of the NASA Exoplanet Archive, operated by Caltech under contract with NASA.
Model files
exoscout.rank writes the release ensemble to runs/release: network weights as safetensors and tree models as skops files, neither of which can run code when loaded. Loading a tree file checks every type inside it against a short list and refuses anything else.
Licence
MIT. See LICENSE.