Calculating WFC3 Zeropoints with STSynphot#


Learning Goals#

By the end of this tutorial, you will:

  • Calculate zeropoints and other photometric properties using stsynphot.

  • Create, plot, and save ‘total system throughput’ tables.

Table of Contents#

Introduction
1. Imports
2. Download throughput tables and define variables
3. Set up the ‘obsmode’ string
4. Basic usage for a single ‘obsmode’
5. Compute zeropoints and other photometric properties
6. Iterate over multiple ‘obsmodes’
7. Create and plot ‘total system throughput’ tables
8. Conclusions
Additional Resources
About the Notebook
Citations

Introduction#

This notebook shows how to calculate photometric zeropoints using the Python package stsynphot for any WFC3 detector, filter, date, or aperture. This tutorial is especially useful for calculating Vegamag zeropoints, which require an input spectrum. The notebook is also useful for computing time-dependent WFC3/UVIS zeropoints for any observation date, as the values listed in WFC3 ISR 2021-04 are defined for the reference epoch. As of mid-2021, the WFC3/IR zeropoints are not time-dependent.

More documentation on stsynphot is available here. Using stsynphot requires downloading the throughput curves for the HST instruments and optical path. One method of doing this is shown in Section 2. More information on the throughput tables can be found here.

1. Imports#

This notebook assumes you have created the virtual environment in WFC3 notebooks’ installation instructions.

We import:

  • os for setting environment variables

  • tarfile for extracting a .tar archive

  • numpy for handling array functions

  • matplotlib.pyplot for plotting data

  • astropy for astronomy related functions

  • synphot and stsynphot for evaluating synthetic photometry

We will need to set the PYSYN_CDBS environment variable before importing stsynphot. We will also create a custom Vega spectrum, as the stsynphot will supercede the usual synphot functionality regarding the Vega spectrum and would otherwise require a downloaded copy of the spectrum to be provided.

import os
import tarfile

import numpy as np
import matplotlib.pyplot as plt

from astropy.table import Table

from synphot import Observation

2. Download throughput tables and define variables#

This section obtains the WFC3 throughput component tables for use with stsynphot. This step only needs to be done once. If these reference files have already been downloaded, this section can be skipped.

cmd_input = 'curl -O https://archive.stsci.edu/hlsps/reference-atlases/hlsp_reference-atlases_hst_multi_everything_multi_v11_sed.tar'
os.system(cmd_input)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  4  796M    4 38.6M    0     0  99.6M      0  0:00:07 --:--:--  0:00:07 99.6M
 39  796M   39  314M    0     0   226M      0  0:00:03  0:00:01  0:00:02  226M
 62  796M   62  499M    0     0   208M      0  0:00:03  0:00:02  0:00:01  208M
 74  796M   74  593M    0     0   175M      0  0:00:04  0:00:03  0:00:01  175M
 85  796M   85  683M    0     0   155M      0  0:00:05  0:00:04  0:00:01  155M
 98  796M   98  785M    0     0   145M      0  0:00:05  0:00:05 --:--:--  149M
100  796M  100  796M    0     0   145M      0  0:00:05  0:00:05 --:--:--  117M
0

Once the downloaded is complete, extract the file and set the environment variable PYSYN_CDBS to the path of the trds subdirectory. The next cell will do this for you, as long as the .tar file downloaded above has not been moved.

tar_archive = 'hlsp_reference-atlases_hst_multi_everything_multi_v11_sed.tar'
extract_to = 'hlsp_reference-atlases_hst_multi_everything_multi_v11_sed'
with tarfile.open(tar_archive, 'r') as tar:
    tar.extractall(path=extract_to)

os.environ['PYSYN_CDBS'] = 'hlsp_reference-atlases_hst_multi_everything_multi_v11_sed/grp/redcat/trds/'

Now, after having set up PYSYN_CDBS, we import stsynphot. A warning regarding the Vega spectrum is expected here.

import stsynphot as stsyn
WARNING: Failed to load Vega spectrum from hlsp_reference-atlases_hst_multi_everything_multi_v11_sed/grp/redcat/trds//calspec/alpha_lyr_stis_010.fits; Functionality involving Vega will be cripped: FileNotFoundError(2, 'No such file or directory') [stsynphot.spectrum]

Rather than downloading the entire calspec database (synphot6.tar.gz), we can point directly to the latest Vega spectrum which is required for computing VEGAMAG.

vega_url = 'https://ssb.stsci.edu/trds/calspec/alpha_lyr_stis_010.fits'
stsyn.Vega = stsyn.spectrum.SourceSpectrum.from_file(vega_url)

3. Set up the ‘obsmode’ string#

Parameters to set in the obsmode string include:

  1. detector,

  2. filter,

  3. observation date (WFC3/UVIS only), and

  4. aperture size (in arcsec).

Note that a 6.0” aperture is considered to be “infinite”, thus containing all of the flux. The zeropoints posted on the WFC3 website are calculated for an infinite aperture, so when computing photometry for smaller radii, aperture corrections must be applied.

The inputs below can be changed to any desired obsmode, with examples of alternate parameters shown as commented lines.

First, here are some detector examples with WFC3/UVIS1 as the default, and other options including both WFC3/UVIS chips or the WFC3/IR detector.

Note: if the IR detector is chosen, the filtnames below must be updated.

detectors = ['uvis1']
# detectors = ['uvis1', 'uvis2']
# detectors = ['ir']

Next, here are some filter examples with all WFC3/UVIS filters as the default, and other options including just F606W and the WFC3/IR filters.

Note: if WFC3/IR filters is chosen, the detectors above must be set to [‘ir’].

filtnames = ['f200lp', 'f218w', 'f225w', 'f275w', 'f280n', 'f300x', 'f336w', 'f343n', 'f350lp', 
             'f373n', 'f390m', 'f390w', 'f395n', 'f410m', 'f438w', 'f467m', 'f469n', 'f475w', 
             'f475x', 'f487n', 'f502n', 'f547m', 'f555w', 'f600lp', 'f606w', 'f621m', 'f625w', 
             'f631n', 'f645n', 'f656n', 'f657n', 'f658n', 'f665n', 'f673n', 'f680n', 'f689m', 
             'f763m', 'f775w', 'f814w', 'f845m', 'f850lp', 'f953n']
# filtnames = ['f606w']   
# filtnames = ['f098m', 'f105w', 'f110w', 'f125w', 'f126n', 'f127m', 'f128n', 'f130n', 
#              'f132n', 'f139m', 'f140w', 'f153m', 'f160w', 'f164n', 'f167n']

Now, here are some date examples with the WFC3/UVIS reference epoch (55008 in MJD; 2009-06-26) as the default, and the other option being the time right now.

mjd = '55008'
# mjd = str(Time.now().mjd)

Finally, here are some aperture radius examples with 6.0” (151 pixels; “infinity”) as the default, and the other options including 0.396” (10 pixels for WFC3/UVIS) and 0.385” (3 pixels for WFC3/IR).

aper = '6.0'
# aper = '0.396'
# aper = '0.385'

4. Basic usage for a single ‘obsmode’#

The calculation of the zeropoints starts with creating a specific bandpass object. Bandpasses generally consist of at least an instrument name, detector name, and filter name, though other parameters (such as the MJD and aperture radius shown above) are optional.

The cell below defines obsmode and creates a bandpass object.

obsmode = 'wfc3,uvis1,f200lp'
bp = stsyn.band(obsmode)

Optional parameters are supplied on the end of the basic bandpass:

obsmode = 'wfc3,uvis1,f200lp,mjd#55008,aper#6.0'
bp = stsyn.band(obsmode)

In addition, we can use the parameters defined in Section 3.

obsmode = f'wfc3, {detectors[0]}, {filtnames[0]}, mjd#{mjd}, aper#{aper}'
bp = stsyn.band(obsmode)

5. Compute zeropoints and other photometric properties#

With the bandpass objects, we can now calculate zeropoints, pivot wavelengths, and photometric bandwidths. To calculate Vegamag zeropoints, we use the Vega spectrum to calculate the flux in a given bandpass.

