How to recover the first TESS planet candidate with Lightkurve?

Data from the TESS mission are available from the data archive at MAST. This tutorial demonstrates how the Lightkurve Python package can be used to read in these data and create your own TESS light curves with different aperture masks.

Below is a quick tutorial on how to get started using Lightkurve and TESS data. We'll use the nearby, bright target Pi Mensae (ID 261136679), around which the mission team recently discovered a short period planet candidate on a 6.27 day orbit. See the pre-print paper by Huang et al (2018) for more details.

TESS data is stored in a binary file format which is documented in the TESS Science Data Products Description Document. Lightkurve provides a TessTargetPixelFile class which allows you to interact with the data easily.

In [1]:
import lightkurve as lk
In [2]:
search_result = lk.search_targetpixelfile('Pi Mensae', mission='TESS', sector=1)
In [3]:
search_result
Out[3]:
SearchResult containing 1 data products.

target_name                     productFilename                        description     distance
----------- ------------------------------------------------------- ------------------ --------
  261136679 tess2018206045859-s0001-0000000261136679-0120-s_tp.fits Target pixel files      0.0
In [4]:
tpf = search_result.download(quality_bitmask='default')
In [5]:
tpf
Out[5]:
TessTargetPixelFile(TICID: 261136679)

TessTargetPixelFile's have many helpful methods and attributes. For example, you can access basic meta data on the target easily:

In [6]:
tpf.mission
Out[6]:
'TESS'
In [7]:
tpf.targetid  # TESS Input Catalog (TIC) Identifier
Out[7]:
261136679
In [8]:
tpf.sector  # TESS Observation Sector
Out[8]:
1
In [9]:
tpf.camera  # TESS Camera Number
Out[9]:
4
In [10]:
tpf.ccd  # TESS CCD Number
Out[10]:
2

We might want to plot the data, we can do this with the plot() method. You can add the keyword aperture_mask to plot an aperture on top of the image. In this case we've used the pipeline_mask which is stored in the original .fits file, but you can use any aperture you like.

In [11]:
%matplotlib inline
tpf.plot(aperture_mask=tpf.pipeline_mask);

If you want to access the original fits file that generated the data you can use the hdu attribute of the tpf. This will return an astropy.io.fits object, for example

In [12]:
tpf.hdu
Out[12]:
[<astropy.io.fits.hdu.image.PrimaryHDU object at 0x1c19e27240>, <astropy.io.fits.hdu.table.BinTableHDU object at 0x1c1c3c7d30>, <astropy.io.fits.hdu.image.ImageHDU object at 0x1c1b54ef28>, <astropy.io.fits.hdu.table.BinTableHDU object at 0x1c1b827be0>]

You can access each extension and the data inside it in the same way you'd use astropy.io.fits. If you want to access data held in the TPF, such as the time of the observations, you can do that easily by using

In [13]:
tpf.time
Out[13]:
array([1325.29698328, 1325.29837215, 1325.29976102, ..., 1353.17431099,
       1353.17569985, 1353.17708871])

This returns the time in units of days counted since Julian Day 2457000.

You can access the corresponding flux values using

In [14]:
tpf.flux
Out[14]:
array([[[  -1.3825388,  -37.66533  ,  -91.830536 , ..., -108.04645  ,
         -103.54175  , -134.1816   ],
        [-135.68407  , -125.30367  , -112.52773  , ..., -113.03839  ,
         -122.92997  , -131.28302  ],
        [-147.63722  , -142.73625  , -134.99854  , ..., -101.330734 ,
         -114.31767  , -121.03055  ],
        ...,
        [-138.13503  , -137.8448   , -146.29425  , ...,  -77.273094 ,
         -105.22347  , -135.82652  ],
        [-142.20255  , -139.6766   , -147.65201  , ..., -118.81814  ,
         -129.52805  , -130.46175  ],
        [-133.71326  , -134.32669  , -148.82013  , ..., -127.16725  ,
          -90.90953  , -112.974434 ]],

       [[  -3.390986 ,  -47.140965 ,  -90.53796  , ..., -106.87563  ,
         -108.55464  , -136.6413   ],
        [-133.7175   , -125.38933  , -114.30842  , ..., -115.82087  ,
         -125.8608   , -132.87209  ],
        [-147.48918  , -142.87836  , -138.43767  , ..., -103.16773  ,
         -116.372955 , -119.86726  ],
        ...,
        [-138.81976  , -138.86803  , -146.27737  , ...,  -78.0859   ,
         -107.27506  , -136.54706  ],
        [-139.40416  , -141.568    , -148.46045  , ..., -122.45605  ,
         -127.82529  , -132.05716  ],
        [-131.66135  , -135.35051  , -150.45163  , ..., -128.91386  ,
          -88.85448  , -111.716286 ]],

       [[  -3.5488758,  -47.50886  ,  -95.015564 , ..., -110.717064 ,
         -108.882744 , -137.5612   ],
        [-137.84918  , -126.03129  , -115.643364 , ..., -114.72093  ,
         -126.24343  , -133.79109  ],
        [-147.31683  , -145.31314  , -137.13574  , ..., -105.026146 ,
         -116.6999   , -122.60661  ],
        ...,
        [-141.17563  , -140.43784  , -149.25896  , ...,  -78.849335 ,
         -108.58714  , -138.34416  ],
        [-140.01288  , -142.26175  , -148.14075  , ..., -126.20568  ,
         -129.13695  , -132.04318  ],
        [-132.21141  , -138.6819   , -146.84227  , ..., -131.71007  ,
          -93.29626  , -116.47916  ]],

       ...,

       [[  18.095486 ,  -34.98728  ,  -91.17793  , ...,  -99.494095 ,
          -93.221954 , -128.76079  ],
        [-124.04631  , -116.41686  , -102.980415 , ..., -107.50661  ,
         -119.95467  , -127.54848  ],
        [-142.71834  , -138.71126  , -129.78357  , ...,  -98.722206 ,
         -112.27363  , -119.09576  ],
        ...,
        [-131.34     , -131.13364  , -141.24506  , ...,  -71.29033  ,
          -98.11233  , -129.48041  ],
        [-134.56529  , -136.50882  , -140.93579  , ..., -114.41215  ,
         -122.87399  , -129.40953  ],
        [-126.638466 , -132.9018   , -142.17902  , ..., -123.72011  ,
          -89.90764  , -108.07525  ]],

       [[  14.83668  ,  -38.491974 ,  -89.118706 , ..., -104.50926  ,
          -98.43293  , -133.46823  ],
        [-129.66971  , -120.571465 , -107.0652   , ..., -110.490425 ,
         -121.109955 , -126.844826 ],
        [-146.30688  , -139.21913  , -132.79062  , ...,  -99.678925 ,
         -112.38672  , -118.37381  ],
        ...,
        [-133.21715  , -132.51721  , -141.62198  , ...,  -73.295105 ,
         -100.20323  , -132.46454  ],
        [-138.18147  , -138.7651   , -144.7824   , ..., -116.421265 ,
         -125.88614  , -129.6993   ],
        [-126.65554  , -130.70747  , -144.31444  , ..., -124.7908   ,
          -86.73392  , -112.15379  ]],

       [[  16.255512 ,  -31.703304 ,  -88.93622  , ...,  -96.80196  ,
          -88.44054  , -128.90186  ],
        [-123.1205   , -115.64475  , -102.827774 , ..., -105.912766 ,
         -116.543015 , -125.83007  ],
        [-143.50493  , -135.33545  , -131.393    , ...,  -96.0497   ,
         -111.65795  , -116.42823  ],
        ...,
        [-130.41649  , -130.36017  , -142.79881  , ...,  -68.48361  ,
          -99.568924 , -132.27666  ],
        [-134.51385  , -135.7366   , -139.90646  , ..., -111.81407  ,
         -125.028984 , -127.74584  ],
        [-122.92239  , -129.48557  , -142.0298   , ..., -121.2471   ,
          -87.20575  , -107.23206  ]]], dtype=float32)

Flux is a numpy.ndarray with a shape of (TIME x PIXELS x PIXELS). If you want to access just the first frame you can use

