panoptes.pipeline.lightcurve.core¶
panoptes.pipeline.lightcurve.core ¶
Reference implementation of the PANOPTES differential photometry algorithm.
This module is a direct, I/O-free transcription of Gee et al., On-sky Demonstration of Precision Photometry with Bayer Color Filter Arrays, section 3.2. Equation numbers in the docstrings refer to that paper.
Everything here operates on plain numpy arrays. There is no pandas, no HDF5, no cloud client and no plotting, so every step is unit-testable in isolation and can be swapped independently. See improvement plan 2.1 for why.
Array shapes¶
A postage stamp cube (PSC) for a single source is (m, n): m frames by
n pixels, with the two spatial axes flattened into j exactly as in the
paper. A collection of r reference PSCs is (r, m, n).
LightcurveResult
dataclass
¶
Everything needed to reproduce, plot and audit one differential lightcurve.
Source code in src/panoptes/pipeline/lightcurve/core.py
coefficients
instance-attribute
¶
(r,) fitted coefficients, aligned with reference_indices.
reference_indices
instance-attribute
¶
(r,) indices into the reference pool, ordered best-match first.
channel
class-attribute
instance-attribute
¶
Which pixels the photometry summed: all, r, g or b.
meta
class-attribute
instance-attribute
¶
Free-form record of the settings used, for provenance.
num_active_references
property
¶
How many coefficients survived regularization (paper Fig. 7: 46 of 100).
subtract_stamp_sky ¶
subtract_stamp_sky(psc: ndarray, sky_mask: ndarray, channel_masks: dict[str, ndarray] | None = None) -> np.ndarray
Remove a per-frame, per-color sky pedestal estimated from the stamp itself.
This is a stopgap, not the right fix. Paper section 4.2 subtracts a
global three-channel background from the full frame before stamps are cut,
and that is where it belongs. The current pipeline computes that background
and then discards it (conformance audit 5.0), so stamps stored in
observation.h5 still carry bias plus sky -- typically 780 ADU per pixel,
which for a median source is over 95% of the stamp sum.
Any precision or transit depth measured on un-subtracted stamps is diluted
by that pedestal and is not a photometric result. Use this to get usable
numbers out of already-processed observations; fix ProcessFITS.ipynb to
stop needing it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
psc
|
ndarray
|
|
required |
sky_mask
|
ndarray
|
|
required |
channel_masks
|
dict[str, ndarray] | None
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of the same shape, pedestal removed. |
Source code in src/panoptes/pipeline/lightcurve/core.py
central_sky_mask ¶
Flat selection mask of pixels outside core_radius of the stamp center.
Source code in src/panoptes/pipeline/lightcurve/core.py
normalize_psc ¶
Flux-marginalize a PSC, per frame (paper Eq. 1).
Each frame is divided by its own summed flux, leaving only the stellar morphology as projected onto the Bayer pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
psc
|
ndarray
|
|
required |
pixel_mask
|
ndarray | None
|
Optional boolean selection mask of length |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Array with the same shape as |
ndarray
|
non-finite are returned as all-NaN so they can be dropped downstream |
ndarray
|
rather than silently contributing zeros. |
Source code in src/panoptes/pipeline/lightcurve/core.py
similarity_scores ¶
similarity_scores(target_norm: ndarray, refs_norm: ndarray, frame_weights: ndarray | None = None) -> np.ndarray
Summed-squared morphological difference to the target (paper Eq. 2).
Lower is more similar. A star compared against itself scores exactly zero.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_norm
|
ndarray
|
|
required |
refs_norm
|
ndarray
|
|
required |
frame_weights
|
ndarray | None
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
|
Source code in src/panoptes/pipeline/lightcurve/core.py
select_references ¶
Indices of the num_refs most similar references, best first.
The caller is responsible for having excluded the target from scores.
Passing a score array that still contains the target is the defect
described in conformance audit 3.4, so this function refuses a perfect
zero score.
Source code in src/panoptes/pipeline/lightcurve/core.py
solve_coefficients ¶
solve_coefficients(target_norm: ndarray, refs_norm: ndarray, method: SolveMethod = 'lasso', alpha: float = 1e-05, pixel_mask: ndarray | None = None, frame_weights: ndarray | None = None) -> np.ndarray
Least-squares coefficients for the synthetic comparison star (paper Eq. 4).
One coefficient per reference, fit jointly across every frame and pixel.
The paper notes that "in practice a regularization term can also [be]
applied" and its Figure 7 shows 46 of 100 coefficients driven to exactly
zero -- a signature of an L1 penalty, not of plain least squares. Plain
ols is therefore not the published configuration; it is provided as
the unregularized baseline to measure against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_norm
|
ndarray
|
|
required |
refs_norm
|
ndarray
|
|
required |
method
|
SolveMethod
|
One of |
'lasso'
|
alpha
|
float
|
Regularization strength (ignored for |
1e-05
|
pixel_mask
|
ndarray | None
|
Optional |
None
|
frame_weights
|
ndarray | None
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in src/panoptes/pipeline/lightcurve/core.py
build_comparison ¶
Apply normalized-space coefficients to the raw references (paper Eq. 5).
The coefficients come from the flux-marginalized fit; they are applied to the flux-carrying stamps. That asymmetry is the heart of the method: the comparison star is built without ever seeing the target's flux, so a real brightness change in the target cannot leak into its own comparison.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refs_raw
|
ndarray
|
|
required |
coefficients
|
ndarray
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in src/panoptes/pipeline/lightcurve/core.py
differential_lightcurve ¶
differential_lightcurve(target_raw: ndarray, comparison: ndarray, pixel_mask: ndarray | None = None, normalize: bool = False) -> np.ndarray
Ratio of summed target flux to summed comparison flux (paper Eq. 6).
The ratio is already a relative flux: the comparison ensemble sets the scale. It does not need, and must not be given, a further normalization to unit median.
normalize defaults to False, which differs from paper Eq. 6 and is
deliberate. PANOPTES targets long-period planets, whose transits can last
longer than a single night's observation, and the survey's goal is to stitch
partial lightcurves from units at different longitudes into one event.
Dividing by the median of a window that is wholly or partly in transit
subtracts the signal from itself: a fully in-transit segment normalizes to a
flat line at 1.0 and the depth is gone. See algorithm design 6.
Set normalize=True only for display, or when the window is known to be
dominated by out-of-transit baseline and nothing downstream will stitch it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_raw
|
ndarray
|
|
required |
comparison
|
ndarray
|
|
required |
pixel_mask
|
ndarray | None
|
Optional |
None
|
normalize
|
bool
|
Divide by the median. Destroys absolute depth; see above. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in src/panoptes/pipeline/lightcurve/core.py
make_lightcurve ¶
make_lightcurve(target_psc: ndarray, reference_pool: ndarray, num_refs: int = 100, method: SolveMethod = 'lasso', alpha: float = 1e-05, aperture_mask: ndarray | None = None, channel_mask: ndarray | None = None, channel: str = 'all', select_on_channel: bool = False, frame_weights: ndarray | None = None) -> LightcurveResult
Run paper sections 3.2.1 through 3.2.5 end to end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_psc
|
ndarray
|
|
required |
reference_pool
|
ndarray
|
|
required |
num_refs
|
int
|
How many references to carry into the coefficient fit. |
100
|
method
|
SolveMethod
|
Coefficient solver, see :func: |
'lasso'
|
alpha
|
float
|
Regularization strength. |
1e-05
|
aperture_mask
|
ndarray | None
|
|
None
|
channel_mask
|
ndarray | None
|
|
None
|
channel
|
str
|
Label recorded on the result. |
'all'
|
select_on_channel
|
bool
|
If True, run selection and the coefficient fit using only the channel's pixels rather than the whole stamp. The paper does selection on all pixels and splits color only at the last step; this flag exists to test the alternative (improvement plan 3.5). |
False
|
frame_weights
|
ndarray | None
|
Optional |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
LightcurveResult
|
class: |
Source code in src/panoptes/pipeline/lightcurve/core.py
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | |