panoptes.pipeline.lightcurve.detection¶
panoptes.pipeline.lightcurve.detection ¶
Transit detection statistics -- the objective the pipeline is actually for.
Scatter is a proxy. The figure of merit for a transit survey is how reliably a transit of a given depth and duration is recovered at a fixed false-alarm rate, and a change that lowers RMS while lowering completeness is a regression. See algorithm design 4.
Nothing here depends on how the lightcurve was produced, so a new architecture can be scored against the old one on identical terms.
Detection
dataclass
¶
transit_weights ¶
transit_weights(times: ndarray, mid_transit: float, duration_hours: float, ingress_fraction: float = 0.15) -> np.ndarray
Trapezoidal in-transit weight per point, 1.0 at full depth.
Source code in src/panoptes/pipeline/lightcurve/detection.py
fit_depth ¶
fit_depth(flux: ndarray, weights: ndarray, sigma: ndarray | float | None = None) -> tuple[float, float]
Weighted least-squares depth and its uncertainty, with a free baseline.
Models flux = baseline - depth * weights. Fitting the baseline rather
than assuming 1.0 matters: a lightcurve normalized by its median is not
exactly at unity once a transit is present in the data being normalized.
Returns:
| Type | Description |
|---|---|
float
|
|
float
|
Lightcurves are not normalized to unity (algorithm design 6), so an |
tuple[float, float]
|
absolute difference would not be a depth. Depth is positive for a dip. |
Source code in src/panoptes/pipeline/lightcurve/detection.py
transit_snr ¶
transit_snr(times: ndarray, flux: ndarray, mid_transit: float, duration_hours: float, sigma: ndarray | float | None = None) -> float
Matched-filter significance of a transit at a known ephemeris.
Source code in src/panoptes/pipeline/lightcurve/detection.py
scan ¶
scan(times: ndarray, flux: ndarray, durations_hours: tuple[float, ...] = (1.0, 2.0, 4.0), num_phases: int = 60) -> Detection
Blind search over mid-transit time and duration; return the best candidate.
Scoring at the best grid point rather than the injected ephemeris is what a real search does, so completeness measured this way includes the penalty for not knowing where to look.
Source code in src/panoptes/pipeline/lightcurve/detection.py
false_alarm_threshold ¶
false_alarm_threshold(times: ndarray, flux: ndarray, durations_hours: tuple[float, ...] = (1.0, 2.0, 4.0), num_phases: int = 60, false_alarm_rate: float = 0.01, num_trials: int = 200, seed: int = 0) -> float
SNR threshold giving the requested false-alarm rate on signal-free data.
The null is built by circular time shifts, not by shuffling. Shuffling destroys the autocorrelation, which would set the threshold as if the noise were white and make every red-noise lightcurve look more significant than it is. Circular shifts preserve the correlation structure and break only the phase, which is the property being tested.
Source code in src/panoptes/pipeline/lightcurve/detection.py
completeness ¶
completeness(recover: Callable[[float, float], ndarray], times: ndarray, depths: tuple[float, ...], durations_hours: tuple[float, ...], threshold: float, num_phases: int = 8, scan_durations: tuple[float, ...] | None = None) -> dict[tuple[float, float], float]
Fraction of injected transits recovered above threshold.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recover
|
Callable[[float, float], ndarray]
|
|
required |
times
|
ndarray
|
Observation times, matching what |
required |
depths
|
tuple[float, ...]
|
Fractional depths to inject. |
required |
durations_hours
|
tuple[float, ...]
|
Durations to inject. |
required |
threshold
|
float
|
From :func: |
required |
num_phases
|
int
|
Injections per (depth, duration) cell. |
8
|
scan_durations
|
tuple[float, ...] | None
|
Durations the blind scan searches. Defaults to the injected set; pass a different grid to include the penalty for a search that does not know the true duration. |
None
|
Returns:
| Type | Description |
|---|---|
dict[tuple[float, float], float]
|
|