In [15]:
tpf.flux[0]
Out[15]:
array([[-1.38253880e+00, -3.76653290e+01, -9.18305359e+01,
        -1.29922272e+02, -1.45692978e+02, -1.35893311e+02,
        -9.54683685e+01, -8.85729370e+01, -1.08046448e+02,
        -1.03541748e+02, -1.34181595e+02],
       [-1.35684067e+02, -1.25303673e+02, -1.12527733e+02,
        -1.35690414e+02, -1.45928650e+02, -1.27757614e+02,
        -6.09818077e+01, -7.37526398e+01, -1.13038391e+02,
        -1.22929970e+02, -1.31283020e+02],
       [-1.47637222e+02, -1.42736252e+02, -1.34998535e+02,
        -1.41663147e+02, -1.40064377e+02, -1.18161720e+02,
        -5.37216644e+01, -6.23722382e+01, -1.01330734e+02,
        -1.14317673e+02, -1.21030548e+02],
       [-1.49992447e+02, -1.43351776e+02, -1.39589264e+02,
        -1.37442719e+02, -1.31207718e+02, -9.68078690e+01,
        -1.37817793e+01, -3.41951828e+01, -7.66406403e+01,
        -8.33256989e+01, -8.85187225e+01],
       [-1.37578445e+02, -1.28364151e+02, -1.26376915e+02,
        -1.21656555e+02, -1.14596848e+02, -5.06528702e+01,
         7.68901138e+01,  4.60617752e+01,  2.71646519e+01,
         3.94530640e+01, -2.65274410e+01],
       [-1.36808558e+01,  3.27203102e+01,  5.92592163e+01,
         3.10102577e+01,  1.12594862e+01,  1.24587753e+02,
         3.30445312e+02,  3.36818207e+02,  4.67378265e+02,
         2.58437531e+02,  5.39367867e+00],
       [ 3.06964386e+02,  5.08380646e+02,  6.83612305e+02,
         6.75774414e+02,  6.43811035e+02,  8.33415833e+02,
         1.27430811e+03,  1.84799011e+03,  1.48316943e+03,
         3.57677521e+02, -2.22004585e+01],
       [ 7.54239929e+02,  1.21837598e+03,  1.75272681e+03,
         2.08298779e+03,  2.50489863e+04,  6.54504688e+04,
         6.99400781e+03,  7.02737646e+03,  1.77507739e+03,
         2.59077271e+02, -1.98949337e+01],
       [ 1.11253564e+03,  2.07831738e+03,  3.65158667e+03,
         5.48250391e+03,  1.00965391e+05,  1.02543531e+05,
         4.09393594e+04,  9.44738086e+03,  1.42760950e+03,
         3.31844025e+02,  5.17090683e+01],
       [ 1.02698645e+03,  2.25617969e+03,  5.54376465e+03,
         2.46261230e+04,  1.03035734e+05,  1.04624336e+05,
         6.53138633e+04,  7.71915039e+03,  1.35093494e+03,
         3.36992310e+02, -3.54690289e+00],
       [ 7.21791382e+02,  1.66756409e+03,  3.87029858e+03,
         2.42247129e+04,  1.03909016e+05,  1.06053984e+05,
         4.83635586e+04,  4.23504346e+03,  1.32237488e+03,
         3.16208466e+02, -6.57367096e+01],
       [ 4.29120178e+02,  1.26064551e+03,  3.15358057e+03,
         1.05985273e+04,  1.02944805e+05,  1.05321906e+05,
         1.19889746e+04,  3.39063574e+03,  1.40142529e+03,
         4.15249512e+02, -3.48390923e+01],
       [ 1.72348724e+02,  7.88590881e+02,  2.23820068e+03,
         4.44562012e+03,  5.58241680e+04,  1.03319727e+05,
         5.61203809e+03,  2.66727393e+03,  1.30703625e+03,
         4.99335266e+02,  4.71602020e+01],
       [ 2.69907742e+01,  3.23401794e+02,  9.99374329e+02,
         1.78104407e+03,  2.04844604e+03,  3.67428320e+04,
         2.80467065e+03,  1.85512500e+03,  1.18081396e+03,
         6.49496826e+02,  1.00554329e+02],
       [-7.98464050e+01,  9.25059509e+01,  3.26399536e+02,
         6.55647888e+02,  9.57525818e+02,  1.18310132e+03,
         1.39609094e+03,  1.17153650e+03,  1.15672314e+03,
         7.71616577e+02,  5.45056877e+01],
       [-1.13249367e+02, -6.13406181e+01,  5.93428993e+01,
         1.54688034e+02,  3.48396606e+02,  5.27463257e+02,
         6.76866699e+02,  6.34336426e+02,  5.01662018e+02,
         3.01781311e+02, -2.48897457e+01],
       [-8.82987518e+01, -1.17512108e+02, -9.47615585e+01,
        -3.40316010e+01,  4.28901634e+01,  1.52270020e+02,
         2.72233185e+02,  2.77604492e+02,  1.88234268e+02,
         4.79093513e+01, -6.75118103e+01],
       [-8.60586777e+01, -1.14533005e+02, -1.38196228e+02,
        -1.20548019e+02, -7.74374313e+01, -3.44268494e+01,
         3.27217293e+01,  4.88377647e+01,  1.26917982e+01,
        -5.03569221e+01, -1.11872086e+02],
       [-1.38135025e+02, -1.37844803e+02, -1.46294250e+02,
        -1.46713867e+02, -1.37058548e+02, -1.05716675e+02,
        -6.76952667e+01, -5.82716599e+01, -7.72730942e+01,
        -1.05223473e+02, -1.35826523e+02],
       [-1.42202545e+02, -1.39676605e+02, -1.47652008e+02,
        -1.50885605e+02, -1.49152878e+02, -1.39763824e+02,
        -1.17666542e+02, -1.09747162e+02, -1.18818138e+02,
        -1.29528046e+02, -1.30461746e+02],
       [-1.33713257e+02, -1.34326691e+02, -1.48820129e+02,
        -1.51972229e+02, -1.49277893e+02, -1.45098679e+02,
        -1.30031540e+02, -1.21871094e+02, -1.27167252e+02,
        -9.09095306e+01, -1.12974434e+02]], dtype=float32)

