monohunter
Find single long-period mono-transits in public TESS light curves — the single-transit events that periodic pipelines (SPOC/QLP, which fold on a period) structurally under-find. Built so many people can each search under-covered targets and combine machine-readable finds.
New here? Run it in your browser — no install. The quickstart notebook installs monohunter in Colab, recovers a known single transit to prove it works, then scans any star you pick. Open it with the badge above.
Why
Automated TESS pipelines run periodic searches (BLS/TLS) over every target. A single transit has no period to fold on, so those searches miss it. Real long-period planets have been co-discovered exactly here (e.g. TOI-2180 b, found from one ~24-hour transit). monohunter targets that gap.
Install
pip install monohunter
Python 3.10+. Pulls in lightkurve, wotan, astroquery, scipy, matplotlib. The
ASAS-SN ground cross-check needs one extra: pip install monohunter[ground].
Or run it with Docker (no Python setup)
docker run --rm -v "$PWD/data:/data" ghcr.io/rinkia/monohunter \
run --tic 298663873 --sectors 19
Outputs land in ./data. For a always-on fresh-data watcher (ideal on a homelab),
docker compose up -d runs monohunter watch on the newest sector in a resumable,
self-healing loop — see docker-compose.yml.
Usage
Search one target by its TESS Input Catalog (TIC) id:
monohunter run --tic 298663873
This searches every available sector, and for each candidate writes a JSON
find-record + a diagnostic PNG into candidates/. Example (TOI-2180, the
canonical mono-transit — restrict to its sector to keep it quick):
monohunter run --tic 298663873 --sectors 19
# S19: depth=4.09ppt dur=24h SNR=39.4 [known TOI-2180.01] -> candidates/tic298663873_s19.json
Options
| Flag | Default | Meaning |
|---|---|---|
--tic <id> |
required | TESS Input Catalog id to search |
--sectors <n...> |
all | restrict to specific sector numbers (faster) |
--window <days> |
3.0 |
detrend window; must be several × the transit duration or flattening eats the dip |
--outdir <path> |
candidates |
where JSON + PNG are written |
--no-plot |
off | skip PNG generation |
--ffi |
off | extract from the Full-Frame Images via TESScut — reaches stars with no pre-made SPOC/QLP light curve |
Reading a result
Each candidate is one JSON file:
{
"schema_version": 6,
"tic": 298663873,
"sector": 19,
"cadence_s": 120,
"event_time_btjd": 1830.77,
"depth_ppt": 4.09,
"duration_hr": 23.8,
"ingress_hr": 2.29,
"snr": 39.4,
"tool_version": "0.3.0",
"known_toi_match": true,
"known_toi_id": "TOI-2180.01",
"likely_eb": false,
"period_constrained": true,
"p_best_d": 856.0,
"p_lo_d": 396.0,
"p_hi_d": 1946.0,
"next_window_btjd": [3200.0, 3245.0, 3290.0],
"n_sectors_observed": 1,
"recurring_dip": false,
"measured_period_d": null,
"plot_path": "candidates/tic298663873_s19.png"
}
| Field | Meaning |
|---|---|
event_time_btjd |
dip center, TESS Barycentric Julian Date |
depth_ppt |
transit depth (parts per thousand), trapezoid flat-bottom |
duration_hr |
total transit duration (first-to-last contact) |
ingress_hr |
ingress/egress time from the trapezoid fit (null if uncharacterized) |
snr |
detection signal-to-noise; the tool reports candidates at SNR ≥ 7 |
known_toi_match / known_toi_id |
whether the target is an existing TESS Object of Interest |
likely_eb |
too deep / V-shaped for a planet — flagged as a likely eclipsing binary (labelled, not rejected) |
p_best_d, p_lo_d, p_hi_d |
single-transit period estimate + range (see Next-transit ephemeris) |
n_sectors_observed, recurring_dip |
multi-sector context: dips in >1 sector flag a periodic/variable star |
measured_period_d |
exact period fitted from multiple transit times, when the target recurs across sectors |
Always look at the PNG. SNR alone lies — confirm the marked dip is a real,
centered transit, not a sector-edge ramp, a data gap, or a single bad cadence.
Re-run with a different --window; a real dip survives, an artifact moves or
vanishes. A known_toi_match: false is the interesting case (potentially
unsearched); true still validates the tool.
A candidate is not a discovery. It means a human thinks the dip is real. Confirming a planet needs follow-up (radial velocity, more transits) beyond this tool.
How it works
fetch search TESS, dedup sectors (prefer 2-min), quality-mask (hard),
stream one sector at a time
|
detrend wotan biweight; window must be >> transit or the dip is flattened away
|
detect matched-filter box scan (non-periodic — finds a SINGLE transit),
red-noise-aware SNR + 7 false-positive guards (edge / gap / scatter /
momentum-dump ramps)
|
characterize trapezoid fit -> true depth, duration, ingress; EB flag
|
cross-match flag known TESS Objects of Interest (NASA Exoplanet Archive)
|
ephemeris period + next-transit window (single-transit, or exact from
multiple sectors); multi-sector recurrence flag
|
FindRecord versioned + validated JSON -> candidates/
The Detector interface is a seam: v1 is the box scan; a GP-based detector
(nuance) can plug in later without touching the pipeline. The versioned
FindRecord JSON is the contract a future aggregation server consumes.
Reuse, not reinvention
Stands on lightkurve, wotan, scipy, and astroquery. monohunter is orchestration + the single-transit gap + result aggregation, not a new detection engine.
Fresh-data watcher (be first)
Institutional pipelines take weeks-to-months to vet a new TESS sector. Run the watcher on a schedule and you process a sector within hours of its release — and a single transit you flag comes with a next-transit window (see below) an observer can still act on.
monohunter watch --sector 90 --max 100 --out watch_out --state watch_state.json
Each run scans the next --max un-processed targets of the sector and prints any
not-yet-known candidates. It's resumable: state tracks which TICs are done,
so scheduled runs continue where the last stopped and a crash loses nothing.
Schedule it to keep chewing through the sector:
# Linux/macOS cron — every 2 hours
0 */2 * * * cd /path/to/monohunter && monohunter watch --sector 90 --max 200
# Windows: Task Scheduler → run the same command on a trigger
Point --sector at the newest released sector. Candidates land in watch_out/;
vet each with monohunter run --tic <id> --sectors <N> to get its PNG, then
submit the good ones (see Contributing).
Reproducible sweeps. watch is the sweep tool — resumable, parallel, and
it logs provenance. For a full sector sweep:
monohunter watch --sector 17 --max 5000 --workers 3 --max-hours 5 \
--summaries summaries_s17 --csv-log sweeps/sector17.csv
--max-hours is a safety net: a hung MAST socket can wedge a worker indefinitely,
so the run force-exits past that wall-clock (set it a bit above the expected
runtime). It's resumable — re-run the same command to continue where it stopped.
--csv-log appends one status row per star (none/novel/error) — the
scan-log a catalog and any retry build from. A star that errors (usually a
transient MAST hiccup) is logged and left un-processed, so simply re-running
the same command retries only the failures — no manual cleanup. --summaries
writes the rotation/variability catalog from the same downloads.
Next-transit ephemeris
When a candidate's target has a catalog stellar density, monohunter estimates the period from the transit duration and predicts when the next transit could occur:
S19: depth=4.09ppt dur=24h SNR=39.4 [known TOI-2180.01]
P~856d (396-1946d, P_min 15d), next transit ~2027-08-07
Single-transit periods are inherently uncertain (a range, not a precise value) — the output is a targeting window for follow-up, not a confirmed ephemeris. If the stellar density is missing or too uncertain, monohunter reports the period as unconstrained rather than guessing.
Multi-sector sharpens both. A target that dips in more than one sector is
flagged recurring_dip (periodic/variable, not a clean mono-transit), and once
it transits in ≥3 sectors the exact period is fitted from the transit times
(measured_period_d) — vastly tighter than the single-transit range.
More commands
Anomaly detection — flares (brightenings) and dippers (aperiodic multi-dip young stars), on the same light curves:
monohunter anomaly --tic 441420236 # AU Mic: flares detected
FFI reach — extract from the Full-Frame Images to search stars with no
pre-made light curve. One target (run --ffi), or a whole cutout at once:
monohunter ffi-batch --tic <center> --sector 14 # every catalog star in one cutout
Ground cross-check — is a candidate's host quiet over years, or a variable star / eclipsing binary? Confirm against ZTF or ASAS-SN:
monohunter ground --tic 198382838 --survey ztf # or --survey asassn
Eclipsing-binary periods — an EB with two or more eclipses in one sector has a
recoverable orbital period. eb finds the eclipse times, splits primary from
secondary by depth, and fits the period from the primary times (their spacing
is exactly one orbit):
monohunter eb --tic 271763138 --sectors 15
# S15: 2 eclipses (1 primary) (primary+secondary) -> period needs >=2 same-type eclipses
A lone primary+secondary pair is left unrecoverable on purpose — on an eccentric orbit the secondary sits at an unknown phase, so the primary-secondary gap is not a period fraction. The tool never reports a confident wrong period.
Rotation / variability catalog — the same download that feeds the transit scan
also yields a per-star rotation period, variability amplitude, flare count, and a
sub-class. summarize writes one JSON per star; catalog aggregates them:
monohunter summarize --tic 100010286 # rotation/variability/flares/dipper + subclass
monohunter catalog --summaries summaries --out catalog.csv
The subclass field splits variables into eclipsing (≥2 eclipse-shaped dips),
pulsator (near-pure sinusoid, low 2nd-harmonic content), and rotator
(non-sinusoidal spot modulation) via periodogram harmonics.
Rotation-period distribution — a population science figure straight from a catalog CSV: the period distribution and the period–amplitude relation over every rotator in the sweep:
monohunter rotation-plot --csv catalogs/sector15.csv --sector 15 --out rotation.png
Faster sweeps — watch (and the sweep scripts) take --workers N for
parallel MAST downloads (network-bound; keep it modest, 4-8).
Crowd vetting + triage — turn a pile of candidates into a labelled queue, then rank future survivors by how much they deserve a human's eyes:
monohunter vet --candidates candidates --out _vet # static page: PNGs + label buttons
monohunter triage-train --labels labels/seed_labels.csv --sweeps sweeps
monohunter triage --candidates candidates # ranks by P(worth vetting)
The vetting page exports labels as JSON; those labels train the triage model, which then puts the real finds at the top of the next sweep's queue.
Survey sensitivity (completeness)
A find list without a sensitivity function is a hobby list; with one it is a survey.
completeness injects synthetic box transits across a depth × duration grid into real
light curves and runs the full detect pipeline on each, measuring the recovered
fraction — what the survey would have caught, and what it would have missed.
Sector 18, mean over 10 quiet stars: ~50% complete at 2–3 ppt for long (12–24 h) transits and ~90% by 5 ppt, while sub-1 ppt dips are largely missed. Reproduce (or plot your own sector) with:
monohunter completeness --sample 10 --catalog catalogs/sector18.csv --sector 18 \
--n 10 --plot completeness_s18.png
Community leaderboard (swarm)
Submitted candidates are aggregated into one ranked list — deduped by
(tic, sector), ranked by novelty (not a known TOI), cross-submitter agreement,
and SNR. Live at https://rinkia.github.io/monohunter/, rebuilt automatically
on every merged contribution.
Vetted candidates worth follow-up (RV, characterization, a second transit) are tracked in docs/followup-targets.md — currently led by TIC 400048097, a bright, uncatalogued star with one clean 2.5% transit.
Build it yourself from a contributions/ tree:
monohunter aggregate --contributions contributions --out _site
# writes _site/index.html + _site/leaderboard.json
This is phase 1 of the swarm: pure aggregation over the PR flow, no backend. A live coordination server (handing out targets so no two people search the same star) is a later increment, worth building only once there's real contention.
One-time to publish: repo Settings → Pages → Source = "GitHub Actions"
(the pages.yml workflow does the rest).
Contributing
Found a candidate, or want to improve the detector? See CONTRIBUTING.md.
- No git? Use the issue form. Open a Submit a candidate issue — fill in the TIC, sector, stats, drag in the PNG, tick the vetting boxes. A maintainer turns a complete submission into a leaderboard entry.
- Comfortable with git? Candidate submissions go to
contributions/<username>/via the candidate PR template.
Development
git clone https://github.com/Rinkia/monohunter
cd monohunter
python -m venv .venv && . .venv/Scripts/activate # Windows
pip install -e ".[dev]"
pytest -q # fast, offline unit tests
pytest --runslow # + live real-data regression (hits MAST)
Releasing to PyPI
CI (.github/workflows/ci.yml) runs the tests on every push. Publishing
(.github/workflows/release.yml) fires on a version tag and uses Trusted
Publishing — no token in GitHub.
One-time PyPI setup (before the first release):
- On PyPI: Account → Publishing → Add a pending publisher:
- PyPI project name:
monohunter - Owner:
Rinkia· Repository:monohunter - Workflow:
release.yml· Environment: leave blank (Any)
- PyPI project name:
- (Optional) For a manual approval gate, create a GitHub Environment, set it
as the pending-publisher Environment, and add
environment: <name>back to thepublishjob inrelease.yml.
Then release:
# bump version in pyproject.toml + monohunter/__init__.py, update CHANGELOG.md
git tag vX.Y.Z
git push origin vX.Y.Z
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file monohunter-0.9.0.tar.gz.
File metadata
- Download URL: monohunter-0.9.0.tar.gz
- Upload date:
- Size: 124.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
866f3e9e8d3c6f7b03ac3c76f661901effc70fa2de293bf7f9887572ca81430b
|
|
| MD5 |
dd2c726701f82ef32419eb6e076d5341
|
|
| BLAKE2b-256 |
ae8f998689610f2a2d23a3ee0960f38f03b9316b8a4fe54fb96e5cfc4f4669b2
|
Provenance
The following attestation bundles were made for monohunter-0.9.0.tar.gz:
Publisher:
release.yml on Rinkia/monohunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
monohunter-0.9.0.tar.gz -
Subject digest:
866f3e9e8d3c6f7b03ac3c76f661901effc70fa2de293bf7f9887572ca81430b - Sigstore transparency entry: 2675861386
- Sigstore integration time:
-
Permalink:
Rinkia/monohunter@b8b4df9c2968ec3622f1aca37235f1b345282120 -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/Rinkia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b8b4df9c2968ec3622f1aca37235f1b345282120 -
Trigger Event:
push
-
Statement type:
File details
Details for the file monohunter-0.9.0-py3-none-any.whl.
File metadata
- Download URL: monohunter-0.9.0-py3-none-any.whl
- Upload date:
- Size: 104.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1f28afebd09acb5f1e4c88526b3e4d38b4791193dd40db3e456b647c06df8e2
|
|
| MD5 |
3348dff2a3be2f478b1c4556498bc5d7
|
|
| BLAKE2b-256 |
2e6d1801755de5fefd52aa345204a72d610002d73106f6077534166b7c3dd9e7
|
Provenance
The following attestation bundles were made for monohunter-0.9.0-py3-none-any.whl:
Publisher:
release.yml on Rinkia/monohunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
monohunter-0.9.0-py3-none-any.whl -
Subject digest:
c1f28afebd09acb5f1e4c88526b3e4d38b4791193dd40db3e456b647c06df8e2 - Sigstore transparency entry: 2675861457
- Sigstore integration time:
-
Permalink:
Rinkia/monohunter@b8b4df9c2968ec3622f1aca37235f1b345282120 -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/Rinkia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b8b4df9c2968ec3622f1aca37235f1b345282120 -
Trigger Event:
push
-
Statement type: