Timefilter


This tutorial outlines the steps needed to filter out unwanted COS data in an exposure. To do this, we will use the TimeFilter python module to select bad data in COS corrtag files, and then re-extract the data into x1d spectra.

This notebook is written in Python, as that is the language of the calibration pipeline (CalCOS) and the Timefilter module. This task can also be run from PyRAF, and is located in the stsdas.hst_calib.hstcos package.

In [1]:
#-- Necessary library imports for the tutorial
import os
import glob
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from astropy.io import fits

from calcos import calcos
from costools import timefilter
The following tasks in the costools package can be run with TEAL:
         splittag                 timefilter                 x1dcorr

In [2]:
#-- iPython notebook 'magic' command to make plots appear within the 
#-- browser instead of opening a separate plotting window.
%matplotlib inline

#-- Increase the default fontsize for the plots
mpl.rcParams['font.size'] = 16

Understanding Airglow


HST UV spectra are subject to contamination from geocoronal airglow emission. The most prominent example of this is the strong emission from Hydrogen Lyman Alpha at 1215.6 Anstroms; present in all COS observations that cover that wavelength.

A list of COS airglow observations, along with possible UV airglow lines, can be found on the airglow webpage

Airglow in data


Three of the strongest lines seen in COS data are given below. The strengths of these lines varies from observation to observation, due to changes in the solar activity and changes in HST's orbital position.

  • Lyman Alpha 1215.67
  • OI 1302.2 - 1306.0
  • NI 1199.5 - 1200.7

To see this, we will look at some COS observations taken of blank sky, and thus contain only airglow features. The top panel in the figure below shows the region around the OI emission, while the bottom panel shows both the Lyman Alpha and the faint NI line. Two observations have been overplotted, and the variation in airglow strength can been seen.

In [4]:
fig = plt.figure(figsize=(15, 15))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)

for item in glob.glob('data/lbh*x1d.fits'):
    hdu = fits.open(item)
    ax1.plot(hdu[1].data['WAVELENGTH'].ravel(), hdu[1].data['FLUX'].ravel(), label=os.path.split(item)[1][:9])
    ax2.plot(hdu[1].data['WAVELENGTH'].ravel(), hdu[1].data['FLUX'].ravel(), label=os.path.split(item)[1][:9])

for axis in [ax1, ax2]:
    axis.grid(True)
    axis.set_xlabel("Wavelength")
    axis.legend(shadow=True, numpoints=1, loc='upper left')
    
ax1.set_ylim(0, 5e-14)
ax1.set_xlim(1295, 1315)

ax2.set_ylim(0, 2e-14)
ax2.set_xlim(1190, 1230)

ax1.set_ylabel('FUVA')
ax2.set_ylabel('FUVB')

ax1.set_title('Airglow Emission')
Out[4]:
<matplotlib.text.Text at 0x109db7bd0>

Airglow Variation


Airglow line strengths are driven by the solar activity influencing the Earth's atmosphere. This impact is severely lessened when the sun is below the horizon, and thus airglow contamination be diminished or eliminated by utilizing only data taken during orbital night.

To facilitate filtering out specific data, a TIMELINE extension was added to corrag data files to track various parameters throughout an exposure. The different parameters are shown below, and each one is calculated on a second-by-second basis throughout the exposure.

In [6]:
hdu = fits.open('data/lbh408vaq_corrtag_b.fits')
print hdu['timeline'].data.names
['TIME', 'LONGITUDE', 'LATITUDE', 'SUN_ALT', 'SUN_ZD', 'TARGET_ALT', 'RADIAL_VEL', 'SHIFT1', 'LY_ALPHA', 'OI_1304', 'OI_1356', 'DARKRATE']

Now let's plot the strength of two airglow lines against the Sun's altitude during the exposure. You can see that, although Lyman Alpha only diminishes during orbital night, OI fades away. Multiple datasets are over-plotted, and all show the same trend.

In [7]:
fig = plt.figure(figsize=(15, 15))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)

for item in glob.glob('data/lbh*corrtag_b.fits'):
    hdu = fits.open(item)
    ax1.plot(hdu['timeline'].data['SUN_ALT'], hdu['timeline'].data['LY_ALPHA'], 'o', label=os.path.split(item)[1][:9])

