Light curves & inputs¶
Every periodogram starts from a light curve: observation times, brightnesses, and optional errors. cuPeriod accepts that data in whatever form you already have it and normalizes it internally, so you rarely have to reshape anything by hand.
Accepted inputs¶
cuperiod.periodogram() (and cuperiod.to_input(), which it calls) accept:
Input |
Example |
|---|---|
A |
|
A dict of arrays |
|
A pandas |
|
An astropy |
|
A |
|
A |
Times are in days throughout cuPeriod (JD/HJD/BJD/MJD all work — periodograms are invariant to the time origin). Values may be magnitude or flux.
Building a LightCurve directly¶
For full control, construct a LightCurve:
from cuperiod import LightCurve
lc = LightCurve.from_arrays(time, mag, err) # error is optional
print(lc.n, lc.baseline) # point count, time span (days)
Useful attributes and transforms:
lc.n— number of points;lc.baseline—max(t) - min(t)in days.lc.finite()— a copy with non-finite points (and non-positive errors) removed.lc.as_flux()/lc.as_magnitude()/lc.in_domain(...)— domain conversion (below).
Note
cuPeriod does not shift your time origin. Periodograms are invariant to it, and each method references times to their own minimum internally for numerical stability, so the container keeps your input times verbatim.
Loading from a file¶
LightCurve.from_file reads CSV, ECSV, FITS, and
Parquet by extension; from_fits() targets a specific FITS HDU:
lc = LightCurve.from_file("star.parquet")
lc = LightCurve.from_file("star.csv")
lc = LightCurve.from_fits("lightcurve.fits", hdu=1)
The originating path is recorded in lc.meta["source"].
Column auto-detection with ColumnMap¶
Light curves will always carry temporal and magnitude/flux measurements, with optional
magnitude/flux errors. cuPeriod detects the common spellings case-insensitively,
most-specific first, so a table with both BJD_TDB and JD picks the corrected time.
The detection lists are:
Time —
bjd_tdb,bjd,hjd,btjd,bkjd,mjd,hmjd,midpointMjdTai,obsTime,jd,time,date,tMagnitude —
mag,magnitude,vmag/gmag/rmag/bmag/imag,psfMag,MAG_0,mFlux —
PDCSAP_FLUX,KSPSAP_FLUX,SAP_FLUX,psfFlux,flux,uJy,mJy, … (corrected/detrended fluxes are preferred over raw)Error — resolved to match the value’s measurement and domain: a flux value pairs with
flux_err/flux_error/psfFluxErr/duJy, a magnitude value withmag_err/magerr/dm/MER_0, then the neutralerr/error/sigmaBand —
band,filter,filtercode,filterID,phot_filter,passband,fid
The error is matched to the same measurement as the value, so a table that carries
both (e.g. ATLAS’s m/dm and uJy/duJy) pairs uJy with duJy, never with the
magnitude error.
When detection isn’t enough — ambiguous names, or a column you want to force — pin it with
ColumnMap:
from cuperiod import ColumnMap
cmap = ColumnMap(time="HJD", value="Vmag", error="e_Vmag")
pg = cup.periodogram(df, "GLS", columns=cmap)
Any field left None is auto-detected; a non-None field is honored verbatim (and
raises ColumnResolutionError if that column is absent). Only time and
value are required; error and band are optional.
Supported surveys¶
The detection lists cover the standard light-curve products of the major time-domain
surveys, so a downloaded table usually needs no column hints — cup.periodogram(df, "GLS") just works:
Survey |
Time |
Value |
Error |
Band |
|---|---|---|---|---|
ASAS-SN (Sky Patrol) |
|
|
|
|
ASAS-3 |
|
|
|
— |
ATLAS |
|
|
|
|
CRTS / CSS |
|
|
|
— |
ZTF |
|
|
|
|
Pan-STARRS (PS1) |
|
|
|
|
LSST / Rubin |
|
|
|
|
TESS (SPOC / QLP) |
|
|
|
— |
Kepler (SPOC) |
|
|
|
— |
Gaia (DR3 epoch) |
|
|
|
|
MACHO |
|
|
|
red / blue ³ |
OGLE |
|
magnitude |
error |
— (per file) |
¹ ATLAS’s single-character F filter isn’t auto-detected (to avoid clashing with a flux
f); pass ColumnMap(band="F") if you need it for multi-band. The value defaults to the
flux (uJy); pin ColumnMap(value="m") for magnitudes. ² LSST DP0.2 uses the older
midPointTai / psFlux / psFluxErr / filterName, which are also detected. ³ MACHO’s
raw per-column header spellings are not consistently documented; if a file uses
non-standard names, pass an explicit ColumnMap.
Note
Headerless files — OGLE .dat photometry (and some raw ASAS-3 dumps) have no column
names to detect. Read them positionally instead, e.g. with
numpy.loadtxt:
import numpy as np
from cuperiod import LightCurve
hjd, mag, err = np.loadtxt("OGLE-LMC-CEP-0001.dat", usecols=(0, 1, 2), unpack=True)
pg = cup.periodogram(LightCurve.from_arrays(hjd, mag, err), "GLS")
Magnitude vs flux: the Domain¶
A value column is either a magnitude or a flux. cuPeriod infers which from the
column name (flux/fnu → flux, mag/… → magnitude), defaulting to magnitude, and you
can always state it explicitly:
from cuperiod import Domain
pg = cup.periodogram(df, "GLS", domain=Domain.FLUX) # or domain="flux"
Why it matters: box/transit methods (BLS, TLS) work in flux, where an eclipse or
transit is a dip. If you hand them magnitudes, cuPeriod converts to flux automatically
(flux = 10 ** (-0.4 * mag), with error propagation), so you don’t have to. The Fourier
and fold methods (GLS, PDM, CE, string-length, MHAOV) are domain-agnostic.
You can convert by hand too:
lc_flux = lc.as_flux() # no-op if already flux
lc_mag = lc.as_magnitude() # no-op if already magnitude
Cleaning¶
Periodograms need finite inputs. finite() drops NaN/inf times and values, and — when
errors are present — any point whose error is non-finite or <= 0 (an unusable weight):
lc = LightCurve.from_file("messy.csv").finite()
Methods also enforce a minimum point count (min_detections in their settings) and raise
InsufficientDataError on too-sparse curves.
Next: Choosing a method — choosing the right statistic for your signal.