Metadata-Version: 2.4
Name: tess-vesper
Version: 0.2.2
Summary: VESPER - fast learned vetting of transiting-planet candidates around TESS M dwarfs
Author: Yohann Tschudi
License: MIT
Project-URL: Repository, https://gitlab.com/yohanntschudi/exoplanets-project
Project-URL: Model card, https://gitlab.com/yohanntschudi/exoplanets-project/-/blob/main/documentation/pipeline_ml/VETTER_MODEL_CARD.md
Keywords: TESS,exoplanets,vetting,M dwarfs,transits
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Astronomy
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.24
Requires-Dist: torch>=2.0
Requires-Dist: scikit-learn>=1.3
Requires-Dist: celerite2>=0.3.0
Provides-Extra: acquire
Requires-Dist: lightkurve>=2.4.0; extra == "acquire"
Provides-Extra: spatial
Requires-Dist: lightkurve>=2.4.0; extra == "spatial"
Requires-Dist: tess_prf==0.1.3; extra == "spatial"
Requires-Dist: scipy>=1.10; extra == "spatial"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"

# VESPER

**Vetting Exoplanet Signals with Pixel-informed Evidence, Rapidly.**

VESPER is a fast, learned vetter for transiting-planet candidates around
**TESS M dwarfs**. Give it a star's TESS photometry and a candidate
ephemeris (period, epoch, duration); it returns a **calibrated probability
that the signal is a planet**, seven named impostor scores — in the
output's `aux_*` column order: background eclipsing binary (`aux_beb`),
window alias (`aux_alias`), eclipsing binary on target (`aux_eb`), stellar
rotation (`aux_rot`), companion dilution (`aux_comp`), no signal at the
ephemeris (`aux_vide`, printed as "empty"), spacecraft systematics
(`aux_sys`) — and a keep/reject disposition, in about a tenth of a second
per star, entirely offline (no external service calls).

The frozen model, its full performance record and its known limitations are
documented in the model card, linked from this page:
https://gitlab.com/yohanntschudi/exoplanets-project/-/blob/main/documentation/pipeline_ml/VETTER_MODEL_CARD.md

## Install

**Python ≥ 3.10 required** — check with `python3 --version`. Work in a
fresh virtual environment (system Pythons are often older and their
site-packages messy):

macOS / Linux:

```
python3.11 -m venv vesper-env          # any Python >= 3.10
source vesper-env/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install "tess-vesper[acquire,spatial]"
```

Windows (PowerShell):

```
python -m venv vesper-env              # any Python >= 3.10
vesper-env\Scripts\Activate.ps1
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install "tess-vesper[acquire,spatial]"
```

If PowerShell refuses to run `Activate.ps1` (execution policy), skip
activation entirely and call the venv's executables by path — it works
identically: `vesper-env\Scripts\vesper.exe vet-batch ...`.

CPU-only torch is enough. The `[acquire]` extra adds lightkurve, used by
`vesper fetch` to download and prepare TESS photometry; the `[spatial]`
extra (lightkurve + tess_prf) lets VESPER **measure the difference-image
centroid itself** — the pixel evidence that separates a background
eclipsing binary from an on-target signal, and the configuration the model
was certified with. The wheel bundles the frozen model (weights,
calibrator, decision threshold, M-dwarf stellar catalog) and the artifacts'
SHA-256 fingerprints are checked at every load.

## How it works

1. **You provide the ephemeris** — VESPER judges a candidate at a given
   (period, T0, duration); it complements your transit search and your
   follow-up, taking the statistical-vetting step in between.
2. **VESPER prepares its own photometry.** From stitched, median-normalised
   PDCSAP flux *before any detrending*, it removes stellar variability with
   its own Gaussian-process model on a 200 s grid — the exact preparation
   the network was trained and certified on. `vesper fetch` builds that
   input for any TIC directly from the TESS archive. Supplying your own
   curves instead: one pickle per host, named `TIC{tic}_stitched.pkl`,
   holding a dict with keys `time` (BTJD), `flux` (PDCSAP, quality flag 0,
   per-sector median-normalised to ~1.0, **before any stellar-variability
   detrending**), and optionally `flux_err` and `sector` — exactly what
   `vesper fetch` writes. Do not feed already-detrended flux: the certified
   numbers do not hold for it.
3. **It folds and judges — pixels included.** The signal is folded into
   shape views (global, transit zoom, secondary, odd/even,
   per-observing-block), normalised to the star's own noise, and weighed
   together with the stellar parameters (bundled M-dwarf catalog, with an
   automatic TIC-8 lookup for hosts outside it — the `stellar_source`
   column says which) and the **difference-image centroid**: with the
   `[spatial]` extra installed, VESPER downloads the target pixel files and
   measures where on the pixels the dip occurs (deterministic, all
   available sectors — the heaviest step: every 2-min TPF of the host).
   `--no-spatial` skips it; without the extra, signals serve with a neutral
   spatial vector, labelled `spatial_status=unmeasured` — keep-rates on
   true planets are measured to be lower in that mode.
4. **It answers in calibrated units.** `p_cal` is a calibrated probability;
   the shipped threshold (0.29565…, set on the model's sealed calibration
   split to keep ≥ 95 % of its planets — 96.1 % realized there, 92.2 % of
   confirmed planets kept on the certified hold-out) turns it into a
   disposition, and the named impostor heads tell you *what* the signal
   most resembles when it is not a planet.

Domain of validity: TESS M dwarfs (Teff ≲ 4,000 K), periods ~0.5–100 d,
native cadences 20–300 s (served on a uniform 200 s grid; coarser
products — e.g. native 600 s — are not servable). Anything the data
contract cannot serve comes back as an explicit `blocked:...` status, never
a silent skip. **The contract checks the data, not the star**: VESPER does
not verify that your target is an M dwarf — a K- or G-dwarf curve will
serve and return a number, but an unvalidated one. Check Teff yourself;
outside the M-dwarf regime the output is uncertified.

## Try it — the bundled example

Twelve signals on six M-dwarf hosts, both sides of the decision:
**L 98-59** (three small confirmed planets, plus one unconfirmed 1.05 d
candidate signal — judge it yourself), **TOI-700** (four confirmed planets,
including the habitable-zone TOI-700 d), and **four TFOPWG-certified false
positives** spanning the impostor zoo — a 22.6 % deep eclipse, a
short-period eclipsing binary, a long-period shallow false alarm, and a
790 ppm signal with a perfectly planet-like depth.

```
vesper example
vesper fetch --candidates candidates_example.csv --out-dir example_curves
vesper vet-batch --candidates candidates_example.csv --curves-dir example_curves --out example_results.csv
```

The fetch downloads every SPOC-family sector of all six stars — TOI-700
alone has 30+ — through lightkurve's cache (`~/.lightkurve/cache`): a few
hundred MB of FITS and several network-bound minutes on the first run.
Stars whose pickle already exists in `--out-dir` are skipped, so an
interrupted fetch can simply be re-run. With the `[spatial]` extra
installed, `vet-batch` then also **measures the difference-image centroid**
of each signal — that step downloads the 2-min target pixel files and is
the long one on many-sector stars; add `--no-spatial` for a quick
photometry-only pass. Measurements are saved to a
`<out>_spatial.csv` sidecar **written incrementally and reloaded on
start-up**: an interrupted run resumes where it stopped by simply re-running
the same command, and finished measurements can be reused forever via
`--spatial <sidecar>`.

For your own targets, write the same kind of CSV (header exactly as below,
no spaces after the commas; extra columns such as `toi` or `note` are
ignored):

```
tic_id,period_days,t0_btjd,duration_hours,depth_ppm,planet_num
307210830,7.450726,1355.290002,1.68,1002.2,4
```

`t0_btjd` is **BTJD = BJD_TDB − 2,457,000** (native TESS time). ExoFOP
lists full BJD epochs — subtract 2,457,000, or the row comes back
`blocked:t0_not_btjd`. The four first columns are required on every row;
`depth_ppm` (feeds the deep-eclipse prior) and `planet_num` (row labelling)
are optional. Single candidates:

```
vesper vet --tic 307210830 --period 7.450726 --t0 1355.29 --duration-h 1.68 \
           --curves-dir example_curves
```

## Reading the output

The results CSV (`--out`) echoes your input columns, then carries the
science columns below, plus diagnostics (`p_raw` — the pre-calibration
score; `kept_0.5`/`kept_0.9` — stricter fixed thresholds;
`cascade_depth_source`; `n_sec_measured`/`n_sec_served` — centroid-evidence
sector count before/after its cap; `log_scatter`; `cadence_s_native`/
`cadence_s_served`). The console prints the same information as one aligned
table per host.

| Column | Meaning |
|---|---|
| `p_cal` | calibrated P(planet) — the number to read |
| `kept_literal` | `p_cal` ≥ the shipped threshold — the threshold-only verdict (the calibration-split recall-95 operating point; hold-out performance in the model card §5) |
| `cascade_fp` | the deep-eclipse prior fired (depth > 50,000 ppm and `p_cal` < 0.9). For very deep candidates around small stars, read `kept_literal` — already in the CSV — since giant planets on M dwarfs can exceed that depth; `--no-cascade` disables the rule outright |
| `final_literal` | the disposition: True = keep as a planet candidate |
| `aux_beb` … `aux_sys` | the seven impostor heads (mapping in the opening paragraph) — triage pointers to the most likely false-positive channel, not individually calibrated probabilities |
| `spatial_status` | `ok(n/m sec)` = centroid measured (n sectors passed the S/N gate of m usable); `failed:...` = measurement attempted, unusable; `unmeasured` = neutral mode (`--no-spatial`, or the `[spatial]` extra not installed) |
| `stellar_source` | `catalog` (bundled M-dwarf catalog) / `tic8` (live TIC-8 lookup) / `imputed` (no parameters found — served with training-mean values) |
| `row_status` | `served`, or an explicit `blocked:...` reason |

**What `p_cal` is calibrated against.** The calibration set is real M-dwarf
light curves carrying injected planets and constructed impostors, held out
from training — `p_cal` is well-scaled under that mixture. It is **not** a
TRICERATOPS-style false-positive probability computed against a Galactic
stellar-population model, and a high `p_cal` is not by itself grounds for
statistical validation. Use it to rank and triage; validation still
requires independent evidence.

## License and citation

MIT. The reference article is in preparation; until it is out, cite this
package and its model card (linked above), which carries the full
provenance of the frozen model.
