Let's begin with the same WASP-100 we used in the quickstart tutorial. I'm going to hide warnings too, just to make it easier to see what's going on.
from IPython.display import Image
import warnings
warnings.filterwarnings('ignore')
import eleanor
import numpy as np
import matplotlib.pyplot as plt
star = eleanor.Source(tic=38846515, sector=1)
data = eleanor.TargetData(star, height=15, width=15, bkg_size=31, do_psf=False, do_pca=True)
q = data.quality == 0
INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-1-cal-0902-1078_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-1-cal-0902-1078_tess_v2_bkg.fits with expected size 78955200. [astroquery.query] INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-1-cal-0902-1078_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-1-cal-0902-1078_tess_v2_pc.fits with expected size 158022720. [astroquery.query] INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-1-cal-0902-1078_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-1_tess_v2_pm.txt with expected size 237847. [astroquery.query] WARNING:tensorflow:From /Users/bmontet/research/tess/eleanor/eleanor/targetdata.py:837: The name tf.logging.set_verbosity is deprecated. Please use tf.compat.v1.logging.set_verbosity instead. WARNING:tensorflow:From /Users/bmontet/research/tess/eleanor/eleanor/targetdata.py:837: The name tf.logging.ERROR is deprecated. Please use tf.compat.v1.logging.ERROR instead.
100%|██████████| 1282/1282 [00:10<00:00, 113.98it/s]
What if you're not satisfied with $\texttt{eleanor}$'s default choice of aperture? Well, we provide you with three ways to explore different apertures.
(1) All of the apertures $\texttt{eleanor}$ tries behind the scenes are saved! So are their raw and corrected light curves. These are stored as data.all_apertures
, data.all_raw_flux
, and data.all_corr_flux
, respectively. We can explore these options by calling the following, for example.
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(data.all_apertures[0], cmap='Greys', alpha=0.7)
ax1.set_title('Aperture over TPF')
ax2.plot(data.time[q], data.all_raw_flux[0][q]/np.nanmedian(data.all_raw_flux[0][q]), 'k', label='Raw')
ax2.plot(data.time[q], data.all_corr_flux[0][q]/np.nanmedian(data.all_corr_flux[0][q]) - 0.015, 'r', label='Corrected')
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux')
ax2.legend();
(2) $\texttt{eleanor}$ can help you create your own mask. By calling eleanor.custom_aperture()
, we can choose from a circular or rectangular aperture. We can also choose the size (radius or length x width) and the pixel the aperture is centered on by passing in a list of $\textit{(x,y)}$ to pos
. The aperture will only be created on pixels within the TPF (it won't spill over to other pixels). Calling eleanor.custom_aperture()
overwrites eleanor.TargetData.aperture
, eleanor.TargetData.raw_flux
, and eleanor.TargetData.corr_flux
.
eleanor.TargetData.custom_aperture(data, shape='circle', r=2, pos=[7,6], method='exact')
eleanor.TargetData.get_lightcurve(data)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(data.aperture, cmap='Greys', alpha=0.5)
ax1.set_title('Aperture over TPF')
#plt.imshow(data.aperture)
ax2.plot(data.time[q], data.raw_flux[q]/np.nanmedian(data.raw_flux[q]), 'k', label='Raw')
ax2.plot(data.time[q], data.corr_flux[q]/np.nanmedian(data.corr_flux[q]) - 0.015, 'r', label='Corrected')
ax2.legend()
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux');
(3) We can pass in our own mask by creating a 2D array of the same shape as the TPF and calling eleanor.TargetData.get_lightcurve(aperture=mask)
mask = np.zeros(np.shape(data.tpf[0]))
mask[6:8,6:8] = 1
data.get_lightcurve(aperture=mask)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(mask, cmap='Greys', alpha=0.5)
ax1.set_title('Aperture over TPF')
#plt.imshow(data.aperture)
ax2.plot(data.time[q], data.raw_flux[q]/np.nanmedian(data.raw_flux[q]), 'k', label='Raw')
ax2.plot(data.time[q], data.corr_flux[q]/np.nanmedian(data.corr_flux[q]) - 0.035, 'r', label='Corrected')
ax2.legend()
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux');
When we call eleanor.TargetData(), some simple systematics corrections are automatically performed on the light curve. Let's apply those explicitly to the newly created raw light curve from the custom aperture and see how we can change them.
corr_flux = eleanor.TargetData.corrected_flux(data, flux=data.raw_flux)
plt.figure(figsize=(15,5))
plt.plot(data.time[q], corr_flux[q], 'k')
plt.ylabel('Flux', fontsize=24)
plt.xlabel('Time', fontsize=24);
If we think certain data are bad, but not quality flagged, we can give them a different quality label and re-run this function, causing them to be ignored so they do not influence other cadences. By default, all highest quality data have flag zero and all other flags are positive. Let's throw out a four-day chunk temporarily:
data.quality[q][(data.time[q] > 1333) & (data.time[q] < 1337)] = -8675309
corr_flux = eleanor.TargetData.corrected_flux(data, flux=data.raw_flux)
q = data.quality <= 0.5
plt.figure(figsize=(15,5))
plt.plot(data.time[q], corr_flux[q], 'k')
plt.ylabel('Flux', fontsize=24)
plt.xlabel('Time', fontsize=24);
By default, the corrected_flux
function removes signals that are correlated with the position of the star on the detector, with common modes of nearby stars, and with the background. We can stop these from being a part of corrected_flux
by setting them to zero.
data.cbvs = np.zeros_like(data.cbvs)
data.centroid_xs = np.zeros_like(data.centroid_xs)
data.centroid_ys = np.zeros_like(data.centroid_ys)
corr_flux = eleanor.TargetData.corrected_flux(data, flux=data.raw_flux)
plt.figure(figsize=(15,5))
plt.plot(data.time[q], corr_flux[q], 'k')
plt.ylabel('Flux', fontsize=24)
plt.xlabel('Time', fontsize=24);
In this case, we have a noisier light curve that looks more like the raw flux.
We can always get back to where we started by just re-running eleanor.TargetData()
with the same call we used initially.
data = eleanor.TargetData(star, height=15, width=15, bkg_size=31, do_psf=False, do_pca=True)
100%|██████████| 1282/1282 [00:11<00:00, 114.33it/s]
With the default settings, eleanor
will attempt to find the proper postcard for your target, as well as other data about your system, through MAST. If you don't have internet access, but have downloaded your postcard already (if you've made a light curve for your star of interest previously, you may already have the postcard in your ~/.eleanor
directory), you can avoid all need for the internet with a few simple tricks.
All you need to do are pass through local = True
in your call to eleanor.Source()
and give the directory of your postcard and pointing model. By default, these are downloaded to the same directory. If you don't pass through a pointing model directory, it will assume it's in the same place as your postcard. I've put mine in ../../testdir1
and ../../testdir2
, respectively.
You also need to pass through your star's coordinates. Normally you can pass through one of a name, TIC ID, Gaia DR2, ID, or coordinates. In these cases, behind the scenes eleanor
will use any one of these to get the rest of this information, but behind the scenes relies on the coordinates. You need to pass through the other values as well, but just so they're set to something so eleanor
doesn't try to look them up. You can set them to zero, that's fine.
coords = (68.959732, -64.02704)
star = eleanor.Source(coords=coords, tic=0, gaia=0, sector=1, local=True,
post_dir='./testdir1')#, pm_dir='./testdir2')
data = eleanor.TargetData(star, do_pca=True, do_psf=False)
plt.figure(figsize=(15,5))
q = data.quality == 0
plt.plot(data.time[q], corr_flux[q], 'k')
plt.ylabel('Flux', fontsize=24)
plt.xlabel('Time', fontsize=24);
The only thing that will be missing is star.tess_mag
, but you can set that yourself too before you run data.save()
and it will be recorded properly.
star.tess_mag = 10.53
When you pass through coords
into eleanor.Source()
, it will use these coordinates to find the TIC entry at that location. However, some targets (often very faint or non-stellar in nature) do not appear in the TIC. This will produce an error that inhibits eleanor.Source()
from running successfully. However, as we noted in the previous section, eleanor
does not use the TIC ID behind the scenes. Passing through a false TIC ID and the proper coordinates will work just fine.
eleanor
tests a variety of apertures and selects the one with the lowest CDPP. Very saturated targets ($T < 6-7$, depending on location on the detector) have many pixels at the saturation limit. Choosing a small number of saturated pixels will then produce a very stable light curve, leading eleanor
to choose very small apertures that do not represent the true astrophysical variability from these sources. To make light curves from these, we need to use larger apertures. This is possible!
star = eleanor.Source(name='alpha draconis', sector=15)
data = eleanor.TargetData(star, height=31, width=13)
No eleanor postcard has been made for your target (yet). Using TessCut instead.
plt.imshow(data.tpf[300]);
Yes, that's a saturated target. Let's define our own aperture and go forth.
ap = np.zeros_like(data.tpf[300])
ap[:,4:11] = 1
data.get_lightcurve(aperture=ap)
q = data.quality == 0
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(data.aperture, cmap='Greys', alpha=0.5)
ax1.set_title('Aperture over TPF')
#plt.imshow(data.aperture)
ax2.plot(data.time[q], data.corr_flux[q]/np.nanmedian(data.corr_flux[q]) - 0.0, 'k')
ax2.set_ylim(0.999, 1.0004)
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux');
Alpha draconis, with $V=3.7$, has a deep secondary eclipse as reported in Bedding et al. (2020), and the star is so bright the photon noise is at the few ppm level on 30-minute timescales.
It often works very well for bright, saturated targets to use additional regressors, drawing on the corner pixels of the TPF to use on the detrending. An example of this is below.
Let's make sure we can recover the planet discovered with TESS data around the bright star $\pi$ Mensae, combining what we have learned about saturated targets with the addition of regressing against corner pixels in the TPF for improved background subtraction.
star = eleanor.Source(name='pi Mensae', sector=1)
data = eleanor.TargetData(star, height=21, width=13)
INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-2-cal-1588-0268_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-2-cal-1588-0268_tess_v2_bkg.fits with expected size 78955200. [astroquery.query] INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-2-cal-1588-0268_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-2-cal-1588-0268_tess_v2_pc.fits with expected size 158022720. [astroquery.query] INFO: Found cached file /Users/bmontet/.eleanor/mastDownload/HLSP/hlsp_eleanor_tess_ffi_postcard-s0001-4-2-cal-1588-0268_tess_v2_pc/hlsp_eleanor_tess_ffi_postcard-s0001-4-2_tess_v2_pm.txt with expected size 239144. [astroquery.query]
plt.imshow(data.tpf[300]);
ap = np.zeros_like(data.tpf[300])
ap[:,3:10] = 1
data.get_lightcurve(aperture=ap)
corr_flux = eleanor.TargetData.corrected_flux(data, flux=data.raw_flux, regressors='corner')
q = data.quality == 0
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(data.aperture, cmap='Greys', alpha=0.5)
ax1.set_title('Aperture over TPF')
#plt.imshow(data.aperture)
ax2.plot(data.time[q], corr_flux[q]/np.nanmedian(corr_flux[q]) - 0.0, 'k')
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux');
It looks like there's a planet here! Let's fold on the known period and phase:
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,4), gridspec_kw={'width_ratios':[1,3]})
ax1.imshow(data.tpf[0])
ax1.imshow(data.aperture, cmap='Greys', alpha=0.5)
ax1.set_title('Aperture over TPF')
#plt.imshow(data.aperture)
modtime = np.mod(data.time[q], 6.268)-2.96
ax2.plot(modtime, corr_flux[q]/np.nanmedian(corr_flux[q]) - 0.0, 'k.')
ax2.set_xlabel('Time [BJD - 2457000]')
ax2.set_ylabel('Normalized Flux');
There it is!