for item in glob.glob('data/lbh*corrtag_a.fits'):
    hdu = fits.open(item)
    ax2.plot(hdu['timeline'].data['SUN_ALT'], hdu['timeline'].data['OI_1304'],'o', label=os.path.split(item)[1][:9])
    
for axis in [ax1, ax2]:
    axis.grid(True)
    axis.axvspan(xmin=-45, xmax=0, ymin=0, ymax=1, label='Orbital Night', alpha=.5, color='grey')
    axis.set_xlim(-45, 90)
    axis.set_xlabel("Sun altitude above horizon (degrees)", fontsize=16)
    axis.legend(shadow=True, numpoints=1, loc='upper left', fontsize=16)

ax1.set_ylabel('Lyman Alpha (cnts/s)', fontsize=16)
ax2.set_ylabel('Oxygen I (cnts/s)', fontsize=16)
    
ax1.set_title('Airglow Lines with Altitude')
fig.savefig('airglow_strength.pdf', bbox_inches='tight')

Dealing with airglow in data


We can use this TIMELINE extension in coordination with the Timefilter module to filter out selected times of input data. Any of the parameters listed in the TIMELINE extension can be used, depending on your interest, but for our current purpose we will focus on the task of eliminating as much airglow as possible.

First we will plot some real data contaminated by airglow. The data shown below is taken by PI Andrew Fox as part of Program 12604. We will be looking at the G130M/1291 data to view the OI airglow line. In the plotted spectra, there are very prominent and variable emission features in the region of OI superimposed on the source continuum.

In [8]:
for dataset in glob.glob('data/lbry*x1d.fits'):
    hdu = fits.open(dataset)
    print dataset, hdu[0].header['OPT_ELEM'], hdu[0].header['CENWAVE'], hdu[1].header['EXPTIME']
data/lbry01i6q_x1d.fits G130M 1291 1176.192
data/lbry01icq_x1d.fits G130M 1291 1176.224

In [9]:
fig = plt.figure(figsize=(17, 8))
ax = fig.add_subplot(1, 1, 1)

for dataset in glob.glob('data/lbry*x1d.fits'):
    hdu = fits.open(dataset)
    ax.plot(hdu[1].data['wavelength'].ravel(), hdu[1].data['flux'].ravel(), label=hdu[0].header['rootname'])
    
#-- Label with arrows
ax.annotate('Geo-coronal Oxygen I', 
            xycoords='data',
            xy=(1302, 1e-13),
            xytext=(1310, 1.5e-13),
            arrowprops=dict(facecolor='black', shrink=0.05),
            fontsize=16)

ax.annotate('', 
            xycoords='data',
            xy=(1305, 1e-13),
            xytext=(1310, 1.5e-13),
            arrowprops=dict(facecolor='black', shrink=0.05),
            fontsize=16)
    
ax.set_ylim(0, 2e-13)
ax.set_xlim(1280, 1340)
ax.legend(shadow=True, numpoints=1, fontsize=16)

ax.set_ylabel('Flux')
ax.set_xlabel('Wavelength $(\AA)$')
ax.set_title('Spectrum Contaminated by Airglow')

fig.savefig('spectrum_before.pdf', bbox_inches='tight')

Timefilter


In [10]:
#-- timefilter and CalCOS need reference files to function.
#-- this will set the lref environment variable if you don't have it set.
#--
#-- This is the correct location for users internal to STScI, you may have to change it.
if not 'lref' in os.environ:
    os.environ['lref'] = '/grp/hst/cdbs/lref/'
In [11]:
for dataset in glob.glob('data/lbry*corrtag*.fits'):
    filepath, filename = os.path.split(dataset)
    print "Filtering ", filename
    timefilter.TimelineFilter(input=dataset, filter='SUN_ALT > 0')
Filtering  lbry01i6q_corrtag_a.fits
Filtering  lbry01i6q_corrtag_b.fits
Filtering  lbry01icq_corrtag_a.fits
Filtering  lbry01icq_corrtag_b.fits

The corrtags have had the unwanted data flagged as bad, but we still need to re-extract the spectrum. To do this, simply call the CalCOS pipeline on the corrtag files. The pipeline will re-run the needed steps and extract the spectrum on the filtered data.

Note:

  • CalCOS will automatically search for the FUVB segment if only FUVA data is supplied (and vice-versa)
  • When running CalCOS on corrtag data (instead of rawtags) a different output directory MUST be supplied
