Using the to_fits and read_generic_lightcurve functions#

Learning Goals#

By the end of this tutorial you will:

Introduction#

The intent of the tutorial is to illustrate how one might use data from any mission (not just Kepler, K2, or TESS) and analize it within Lightkurve.

Imports#

This tutorial requires:

[15]:
%matplotlib inline

import lightkurve as lk
import matplotlib.pyplot as plt
import numpy as np

from astropy.time import Time
import astropy.units as u

1: Creating our lightcurve data and our LightCurve Object#

First we will create some fake lightcurve data to use as an example and convert this into a LightCurve Object. You could also download a LightCurve Object via using `search_lightcurve <https://lightkurve.github.io/lightkurve/reference/api/lightkurve.search_lightcurve.html?highlight=search_lightcurve#lightkurve.search_lightcurve>`__ and `download() <https://lightkurve.github.io/lightkurve/reference/api/lightkurve.SearchResult.download.html?highlight=download#lightkurve.SearchResult.download>`__ if you wish, but for now we will create our simple data set.

[16]:
#Create the time values and specify the format
time_values = Time(np.linspace(2458000, 2458000 + 10, 100), format='jd')

#Create the flux and flux error values
flux_values = np.sin(time_values.value * 0.5) + 1000
flux_err_values = np.full_like(flux_values, 0.01, dtype=np.float32)

#Specify the flux units
unit_flux = u.electron/u.second

Using this data set we then use the LightCurve function to create our object and look at the data.

[17]:
#Create the LightCurve object and look
my_lc = lk.LightCurve(time=time_values, flux=flux_values * unit_flux, flux_err=flux_err_values * unit_flux)
my_lc
[17]:
LightCurve length=100
timefluxflux_err
electron / selectron / s
Timefloat64float32
2458000.01000.45365490544710.009999999776482582
2458000.1010101011000.40808661657670.009999999776482582
2458000.2020202021000.36147761788350.009999999776482582
2458000.3030303031000.31394677225240.009999999776482582
2458000.4040404041000.26561529413830.009999999776482582
2458000.5050505051000.21660643912810.009999999776482582
2458000.6060606061000.16704519048440.009999999776482582
2458000.7070707071000.11705794019420.009999999776482582
2458000.80808080831000.0667721664090.009999999776482582
.........
2458009.19191919171000.83244443768360.009999999776482582
2458009.2929292931000.8593563636950.009999999776482582
2458009.3939393941000.88407674353620.009999999776482582
2458009.4949494951000.90654253497940.009999999776482582
2458009.5959595961000.92669644535940.009999999776482582
2458009.6969696971000.94448707781210.009999999776482582
2458009.7979797981000.95986906241190.009999999776482582
2458009.8989898991000.97280317160380.009999999776482582
2458010.01000.98325642066630.009999999776482582

We can now access the column values directly as an attribute of the object.

[18]:
my_lc.flux
[18]:
$[1000.4537,~1000.4081,~1000.3615,~1000.3139,~1000.2656,~1000.2166,~1000.167,~1000.1171,~1000.0668,~1000.0163,~999.96582,~999.91541,~999.86521,~999.81536,~999.76598,~999.7172,~999.66914,~999.62192,~999.57566,~999.53049,~999.48652,~999.44385,~999.4026,~999.36288,~999.32478,~999.28841,~999.25384,~999.22119,~999.19051,~999.1619,~999.13543,~999.11117,~999.08917,~999.06949,~999.05219,~999.0373,~999.02487,~999.01492,~999.00749,~999.00259,~999.00023,~999.00042,~999.00316,~999.00845,~999.01626,~999.02658,~999.03938,~999.05463,~999.0723,~999.09233,~999.11467,~999.13927,~999.16607,~999.19499,~999.22597,~999.25892,~999.29376,~999.3304,~999.36875,~999.40871,~999.45018,~999.49305,~999.53721,~999.58255,~999.62896,~999.67631,~999.72449,~999.77337,~999.82283,~999.87274,~999.92297,~999.97341,~1000.0239,~1000.0743,~1000.1246,~1000.1745,~1000.224,~1000.2729,~1000.3211,~1000.3685,~1000.415,~1000.4604,~1000.5046,~1000.5476,~1000.5891,~1000.6292,~1000.6676,~1000.7043,~1000.7393,~1000.7723,~1000.8034,~1000.8324,~1000.8594,~1000.8841,~1000.9065,~1000.9267,~1000.9445,~1000.9599,~1000.9728,~1000.9833] \; \mathrm{\frac{e^{-}}{s}}$

We can also plot the data.

[19]:
my_lc.plot()
[19]:
<Axes: xlabel='Time [JD]', ylabel='Flux [$\\mathrm{e^{-}\\,s^{-1}}$]'>
../../_images/tutorials_2-creating-light-curves_2-4-using_the_to_fits_and_read_generic_lightcurve_function_9_1.png

Additional meta data or keyword arguments may be added via a dictionary as part of LightCurve function.

For example we could define the mission and the telescope we obtained the data from via the following:

[20]:
my_lc.meta['TELESCOP'] = "MY_FAKE_TELESCOPE"
my_lc.meta['MISSION'] = "MY_FAKE_MISSION"
print(my_lc.meta['TELESCOP'], my_lc.meta['MISSION'])
MY_FAKE_TELESCOPE MY_FAKE_MISSION

2: Saving the LightCurve Object as a FITS file#

We can now convert our object into a FITS file using the to_fits function. For this we simply need to state the file name and path. Be sure to state if you want your file to be overwritten or not using either False or True.

[21]:
output_filename = './my_custom_lightcurve.fits'
my_lc.to_fits(output_filename, overwrite=True)
f"LightCurve saved to {output_filename}"
[21]:
'LightCurve saved to ./my_custom_lightcurve.fits'

