# Beginner: Read and Display A K2 Target Pixel File¶

This notebook tutorial demonstrates how to load and display the contents of a K2 target pixel (tp) file. We will display the flux values from a given cadence, and identify the pixels used in the photometric aperture.

In [1]:
%matplotlib inline
from astropy.io import fits
import matplotlib.pyplot as plt
import numpy as np


## Introduction¶

A target pixel file contains the raw and calibrated fluxes for the pixels downloaded at either 1-minute (short) or 30-minute (long) cadence. It also contains information about the aperture, including which pixels were used to calculate the total flux, which pixels were used to estimate the background flux, and which pixels were used to calculate the flux weighted or pixel response function centroids. The data shown here will be for the star TRAPPIST-1 (the standard readout is EPIC 246199087, a larger readout for this star was also obtained with a K2 ID of EPIC 200164267), which is known to host a system of seven Earth-sized planets.

This tutorial will refer to a couple TESS-related terms that we define here.

• Campaign = During the K2 mission, the Kepler telescope observed the sky in a given pointing along the ecliptic plane for approximately 80 days at a time. Each of these regions is referred to as a "Campaign", starting with Campaign 0 and ending with Campaign 19. There was also a special "Engineering" Campaign before Campaign 0 that lasted ~10 days.
• HDU = Header Data Unit. A FITS file is made up of HDUs that contain data and metadata relating to the file. The first HDU is called the primary HDU, and anything that follows is considered an "extension", e.g., "the first FITS extension", "the second FITS extension", etc.
• BJD = Barycentric Julian Date, the Julian Date that has been corrected for differences in the Earth's position with respect to the Solar System center of mass.
• BKJD = Barycentric Kepler Julian Date, the timestamp measured in BJD, but offset by 2454833.0. I.e., BKJD = BJD - 2454833.0
• Cadence = The interval between flux measurements, either one minute for short cadence or 30 minutes for long cadence.

## Obtaining The Target Pixel File¶

We will read the target pixel file from Campaign 12 using the MAST URL location. So that we can get started with understanding the file contents without reviewing how to automatically search for and retrieve K2 files, we won't show how to search and retrieve K2 target pixel files in this tutorial.

In [2]:
# For the purposes of this tutorial, we just know the MAST URL location of the file we want to examine.
fits_file = "https://archive.stsci.edu/missions/k2/target_pixel_files/c12/246100000/99000/ktwo246199087-c12_lpd-targ.fits.gz"


## Understanding The Target Pixel File Structure¶

The K2 target pixel files contain a primary HDU with metadata stored in the header. The first extension HDU contains more metadata in the header and stores arrays of data in a binary FITS table, which include the timestamps, fluxes, and background fluxes for each cadence of the pixels read out and downloaded by the spacecraft. The second extension HDU contains an image that stores the pixels that were read out, and records information such as which pixels were used in the optimal photometric aperture to create the SAP fluxes. Let's examine the structure of the FITS file using the astropy.fits info function, which shows the FITS file format in more detail.

In [3]:
fits.info(fits_file)

Filename: /root/.astropy/cache/download/py3/2a1d76d4a7a97686769fb2523dbe4763
No.    Name      Ver    Type      Cards   Dimensions   Format
0  PRIMARY       1 PrimaryHDU      54   ()
1  TARGETTABLES    1 BinTableHDU    282   3862R x 13C   [D, E, J, 90J, 90E, 90E, 90E, 90E, 90E, J, E, E, 0E]
2  APERTURE      1 ImageHDU        48   (10, 9)   int32


Let's examine the binary table in the first FITS extension, since that contains the arrays of timestamps and fluxes for each cadence. We will use the astropy.fits getdata function to access the table from the first extension HDU, and then show the columns of the table. We can see included in the table are columns for the timestamps in K2 BJD format (TIME), raw counts (RAW_CNTS), and calibrated fluxes (FLUX).

In [4]:
fits.getdata(fits_file, ext=1).columns