In [12]:
#-- This step will take a little while as we re-un CalCOS on the filtered datasets.
for dataset in glob.glob('data/lbry*corrtag_a*'):
    calcos(dataset, outdir='data/out/')
Warning:  Creating output directory 'data/out/'.
CALCOS version 2.21 (2014-02-18)
numpy version 1.8.0
astropy version 0.3
Begin 02-Jun-2014 08:31:01 EDT
Input file = data/lbry01i6q_corrtag_a.fits

TIME-TAG calibration -- 02-Jun-2014 08:31:05 EDT
Input     data/lbry01i6q_corrtag_a.fits
OutTag    data/out/lbry01i6q_corrtag_a.fits
OutFlt    data/out/lbry01i6q_flt_a.fits
OutCounts data/out/lbry01i6q_counts_a.fits
OutFlash  data/out/lbry01i6q_lampflash_a.fits
DETECTOR  FUV, segment A
EXPTYPE   EXTERNAL/SCI
OPT_ELEM  G130M, CENWAVE 1291, FPOFFSET 0
APERTURE  PSA

HVTAB   = lref$wa41551al_hv.fits
BADTCORR  OMIT (already complete)
RANDCORR  OMIT (already complete)
TEMPCORR  OMIT (already complete)
GEOCORR   OMIT (already complete)
WALKCORR  OMIT (already complete)
DEADCORR  OMIT (already complete)
PHACORR   OMIT (already complete)
DOPPCORR  OMIT (already complete)
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
BRFTAB  = lref$s7g1700el_brf.fits
FLATCORR  OMIT (already complete)
WAVECORR  OMIT (already complete)
BRSTCORR  OMIT
DQICORR   PERFORM (complete, but repeat)
BPIXTAB = lref$w4i1707ol_bpix.fits
GSAGTAB = lref$w9o1334ml_gsag.fits
STATFLAG  T
Update the TIMELINE extension.
Airglow region for OI_1304 is X: 2328 to 2894, Y: 457 to 518

Spectral Extraction -- 02-Jun-2014 08:33:10 EDT
Input     data/out/lbry01i6q_flt_a.fits
Incounts  data/out/lbry01i6q_counts_a.fits
Output    data/out/lbry01i6q_x1d_a.fits

Info:  find-target option = no
X1DCORR   PERFORM
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
HELCORR   PERFORM
BACKCORR  PERFORM
STATFLAG  T
FLUXCORR  PERFORM
FLUXTAB = lref$u8k1433ql_phot.fits
TDSCORR   PERFORM
TDSTAB  = lref$w7h1935dl_tds.fits
FUVA spectrum was found at y = 490.32 vs. nominal y = 486.38
    error estimate for y location = 4.22, FWHM = 13.86
Spectrum will be extracted at y = 486.38

TIME-TAG calibration -- 02-Jun-2014 08:33:20 EDT
Input     data/lbry01i6q_corrtag_b.fits
OutTag    data/out/lbry01i6q_corrtag_b.fits
OutFlt    data/out/lbry01i6q_flt_b.fits
OutCounts data/out/lbry01i6q_counts_b.fits
OutFlash  data/out/lbry01i6q_lampflash_b.fits
DETECTOR  FUV, segment B
EXPTYPE   EXTERNAL/SCI
OPT_ELEM  G130M, CENWAVE 1291, FPOFFSET 0
APERTURE  PSA

HVTAB   = lref$wa41551al_hv.fits
BADTCORR  OMIT (already complete)
RANDCORR  OMIT (already complete)
TEMPCORR  OMIT (already complete)
GEOCORR   OMIT (already complete)
WALKCORR  OMIT (already complete)
DEADCORR  OMIT (already complete)
PHACORR   OMIT (already complete)
DOPPCORR  OMIT (already complete)
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
BRFTAB  = lref$s7g1700el_brf.fits
FLATCORR  OMIT (already complete)
WAVECORR  OMIT (already complete)
BRSTCORR  OMIT
DQICORR   PERFORM (complete, but repeat)
BPIXTAB = lref$w4i1707ol_bpix.fits
GSAGTAB = lref$w9o1334ml_gsag.fits
STATFLAG  T
Update the TIMELINE extension.
Airglow region for LY_ALPHA is X: 9029 to 9214, Y: 517 to 578