Note we can also add information to our fits file using an extra_data dictionary. Defined extra keywords or columns would then be included in the FITS file. Keywords would be stroed in the primary header and np.array data as columns in the 1st extension. As an example let’s add an “ORIGIN” keyword which will indicate where got our data from.

[22]:
extra_data = {"ORIGIN": "Tutorial"}

We can then overwrite our old file and add this so when we read it in, it will be there.

[23]:
my_lc.to_fits(output_filename, overwrite=True, **extra_data)
[23]:
[<astropy.io.fits.hdu.image.PrimaryHDU object at 0x1687bb440>, <astropy.io.fits.hdu.table.BinTableHDU object at 0x16890c2f0>]

3: Reading in a FITS file#

We can now use the read_generic_lightcurve funtion to read in our FITS file as a LightCurve Object. To do this we must specify the file and the relevent columns for our data.

[24]:
read_lc = lk.io.generic.read_generic_lightcurve(filename='./my_custom_lightcurve.fits', time_column="time", flux_column="flux", flux_err_column="flux_err", time_format="jd")

We can now query the object as done previously.

[25]:
read_lc
[25]:
LightCurve length=100 LABEL="None" FLUX_ORIGIN=lightkurve.LightCurve.to_fits()
timefluxflux_err
electron / selectron / s
Timefloat32float32
2458000.01000.45367431640620.009999999776482582
2458000.1010101011000.40808105468750.009999999776482582
2458000.2020202021000.36145019531250.009999999776482582
2458000.3030303031000.313964843750.009999999776482582
2458000.4040404041000.2656250.009999999776482582
2458000.5050505051000.21661376953120.009999999776482582
2458000.6060606061000.16705322265620.009999999776482582
2458000.7070707071000.11706542968750.009999999776482582
2458000.80808080831000.06677246093750.009999999776482582
.........
2458009.19191919171000.83245849609380.009999999776482582
2458009.2929292931000.8593750.009999999776482582
2458009.3939393941000.88409423828120.009999999776482582
2458009.4949494951000.90655517578120.009999999776482582
2458009.5959595961000.92669677734380.009999999776482582
2458009.6969696971000.94445800781250.009999999776482582
2458009.7979797981000.95983886718750.009999999776482582
2458009.8989898991000.97277832031250.009999999776482582
2458010.01000.98327636718750.009999999776482582
[26]:
read_lc.flux
[26]:
$[1000.4537,~1000.4081,~1000.3615,~1000.314,~1000.2656,~1000.2166,~1000.1671,~1000.1171,~1000.0668,~1000.0163,~999.96582,~999.91541,~999.86523,~999.81537,~999.76599,~999.71722,~999.66913,~999.62195,~999.57568,~999.53052,~999.48651,~999.44385,~999.40259,~999.36285,~999.32477,~999.28839,~999.25385,~999.22119,~999.19049,~999.16193,~999.13544,~999.11115,~999.08917,~999.06952,~999.05219,~999.03729,~999.02484,~999.01489,~999.00751,~999.00256,~999.00024,~999.00043,~999.00317,~999.00842,~999.01624,~999.02655,~999.03937,~999.05463,~999.07233,~999.09235,~999.11469,~999.13928,~999.16608,~999.19501,~999.22595,~999.25891,~999.29376,~999.33038,~999.36877,~999.40869,~999.4502,~999.49304,~999.53723,~999.58258,~999.62897,~999.67633,~999.72449,~999.77338,~999.82281,~999.87274,~999.92297,~999.97339,~1000.0239,~1000.0743,~1000.1246,~1000.1745,~1000.224,~1000.2729,~1000.3212,~1000.3685,~1000.415,~1000.4604,~1000.5046,~1000.5475,~1000.5891,~1000.6292,~1000.6676,~1000.7043,~1000.7393,~1000.7723,~1000.8034,~1000.8325,~1000.8594,~1000.8841,~1000.9066,~1000.9267,~1000.9445,~1000.9598,~1000.9728,~1000.9833] \; \mathrm{\frac{e^{-}}{s}}$
[27]:
read_lc.plot()
[27]:
<Axes: xlabel='Time [JD]', ylabel='Flux [$\\mathrm{e^{-}\\,s^{-1}}$]'>
../../_images/tutorials_2-creating-light-curves_2-4-using_the_to_fits_and_read_generic_lightcurve_function_23_1.png

We can even check the meta data to make sure our telescope, mission, and origin information was saved correctly.

[28]:
read_lc.meta
[28]:
{'EXTNAME': 'PRIMARY',
 'CHECKSUM': 'AaAiBX9ZAaAfAU7Z',
 'DATASUM': '0',
 'SIMPLE': True,
 'BITPIX': 8,
 'NAXIS': 0,
 'EXTEND': True,
 'NEXTEND': 2,
 'EXTVER': 1,
 'ORIGIN': 'Tutorial',
 'DATE': '2026-03-04',
 'CREATOR': 'lightkurve.LightCurve.to_fits()',
 'TELESCOP': 'MY_FAKE_TELESCOPE',
 'INSTRUME': None,
 'OBJECT': None,
 'RADESYS': 'ICRS',
 'RA_OBJ': None,
 'DEC_OBJ': None,
 'EQUINOX': 2000.0,
 'PROCVER': '2.5.2dev',
 'MISSION': 'MY_FAKE_MISSION',
 'LABEL': None,
 'RA': None,
 'DEC': None,
 'FILENAME': './my_custom_lightcurve.fits',
 'FLUX_ORIGIN': 'lightkurve.LightCurve.to_fits()'}

We now know how to create a LightCurve Object, save it as a FITS file, and read in a FITS file as a LightCurve Object.