Out[4]:
ColDefs(
name = 'TIME'; format = 'D'; unit = 'BJD - 2454833'; disp = 'D14.7'
name = 'TIMECORR'; format = 'E'; unit = 'd'; disp = 'E14.7'
name = 'CADENCENO'; format = 'J'; disp = 'I10'
name = 'RAW_CNTS'; format = '90J'; unit = 'count'; null = -1; disp = 'I8'; dim = '(10,9)'
name = 'FLUX'; format = '90E'; unit = 'e-/s'; disp = 'E14.7'; dim = '(10,9)'
name = 'FLUX_ERR'; format = '90E'; unit = 'e-/s'; disp = 'E14.7'; dim = '(10,9)'
name = 'FLUX_BKG'; format = '90E'; unit = 'e-/s'; disp = 'E14.7'; dim = '(10,9)'
name = 'FLUX_BKG_ERR'; format = '90E'; unit = 'e-/s'; disp = 'E14.7'; dim = '(10,9)'
name = 'COSMIC_RAYS'; format = '90E'; unit = 'e-/s'; disp = 'E14.7'; dim = '(10,9)'
name = 'QUALITY'; format = 'J'; disp = 'B24.24'
name = 'POS_CORR1'; format = 'E'; unit = 'pixel'; disp = 'E14.7'
name = 'POS_CORR2'; format = 'E'; unit = 'pixel'; disp = 'E14.7'
name = 'RB_LEVEL'; format = '0E'; unit = 'sigma'; disp = 'E14.7'; dim = '(0,9)'
)

If you examine the structure of the data, the table is more complicated than just rows and columns of values. You can see the dimensions of the raw counts, flux, flux errors, flux backgrounds, and flux background errors are 9x10 (the size of the pixel stamp read out for this particular target). We'll show how to examine the fluxes from a particular cadence below.

## Reading the timestamps and fluxes.¶

Now that we have the target pixel file, let's store the timestamps and fluxes for use later.

In [5]:
with fits.open(fits_file, mode="readonly") as hdulist:
k2_bjds = hdulist[1].data['TIME']
raw_counts = hdulist[1].data['RAW_CNTS']
calibrated_fluxes = hdulist[1].data['FLUX']


Let's examine one of these to see how Python stores the data.

In [6]:
print(type(calibrated_fluxes))
print(calibrated_fluxes.shape)

<class 'numpy.ndarray'>
(3862, 9, 10)


So these data are stored as a numpy array. The first dimension is the number of cadences, followed by the size of the readout. Thus, accessing calibrated_fluxes[4,:,:] should return to us the 9x10 grid of pixels from the fifth cadence (remember Python is zero-indexed, so the first cadence is stored in the zeroth index, the second in the first index, etc.)

In [7]:
calibrated_fluxes[4,:,:].shape

Out[7]:
(9, 10)

## Show the calibrated pixel values.¶

Let's display the calibrated fluxes for one of the cadences in our target pixel file. For now, we'll pick the fifth cadence (index number 4). You could of course decide to look at multiple cadences by looping over each index in the first dimension of the structure (e.g., loop over calibrated_fluxes[i,:,:]). You could even make an animated .gif of the fluxes and see how they change from cadence to cadence. To get you started though, let's just display one of the cadence's flux values.

In [8]:
# Start figure and axis.
fig, ax = plt.subplots()
fig.set_size_inches(12., 8.)

# Display the calibrated fluxes as an image for the fifth cadence.
cax = ax.imshow(calibrated_fluxes[4,:,:], cmap=plt.cm.YlGnBu_r, origin="lower")

# Let's define a title for the figure.
fig.suptitle("TRAPPIST-1 Calibrated Fluxes - Campaign 12, Fifth Cadence")

# Add a color bar.
cbar = fig.colorbar(cax)
plt.show()


## Displaying The Aperture Pixel Information¶