Spectral Extraction -- 02-Jun-2014 08:35:54 EDT
Input     data/out/lbry01i6q_flt_b.fits
Incounts  data/out/lbry01i6q_counts_b.fits
Output    data/out/lbry01i6q_x1d_b.fits

Info:  find-target option = no
X1DCORR   PERFORM
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
HELCORR   PERFORM
BACKCORR  PERFORM
STATFLAG  T
FLUXCORR  PERFORM
FLUXTAB = lref$u8k1433ql_phot.fits
TDSCORR   PERFORM
TDSTAB  = lref$w7h1935dl_tds.fits
FUVB spectrum was found at y = 548.81 vs. nominal y = 547.27
    error estimate for y location = 6.08, FWHM = 15.17
Spectrum will be extracted at y = 547.27
SPWCSTAB= lref$uai1737rl_spwcs.fits
End   02-Jun-2014 08:36:54 EDT
CALCOS version 2.21 (2014-02-18)
numpy version 1.8.0
astropy version 0.3
Begin 02-Jun-2014 08:36:54 EDT
Input file = data/lbry01icq_corrtag_a.fits

TIME-TAG calibration -- 02-Jun-2014 08:36:56 EDT
Input     data/lbry01icq_corrtag_a.fits
OutTag    data/out/lbry01icq_corrtag_a.fits
OutFlt    data/out/lbry01icq_flt_a.fits
OutCounts data/out/lbry01icq_counts_a.fits
OutFlash  data/out/lbry01icq_lampflash_a.fits
DETECTOR  FUV, segment A
EXPTYPE   EXTERNAL/SCI
OPT_ELEM  G130M, CENWAVE 1291, FPOFFSET 1
APERTURE  PSA

HVTAB   = lref$wa41551al_hv.fits
BADTCORR  OMIT (already complete)
RANDCORR  OMIT (already complete)
TEMPCORR  OMIT (already complete)
GEOCORR   OMIT (already complete)
WALKCORR  OMIT (already complete)
DEADCORR  OMIT (already complete)
Warning:  No GTI table found in raw file.
Warning:  exposure time in header was 0.000
    exptime has been corrected to 1176.224
PHACORR   OMIT (already complete)
DOPPCORR  OMIT (already complete)
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
BRFTAB  = lref$s7g1700el_brf.fits
FLATCORR  OMIT (already complete)
WAVECORR  OMIT (already complete)
BRSTCORR  OMIT
Warning:  No GTI table found in raw file.
DQICORR   PERFORM (complete, but repeat)
BPIXTAB = lref$w4i1707ol_bpix.fits
GSAGTAB = lref$w9o1334ml_gsag.fits
STATFLAG  T
Update the TIMELINE extension.
Airglow region for OI_1304 is X: 2328 to 2894, Y: 457 to 518

Spectral Extraction -- 02-Jun-2014 08:39:58 EDT
Input     data/out/lbry01icq_flt_a.fits
Incounts  data/out/lbry01icq_counts_a.fits
Output    data/out/lbry01icq_x1d_a.fits

Info:  find-target option = no
X1DCORR   PERFORM
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
HELCORR   PERFORM
BACKCORR  PERFORM
STATFLAG  T
FLUXCORR  PERFORM
FLUXTAB = lref$u8k1433ql_phot.fits
TDSCORR   PERFORM
TDSTAB  = lref$w7h1935dl_tds.fits
FUVA spectrum was found at y = 531.81 vs. nominal y = 486.38
    error estimate for y location = 999.00, FWHM = -1.00
Spectrum will be extracted at y = 486.38

TIME-TAG calibration -- 02-Jun-2014 08:40:30 EDT
Input     data/lbry01icq_corrtag_b.fits
OutTag    data/out/lbry01icq_corrtag_b.fits
OutFlt    data/out/lbry01icq_flt_b.fits
OutCounts data/out/lbry01icq_counts_b.fits
OutFlash  data/out/lbry01icq_lampflash_b.fits
DETECTOR  FUV, segment B
EXPTYPE   EXTERNAL/SCI
OPT_ELEM  G130M, CENWAVE 1291, FPOFFSET 1
APERTURE  PSA

HVTAB   = lref$wa41551al_hv.fits
BADTCORR  OMIT (already complete)
RANDCORR  OMIT (already complete)
TEMPCORR  OMIT (already complete)
GEOCORR   OMIT (already complete)
WALKCORR  OMIT (already complete)
DEADCORR  OMIT (already complete)
Warning:  No GTI table found in raw file.
Warning:  exposure time in header was 0.000
    exptime has been corrected to 1176.224