def calculate_values(detector, filt, mjd, aper):
    # parameters can be removed from obsmode as needed
    obsmode = f'wfc3, {detector}, {filt}, mjd#{mjd}, aper#{aper}'
    bp = stsyn.band(obsmode)  
    
    # STMag
    photflam = bp.unit_response(stsyn.conf.area)  # inverse sensitivity in flam
    stmag = -21.1 - 2.5 * np.log10(photflam.value)
    
    # Pivot Wavelength and bandwidth
    photplam = bp.pivot() # pivot wavelength in angstroms
    bandwidth = bp.photbw() # bandwidth in angstroms
    
    # ABMag
    abmag = stmag - 5 * np.log10(photplam.value) + 18.6921
    
    # Vegamag
    obs = Observation(stsyn.Vega, bp, binset=bp.binset)  # synthetic observation of vega in bandpass using vega spectrum
    vegamag = -1 * obs.effstim(flux_unit='obmag', area=stsyn.conf.area)
    
    return obsmode, photplam.value, bandwidth.value, photflam.value, stmag, abmag, vegamag.value
obsmode, photplam, bandwidth, photflam, stmag, abmag, vegamag = calculate_values(detectors[0], filtnames[0], mjd, aper)

# print values
print('Obsmode                              PivotWave Photflam   STMAG   ABMAG   VEGAMAG')
print(f'{obsmode}, {photplam:.1f}, {photflam:.4e}, {stmag:.3f}, {abmag:.3f}, {vegamag:.3f}')
Obsmode                              PivotWave Photflam   STMAG   ABMAG   VEGAMAG
wfc3, uvis1, f200lp, mjd#55008, aper#6.0, 4971.9, 4.9157e-20, 27.171, 27.381, 26.931

6. Iterate over multiple ‘obsmodes’#

To calculate zeropoints for multiple detectors and/or filters, we can use the function defined above and loop through detectors and filters defined in Section 3.

oms, pivots, bws, pfs, st, ab, vm = [], [], [], [], [], [], []

print('Obsmode                              PivotWave Photflam   STMAG   ABMAG   VEGAMAG')
for detector in detectors:
    for filt in filtnames:
        res = calculate_values(detector, filt, mjd, aper)
        obsmode, photplam, bandwidth, photflam, stmag, abmag, vegamag = res # solely for readability
        
        # print values
        print(f'{obsmode}, {photplam:.1f}, {photflam:.4e}, {stmag:.3f}, {abmag:.3f}, {vegamag:.3f}')
        
        oms.append(obsmode)
        pivots.append(photplam)
        bws.append(bandwidth)
        pfs.append(photflam)
        st.append(stmag)
        ab.append(abmag)
        vm.append(vegamag)