Let's read in the second FITS extension HDU to display the aperture information. First, let's read in the aperture pixels from the HDU.

In [9]:
with fits.open(fits_file, mode="readonly") as hdulist:
aperture = hdulist[2].data


Let's plot the pixels as an image.

In [10]:
# Start figure and axis.
fig, ax = plt.subplots()
fig.set_size_inches(12., 8.)

# Display the pixels as an image.
cax = ax.imshow(aperture, cmap=plt.cm.YlGnBu_r, origin="lower")

# Add a color bar.
cbar = fig.colorbar(cax)

# Add a title to the plot.
fig.suptitle("TRAPPIST-1 Aperture - Campaign 12")
plt.show()


## Understanding The Aperture Pixel Values¶

We see the pixel values are integers, but what do they mean? The pixels are bitmasks that encode information about each pixel. You can find a summary of what the different values mean here. For example, a pixel in the aperture that might have a value of 15 can be broken down into power of 2 like: 8+4+2+1 = 15. Referencing the table of values, this means this particular pixel was in was used to calculate the Pixel Response Function (PRF) centroid, was used to calculate the flux weighted centroid, was part of the optimal photometric aperture, and was collected by the spacecraft. Numpy has a bulit-in function that can convert an integer into a binary bit mask. Let's use that now one one of the common values we see in our displayed image above.

In [11]:
# Break down a pixel value of 3 (yellow pixels displayed above) into its constituent bits.
bits = np.binary_repr(3)
print(bits)

11


Binary bits start from the right and end at the left, so the bit farthest on the right is the Least Significant Bit (LSB, equal to 2^0 = 1), the second from the right is 2^1=2, the third from the right is 2^2=4, etc. This bit mask has bit values of 1 (2^0) and 2 (2^1) set. From our look-up table, these values mean the pixels have been: collected by the spacecraft (value = 1) and used in the optimal photometric aperture (value = 2), so these pixels were used from this cadence to create the flux measurement in the mission-produced light curve files.

## Marking All Pixels Used In The Photometric Aperture¶

Let's display the pixels in the aperture FITS extension again, but this time, let's only mark those that are used in the optimal photometric aperture (bit mask value of 2 set). While it is possible to do this in fewer lines, we'll show more of the steps involved for clarity.

In [12]:
# Create an array that will keep track of which pixels have a bitmask
# value of 2 set. To start, everything is set to 0 = not set.  We make it
# the same dimension as the aperture pixel table, for clarity.

# Identify which pixels have the bit mask value of 2 set.  We'll first
# loop over each row (i) in the 9x10 table, and then each column (j).
for i, row in enumerate(aperture):
for j, pix in enumerate(row):

# Get the bitmask as a string of zeros and ones.

# Is the bitmask value of 2 set?  If so, the 2nd integer from the
# right should be set to 1.  We'll use Python negative indexes to
# access the second integer from the right. But, we also need to
# check that the length of the returned string is at least two!
# If the pixel mask is 0 or 1 then it won't even have two digits
# to check, and would result in an error.
if len(this_bitmask) > 1:
if this_bitmask[-2] == '1':
# Then record this pixel as having a bitmask value of 2 set.


Now we can display the aperture, but this time we will only display the bitmask2_set table we've created, where a zero means the bitmask value of 2 is NOT set (thus these pixels aren't used in the optimal photometric aperture) and a one means the bitmask value of 2 is set (and thus these pixels were used in the optimal photometric aperture).

In [13]:
# Start figure and axis.
fig, ax = plt.subplots()
fig.set_size_inches(12., 8.)

# Display, as an image, the 11x11 table that records the bitmask value of 2 being set.
cax = ax.imshow(bitmask2_set, cmap=plt.cm.YlGnBu_r, origin="lower")

# Add a color bar.
cbar = fig.colorbar(cax)

# Add a title to the plot.
fig.suptitle("TRAPPIST-1 Aperture - Campaign 12 - Pixels Used In Phot. Ap.")
plt.show()