These values are in units electrons per second.

Building Light Curves from TPFs

We can use the to_lightcurve() method to turn this TPF into a light curve using Simple Aperture Photometry. This will put an aperture on the target, and sum up the flux in all the pixels inside the aperture.

The default for to_lightcurve() is to use the mask generated by the TESS pipeline.

In [16]:
lc = tpf.to_lightcurve()

Now we can use the plot function to take a look at the data.

In [17]:
lc.errorbar();

This looks pretty good, but maybe we can improve things by creating a new aperture.

In [18]:
aperture_mask = tpf.create_threshold_mask(threshold=10)

# Plot that aperture
tpf.plot(aperture_mask=aperture_mask);
In [19]:
lc = tpf.to_lightcurve(aperture_mask=aperture_mask)
In [20]:
lc.errorbar();

There's a long term trend in this dataset, which we can remove with a simple smoothing filter. You can use the lc.flatten() method to apply and divide the Savitzky-Golay smoothing filter. Here we'll use a window_length of 1001 cadences, which is roughly a 5% of the full length of the light curve.

In [21]:
# Number of cadences in the full light curve
print(lc.time.shape)
(18104,)
In [22]:
flat_lc = lc.flatten(window_length=1001)
flat_lc.errorbar();

The light curve looks much flatter. Unfortunately there is a portion of the light curve that is very noisy, due to a jitter in the TESS spacecraft. We can remove this simply by masking the light curve. First we'll select the times that had the jitter.

In [23]:
# Flag the times that are good quality
mask = (flat_lc.time < 1346) | (flat_lc.time > 1350)

Then we can just clip those times out.

In [24]:
masked_lc = flat_lc[mask]
masked_lc.errorbar();

We can use Lightkurve to plot these two light curves over each other to see the difference.

In [25]:
# First define the `matplotlib.pyplot.axes`
ax = flat_lc.errorbar()

# Pass that axis to the next plot
masked_lc.errorbar(ax=ax, label='masked');

This looks much better. Now we might want to clip out some outliers from the light curve. We can do that with a simple lightkurve function remove_outliers()

In [26]:
clipped_lc = masked_lc.remove_outliers(sigma=6)
clipped_lc.errorbar();