Obsmode                              PivotWave Photflam   STMAG   ABMAG   VEGAMAG
wfc3, uvis1, f200lp, mjd#55008, aper#6.0, 4971.9, 4.9157e-20, 27.171, 27.381, 26.931
wfc3, uvis1, f218w, mjd#55008, aper#6.0, 2228.0, 1.4594e-17, 20.990, 22.942, 21.278
wfc3, uvis1, f225w, mjd#55008, aper#6.0, 2372.1, 4.5688e-18, 22.251, 24.067, 22.430
wfc3, uvis1, f275w, mjd#55008, aper#6.0, 2709.7, 3.2206e-18, 22.630, 24.158, 22.677
wfc3, uvis1, f280n, mjd#55008, aper#6.0, 2832.9, 5.7472e-17, 19.501, 20.932, 19.516
wfc3, uvis1, f300x, mjd#55008, aper#6.0, 2820.5, 1.4093e-18, 23.527, 24.968, 23.565
wfc3, uvis1, f336w, mjd#55008, aper#6.0, 3354.5, 1.2848e-18, 23.628, 24.692, 23.527
wfc3, uvis1, f343n, mjd#55008, aper#6.0, 3435.2, 2.5672e-18, 22.876, 23.889, 22.754
wfc3, uvis1, f350lp, mjd#55008, aper#6.0, 5873.9, 5.1638e-20, 27.118, 26.965, 26.810
wfc3, uvis1, f373n, mjd#55008, aper#6.0, 3730.2, 1.3488e-17, 21.075, 21.909, 21.036
wfc3, uvis1, f390m, mjd#55008, aper#6.0, 3897.2, 2.5524e-18, 22.883, 23.621, 23.545
wfc3, uvis1, f390w, mjd#55008, aper#6.0, 3923.7, 5.0142e-19, 24.649, 25.373, 25.174
wfc3, uvis1, f395n, mjd#55008, aper#6.0, 3955.2, 5.9589e-18, 21.962, 22.668, 22.712
wfc3, uvis1, f410m, mjd#55008, aper#6.0, 4109.0, 2.3481e-18, 22.973, 23.597, 23.771
wfc3, uvis1, f438w, mjd#55008, aper#6.0, 4326.2, 6.7475e-19, 24.327, 24.839, 25.003
wfc3, uvis1, f467m, mjd#55008, aper#6.0, 4682.6, 1.6498e-18, 23.356, 23.696, 23.859
wfc3, uvis1, f469n, mjd#55008, aper#6.0, 4688.1, 9.3089e-18, 21.478, 21.815, 21.981
wfc3, uvis1, f475w, mjd#55008, aper#6.0, 4773.1, 2.4962e-19, 25.407, 25.705, 25.810
wfc3, uvis1, f475x, mjd#55008, aper#6.0, 4940.7, 1.5343e-19, 25.935, 26.158, 26.216
wfc3, uvis1, f487n, mjd#55008, aper#6.0, 4871.4, 5.8860e-18, 21.975, 22.229, 22.050
wfc3, uvis1, f502n, mjd#55008, aper#6.0, 5009.6, 5.0824e-18, 22.135, 22.328, 22.421
wfc3, uvis1, f547m, mjd#55008, aper#6.0, 5447.5, 4.5847e-19, 24.747, 24.758, 24.761
wfc3, uvis1, f555w, mjd#55008, aper#6.0, 5308.4, 1.8272e-19, 25.746, 25.813, 25.841
wfc3, uvis1, f600lp, mjd#55008, aper#6.0, 7468.1, 8.6226e-20, 26.561, 25.887, 25.554
wfc3, uvis1, f606w, mjd#55008, aper#6.0, 5889.2, 1.1506e-19, 26.248, 26.090, 26.006
wfc3, uvis1, f621m, mjd#55008, aper#6.0, 6218.9, 4.0126e-19, 24.891, 24.615, 24.465
wfc3, uvis1, f625w, mjd#55008, aper#6.0, 6242.6, 1.7139e-19, 25.815, 25.530, 25.379
wfc3, uvis1, f631n, mjd#55008, aper#6.0, 6304.3, 4.8250e-18, 22.191, 21.885, 21.723
wfc3, uvis1, f645n, mjd#55008, aper#6.0, 6453.6, 3.3059e-18, 22.602, 22.245, 22.049
wfc3, uvis1, f656n, mjd#55008, aper#6.0, 6561.4, 1.6713e-17, 20.842, 20.450, 19.868
wfc3, uvis1, f657n, mjd#55008, aper#6.0, 6566.6, 2.1811e-18, 23.053, 22.659, 22.333
wfc3, uvis1, f658n, mjd#55008, aper#6.0, 6584.0, 9.7496e-18, 21.428, 21.027, 20.672
wfc3, uvis1, f665n, mjd#55008, aper#6.0, 6655.9, 1.9774e-18, 23.160, 22.736, 22.492
wfc3, uvis1, f673n, mjd#55008, aper#6.0, 6765.9, 2.1926e-18, 23.048, 22.588, 22.343
wfc3, uvis1, f680n, mjd#55008, aper#6.0, 6877.6, 6.8241e-19, 24.315, 23.820, 23.556
wfc3, uvis1, f689m, mjd#55008, aper#6.0, 6876.8, 3.7208e-19, 24.973, 24.479, 24.196
wfc3, uvis1, f763m, mjd#55008, aper#6.0, 7614.4, 3.8291e-19, 24.942, 24.226, 23.837
wfc3, uvis1, f775w, mjd#55008, aper#6.0, 7651.4, 2.0922e-19, 25.599, 24.872, 24.480
wfc3, uvis1, f814w, mjd#55008, aper#6.0, 8039.1, 1.4994e-19, 25.960, 25.126, 24.698
wfc3, uvis1, f845m, mjd#55008, aper#6.0, 8439.1, 4.5207e-19, 24.762, 23.823, 23.316
wfc3, uvis1, f850lp, mjd#55008, aper#6.0, 9176.1, 3.7052e-19, 24.978, 23.857, 23.326
wfc3, uvis1, f953n, mjd#55008, aper#6.0, 9530.6, 8.0946e-18, 21.630, 20.426, 19.803