PHACORR   OMIT (already complete)
DOPPCORR  OMIT (already complete)
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
BRFTAB  = lref$s7g1700el_brf.fits
FLATCORR  OMIT (already complete)
WAVECORR  OMIT (already complete)
BRSTCORR  OMIT
Warning:  No GTI table found in raw file.
DQICORR   PERFORM (complete, but repeat)
BPIXTAB = lref$w4i1707ol_bpix.fits
GSAGTAB = lref$w9o1334ml_gsag.fits
STATFLAG  T
Update the TIMELINE extension.
Airglow region for LY_ALPHA is X: 9029 to 9214, Y: 517 to 578

Spectral Extraction -- 02-Jun-2014 08:42:33 EDT
Input     data/out/lbry01icq_flt_b.fits
Incounts  data/out/lbry01icq_counts_b.fits
Output    data/out/lbry01icq_x1d_b.fits

Info:  find-target option = no
X1DCORR   PERFORM
XTRACTAB= lref$w5g1439ql_1dx.fits
DISPTAB = lref$v3g18194l_disp.fits
HELCORR   PERFORM
BACKCORR  PERFORM
STATFLAG  T
FLUXCORR  PERFORM
FLUXTAB = lref$u8k1433ql_phot.fits
TDSCORR   PERFORM
TDSTAB  = lref$w7h1935dl_tds.fits
FUVB spectrum was found at y = 592.33 vs. nominal y = 547.27
    error estimate for y location = 999.00, FWHM = -1.00
Spectrum will be extracted at y = 547.27
SPWCSTAB= lref$uai1737rl_spwcs.fits
End   02-Jun-2014 08:42:53 EDT

In [13]:
fig = plt.figure(figsize=(17, 8))
ax = fig.add_subplot(1, 1, 1)

for dataset in glob.glob('data/out/lbry*x1d.fits'):
    hdu = fits.open(dataset)
    ax.plot(hdu[1].data['wavelength'].ravel(), hdu[1].data['flux'].ravel(), label=hdu[0].header['rootname'])
    
ax.set_ylim(0, 2e-13)
ax.set_xlim(1280, 1340)
ax.legend(shadow=True, numpoints=1, fontsize=16)

ax.set_title('Airglow Filtered Spectrum')
ax.set_ylabel('Flux')
ax.set_xlabel('Wavelength $(\AA)$')

fig.savefig('spectrum_after.pdf', bbox_inches='tight')

You may have noticed that only a single spectrum was visible here, though two were seen before filtering. This is not an error, but rather that only 1 of the two datasets actually contains data taken during orbital night. For a dataset containing only data with SUN_ALT > 0, no events would still be remaining after filtering, and the spectra are empty. Even for the one that remains, the signal-to-noise is considerably reduced. That is because only a small portion of the data was taken during night.

Again, the TIMELINE extension can help visualize this: below is plotted the SUN_ALT of each observation vs time. Only lbry01i6q contains data taken during orbital night.

In [14]:
fig = plt.figure(figsize=(13, 6))
ax = fig.add_subplot(1, 1, 1)

for dataset in glob.glob('data/out/lbry*corrtag_a.fits'):
    hdu = fits.open(dataset)
    file_path, file_name = os.path.split(dataset)
    
    #-- Plot the sun altitude against time
    ax.plot(hdu['timeline'].data['time'], hdu['timeline'].data['sun_alt'], lw=3)
    
    #-- Label dataset name at the end of track
    ax.annotate(file_name[:9], 
                xycoords='data',
                xy=(hdu['timeline'].data['time'][-1], hdu['timeline'].data['sun_alt'][-1]),
                xytext=(hdu['timeline'].data['time'][-1], hdu['timeline'].data['sun_alt'][-1]),
                fontsize=16)
    
ax.axhspan(ymin=-20, ymax=0, xmin=0, xmax=1, color='grey', alpha=.5, label='Orbital Night')
ax.set_ylim(-20, 120)

ax.set_title('Sun Altitude During Exposure')
ax.set_xlabel('Time into observation (seconds)')
ax.set_ylabel('Sun Altitude (degrees)')
ax.legend(loc='upper left', shadow=True, numpoints=1)
Out[14]:
<matplotlib.legend.Legend at 0x1065d9f90>