Calculating WFC3 zeropoints with STSynphot

This notebook shows how to calculate photometric zeropoints using the python package stsynphot for any WFC3 detector, filter, date, or aperture. This 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).

To install stsynphot, activate your conda environment in a bash shell and enter the command pip install stsynphot. 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 below. More information can be found here.

1. Downloading throughput tables and defining variables

This section obtains the WFC3 throughput component tables for use with synphot

In [1]:
import os

#cmd_input = 'curl -O ftp://archive.stsci.edu/pub/hst/pysynphot/synphot1.tar.gz'
#os.system(cmd_input)

Once the files are downloaded, unpack the files and set the environment variable PYSYN_CDBS to the path of the unpacked files.

In [2]:
# os.environ['PYSYN_CDBS'] = '/YOUR/PATH/HERE/'
os.environ['PYSYN_CDBS'] = '/grp/hst/cdbs/' # for STScI staff
In [3]:
import numpy as np
import stsynphot as stsyn

from astropy.table import Table
from astropy.time import Time
from synphot import Observation

#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)

2. Setting up the 'obsmode' string

Parameters to set in the obsmode string include: 1.) detector, 2.) filter, 3.) observation date (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.

In [4]:
#Detector:
detectors  = ['uvis1']
#detectors = ['uvis1', 'uvis2']  # both UVIS chips
#detectors = ['ir']              # if using IR, must update the filtnames below
In [5]:
#Filters:
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']   

# For IR filters, must set detectors = ['ir'] above
#filtnames = ['f098m','f105w','f110w','f125w','f126n','f127m','f128n','f130n','f132n','f139m','f140w','f153m','f160w','f164n','f167n']
In [6]:
#Date
mjd = '55008'                # WFC3/UVIS reference epoch (26Jun2009)
# mjd = str(Time.now().mjd)  # Time right now
In [7]:
#Aperture Radius
aper = '6.0'                 # 151 pixels (infinity) [default behavior]
#aper = '0.396'              # 10 pixels for UVIS
#aper = '0.385'              #  3 pixels for IR

3. 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. For example:

In [8]:
obsmode = 'wfc3,uvis1,f200lp'
bp = stsyn.band(obsmode) # creates bandpass object

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

In [9]:
obsmode = 'wfc3,uvis1,f200lp,mjd#55008,aper#6.0'
# or to use parameters above:
obsmode = 'wfc3,{},{},mjd#{},aper#{}'.format(detectors[0],filtnames[0],mjd,aper)
bp = stsyn.band(obsmode)

4. Computing 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.

In [10]:
def calculate_values(detector, filt, mjd, aper):
    # parameters can be removed from obsmode as needed
    obsmode = 'wfc3,{},{},mjd#{},aper#{}'.format(detector, filt, mjd, 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 = -obs.effstim(flux_unit='obmag', area=stsyn.conf.area)
    
    return obsmode, photplam.value, bandwidth.value, photflam.value, stmag, abmag, vegamag.value
In [11]:
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

5. Iterating over multiple 'obsmodes'

To calculate zeropoints for multiple detectors and/or filters:

In [12]:
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:

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

# Just for rounding columns to 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'

# Show table
tbl
Out[13]:
Table length=42
ObsmodePivot WaveBandwidthPhotflamSTMagABMagVegaMag
str36float64float64float64float64float64float64
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,f373n,mjd#55008,aper#6.03730.17018.3431.3488e-1721.07521.90921.036
.....................
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
In [14]:
# Write to a file
tbl.write('uvis_zp_tbl.txt', format='ascii.commented_header')

6. Creating and plotting 'total system throughput' tables

In [15]:
def calculate_bands(bp, save=False):
    # 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')
        
    return (waves, throughput)

The above function returns a tuple containing two objects, the first being an array of wavelengths, and the second being the throughput at each of those wavelengths. The result can be plotted via:

In [16]:
wl, tp = calculate_bands(bp)

import matplotlib.pyplot as plt

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')
Out[16]:
Text(0.5, 1.0, 'WFC3,UVIS1,F200LP')

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

In [17]:
calculate_bands(bp, save=True)
Out[17]:
(<Quantity [  500.,  1000.,  1010., ..., 20002., 30000., 30010.] Angstrom>,
 <Quantity [0., 0., 0., ..., 0., 0., 0.]>)
In [18]:
ls *txt
uvis_zp_tbl.txt                   wfc3,uvis1,f200lp,mjd#,aper#.txt

To save curves for all obsmodes in the input list:

In [19]:
for det in detectors:
    for filt in filtnames:
        obsmode = 'wfc3,{},{}'.format(det, filt)
        bp = stsyn.band(obsmode)
        calculate_bands(bp, save=True)
In [20]:
ls wfc3*txt
wfc3,uvis1,f200lp,mjd#,aper#.txt  wfc3,uvis1,f547m.txt
wfc3,uvis1,f200lp.txt             wfc3,uvis1,f555w.txt
wfc3,uvis1,f218w.txt              wfc3,uvis1,f600lp.txt
wfc3,uvis1,f225w.txt              wfc3,uvis1,f606w.txt
wfc3,uvis1,f275w.txt              wfc3,uvis1,f621m.txt
wfc3,uvis1,f280n.txt              wfc3,uvis1,f625w.txt
wfc3,uvis1,f300x.txt              wfc3,uvis1,f631n.txt
wfc3,uvis1,f336w.txt              wfc3,uvis1,f645n.txt
wfc3,uvis1,f343n.txt              wfc3,uvis1,f656n.txt
wfc3,uvis1,f350lp.txt             wfc3,uvis1,f657n.txt
wfc3,uvis1,f373n.txt              wfc3,uvis1,f658n.txt
wfc3,uvis1,f390m.txt              wfc3,uvis1,f665n.txt
wfc3,uvis1,f390w.txt              wfc3,uvis1,f673n.txt
wfc3,uvis1,f395n.txt              wfc3,uvis1,f680n.txt
wfc3,uvis1,f410m.txt              wfc3,uvis1,f689m.txt
wfc3,uvis1,f438w.txt              wfc3,uvis1,f763m.txt
wfc3,uvis1,f467m.txt              wfc3,uvis1,f775w.txt
wfc3,uvis1,f469n.txt              wfc3,uvis1,f814w.txt
wfc3,uvis1,f475w.txt              wfc3,uvis1,f845m.txt
wfc3,uvis1,f475x.txt              wfc3,uvis1,f850lp.txt
wfc3,uvis1,f487n.txt              wfc3,uvis1,f953n.txt
wfc3,uvis1,f502n.txt