Values can also be written into an astropy table.

tbl = Table([oms, pivots, bws, pfs, st, ab, vm], 
            names=['Obsmode', 'Pivot Wave', 'Bandwidth', 'Photflam', 'STMag', 'ABMag', 'VegaMag'])

We’ll also round columns to a smaller number of decimals.

for col in tbl.itercols():
    if col.name == 'Photflam':
        col.info.format = '.4e'
    elif col.info.dtype.kind == 'f':        
        col.info.format = '.3f'

Let’s view our astropy table:

tbl
Table length=42
ObsmodePivot WaveBandwidthPhotflamSTMagABMagVegaMag
str40float64float64float64float64float64float64
wfc3, uvis1, f200lp, mjd#55008, aper#6.04971.8601742.1984.9157e-2027.17127.38126.931
wfc3, uvis1, f218w, mjd#55008, aper#6.02228.039128.9411.4594e-1720.99022.94221.278
wfc3, uvis1, f225w, mjd#55008, aper#6.02372.053177.4304.5688e-1822.25124.06722.430
wfc3, uvis1, f275w, mjd#55008, aper#6.02709.689164.4353.2206e-1822.63024.15822.677
wfc3, uvis1, f280n, mjd#55008, aper#6.02832.862200.6895.7472e-1719.50120.93219.516
wfc3, uvis1, f300x, mjd#55008, aper#6.02820.469316.5611.4093e-1823.52724.96823.565
wfc3, uvis1, f336w, mjd#55008, aper#6.03354.492158.4221.2848e-1823.62824.69223.527
wfc3, uvis1, f343n, mjd#55008, aper#6.03435.15186.7132.5672e-1822.87623.88922.754
wfc3, uvis1, f350lp, mjd#55008, aper#6.05873.8701490.0605.1638e-2027.11826.96526.810
.....................
wfc3, uvis1, f665n, mjd#55008, aper#6.06655.87642.1911.9774e-1823.16022.73622.492
wfc3, uvis1, f673n, mjd#55008, aper#6.06765.93941.9432.1926e-1823.04822.58822.343
wfc3, uvis1, f680n, mjd#55008, aper#6.06877.596112.0136.8241e-1924.31523.82023.556
wfc3, uvis1, f689m, mjd#55008, aper#6.06876.755207.6133.7208e-1924.97324.47924.196
wfc3, uvis1, f763m, mjd#55008, aper#6.07614.371229.4253.8291e-1924.94224.22623.837
wfc3, uvis1, f775w, mjd#55008, aper#6.07651.363419.7192.0922e-1925.59924.87224.480
wfc3, uvis1, f814w, mjd#55008, aper#6.08039.056666.7601.4994e-1925.96025.12624.698
wfc3, uvis1, f845m, mjd#55008, aper#6.08439.057260.3044.5207e-1924.76223.82323.316
wfc3, uvis1, f850lp, mjd#55008, aper#6.09176.126470.5293.7052e-1924.97823.85723.326
wfc3, uvis1, f953n, mjd#55008, aper#6.09530.57971.1908.0946e-1821.63020.42619.803

We can finally save the table as a .txt file.

if not os.path.exists('./uvis_zp_tbl.txt'):
    tbl.write('uvis_zp_tbl.txt', format='ascii.commented_header')

7. Create and plot ‘total system throughput’ tables#

The function below returns a tuple containing two objects, the first being an array of wavelengths, and the second being the throughput at each of those wavelengths.

def calculate_bands(bp, save=False, overwrite=True):
    # Pass in bandpass object as bp
    waves = bp.waveset
    throughput = bp(waves)
    
    if save:
        tmp = Table([waves, throughput], names=['WAVELENGTH', 'THROUGHPUT'])
        tmp.write(', '.join(bp.obsmode.modes)+'.txt', format='ascii.commented_header', overwrite=overwrite)
        
    return (waves, throughput)

We’ll calculate the throughput table for WFC3/UVIS1 in F200LP.

obsmode = 'wfc3,uvis1,f200lp'
bp = stsyn.band(obsmode)
wl, tp = calculate_bands(bp)

Now, let’s plot our results.

fig = plt.figure(figsize=(10, 5))
plt.plot(wl, tp)
plt.xlim(1500, 11000) 
plt.xlabel('Wavelength [Angstroms]')
plt.ylabel('Throughput')
plt.title('WFC3,UVIS1,F200LP')
Text(0.5, 1.0, 'WFC3,UVIS1,F200LP')
../../../_images/81ce367ff87e9c5d52e079dadd11236c1ba636c16679c796b5bb5bb7c9f32e60.png

To save the curve in an ascii table, simply pass the argument save=True:

calculate_bands(bp, save=True)
(<Quantity [  500.,  1000.,  1010., ..., 20002., 30000., 30010.] Angstrom>,
 <Quantity [0., 0., 0., ..., 0., 0., 0.]>)

To save curves for all obsmodes defined in Section 3 in the input list, we can loop through detectors and filters.

for det in detectors:
    for filt in filtnames:
        obsmode = f'wfc3, {det}, {filt}'
        bp = stsyn.band(obsmode)
        calculate_bands(bp, save=True)

In addition, we’ll create a directory called obsmodes_curves and move all the saved files to that directory.

! mkdir obsmodes_curves
! mv wfc3*txt obsmodes_curves
! ls obsmodes_curves
'wfc3, uvis1, f200lp.txt'  'wfc3, uvis1, f438w.txt'   'wfc3, uvis1, f645n.txt'
'wfc3, uvis1, f218w.txt'   'wfc3, uvis1, f467m.txt'   'wfc3, uvis1, f656n.txt'
'wfc3, uvis1, f225w.txt'   'wfc3, uvis1, f469n.txt'   'wfc3, uvis1, f657n.txt'
'wfc3, uvis1, f275w.txt'   'wfc3, uvis1, f475w.txt'   'wfc3, uvis1, f658n.txt'
'wfc3, uvis1, f280n.txt'   'wfc3, uvis1, f475x.txt'   'wfc3, uvis1, f665n.txt'
'wfc3, uvis1, f300x.txt'   'wfc3, uvis1, f487n.txt'   'wfc3, uvis1, f673n.txt'
'wfc3, uvis1, f336w.txt'   'wfc3, uvis1, f502n.txt'   'wfc3, uvis1, f680n.txt'
'wfc3, uvis1, f343n.txt'   'wfc3, uvis1, f547m.txt'   'wfc3, uvis1, f689m.txt'
'wfc3, uvis1, f350lp.txt'  'wfc3, uvis1, f555w.txt'   'wfc3, uvis1, f763m.txt'
'wfc3, uvis1, f373n.txt'   'wfc3, uvis1, f600lp.txt'  'wfc3, uvis1, f775w.txt'
'wfc3, uvis1, f390m.txt'   'wfc3, uvis1, f606w.txt'   'wfc3, uvis1, f814w.txt'
'wfc3, uvis1, f390w.txt'   'wfc3, uvis1, f621m.txt'   'wfc3, uvis1, f845m.txt'
'wfc3, uvis1, f395n.txt'   'wfc3, uvis1, f625w.txt'   'wfc3, uvis1, f850lp.txt'
'wfc3, uvis1, f410m.txt'   'wfc3, uvis1, f631n.txt'   'wfc3, uvis1, f953n.txt'

8. Conclusions#

Thank you for walking through this notebook. Now using WFC3 data, you should be more familiar with:

  • Calculating zeropoints and other photometric properties using stsynphot.

  • Creating, plotting, and saving ‘total system throughput’ tables.

Congratulations, you have completed the notebook!#

Additional Resources#

Below are some additional resources that may be helpful. Please send any questions through the HST Helpdesk.

About this Notebook#

Authors: Varun Bajaj, Jennifer Mack; WFC3 Instrument Team

Updated on: 2024-03-18

Citations#

If you use numpy, astropy, synphot, or stsynphot for published research, please cite the authors. Follow these links for more information about citing the libraries below:


Top of Page Space Telescope Logo