Pixel-based ACS/WFC CTE Forward Model#
This notebook demonstrates preparing data for input into the ACS/WFC pixel-based CTE forward model and running the model.
Table of Contents#
Introduction
0. Install/update stenv
1. Imports
2. Download data and reference files
3. Create an image of artificial stars
Option A: Start with an observed FLC image
4. Add artificial stars
5. Reverse the flat and dark correction
6. Run CTE forward model
7. (Optional) Run CTE correction
8. Apply flat and dark correction
Option B: Start with a synthetic image
4. Create your image
5. Run CTE forward model
6. (Optional) Run CTE correction
7. Apply flat and dark correction
About this Notebook
This notebook currently fails to execute, use as reference only
Introduction#
The charge transfer efficiency (CTE) of the Advanced Camera for Surveys (ACS) Wide Field Channel (WFC) has been decreasing over the lifetime of the instrument. Radiation damage from cosmic rays and other sources leads to charge traps within the detector. These traps remove electrons from charge packets as they are transferred between rows of the detector, and release the electrons in subsequent pixels. This causes flux to be removed from bright features and released into pixels behind the features (relative to the row closest to the amplifier), creating bright trails.
A pixel-based CTE correction model for the ACS/WFC detector is fully described in Anderson & Bedin (2010), and a recent update to the model is presented in ACS ISR 2018-04. The model is based on an empirical determination of the number and depth of charge traps distributed across the detector. It simulates detector readout of an input image, removes the result from the input, and iterates five times. In this way, a reverse model is successively approximated by the forward model. Electrons released in trails are removed and added back to the bright feature in which they originated.
The pixel-based correction was implemented in the calibration pipeline code for ACS (CALACS
) in 2012 and the algorithm was updated and improved in 2018. The CTE correction step within CALACS
runs on bias-corrected images, blv_tmp
files, producing blc_tmp
files, which lack the bright trails due to poor CTE. Further calibration, including dark correction and flat-fielding, produces flt
and flc
files from the blv_tmp
and blc_tmps
files, respectively. For more information on calibration of ACS/WFC data, see the ACS Data Handbook.
Users desiring to more fully understand the effects of pixel-based CTE correction on their science may wish to run the forward model (i.e., the detector readout simulation) on data containing artificial stars. Here we demonstrate two methods for running the CTE forward model. In Option A, we begin with an observed flc
image, whereas in Option B, we begin with a raw
image and generate synthetic data on which to run the forward model.
Note: The forward model, like the CTE correction step in CALACS
, adds 10% of the difference between the input and output SCI
extensions to the ERR
extensions to account for uncertainty in the CTE model. Below, we provide guidance for properly repopulating the ERR
extensions of forward-modeled data.
0. Install/update stenv#
*AstroConda is no longer supported and is superseded by stenv.#
stenv will include most packages from AstroConda and is recommended to process and analyze data from the Hubble Space Telescope (HST) and James Webb Space Telescope (JWST). To install and activate stenv, please refer to the documentation. NOTE: stenv requires Python 3.8 or greater.
If you already use stenv, make sure the versions of hstcal
and acstools
are both at least 2.1.0. The version of CALACS
should be at least 10.1.0. Check the versions of all three on the command line:
$ conda list hstcal
$ conda list acstools
$ calacs.e --version
It is recommended, however, that you use the most up-to-date versions of these packages. To update these packages, run the following via the command line:
$ conda update hstcal
$ conda update acstools
1. Imports#
Start by importing several packages:
matplotlib notebook for creating interactive plots
os for setting environment variables
shutil for managing directories
numpy for math and array calculations
collections OrderedDict for making dictionaries easily
matplotlib pyplot for plotting
matplotlib.colors LogNorm for scaling images
astropy.io fits for working with FITS files
photutils datasets for creating synthetic stars and images
astroquery.mast Observations for downloading data from MAST
acstools acsccd for performing bias correction
acstools acscte for performing CTE correction (reversing CTE trailing)
acstools acs2d for performing dark correction and flat-fielding
acstools acscteforwardmodel for running CTE forward model (generating CTE trailing)
import os
import shutil
import numpy as np
from collections import OrderedDict
import matplotlib.pyplot as plt
from astropy.io import fits
from photutils import datasets
from astroquery.mast import Observations
from acstools import acsccd
from acstools import acscte
from acstools import acs2d
from acstools import acscteforwardmodel
Top of Page
2. Download data and reference files#
Full-frame, new-mode subarray, and 2K old-mode subarray ACS/WFC images can be run through the CTE forward model. New-mode subarrays were added to the HST flight software at the beginning of Cycle 24. These subarrays have APERTURE
keywords of the type WFC1A-512, WFC1A-1K, WFC1A-2K
, etc. Old-mode subarrays have APERTURE
keywords of the type WFC1-512, WFC1-1K, WFC1-2K
, etc. WFC apertures are also listed in Table 7.7 of the ACS IHB.
We recommend that the CTE forward model be run on data that has been bias-corrected, but not dark-corrected or flat-fielded. The flat and dark should be present in the image input into the CTE forward model because these features are present in the image when it is read out, and are therefore affected by CTE losses. The forward model can be run on flc
files, but the results will technically be incorrect. Photometric tests of forward modeled data of both types show minor differences. Post-SM4 subarray data must be destriped with acs_destripe_plus
, which will also perform the other calibration steps. Note: At this time, acs_destripe_plus
only produces flt
/flc
images.
We download a full-frame 47 Tuc image, jd0q14ctq
, from the ACS CCD Stability Monitor program (PI: Coe, 14402) from the Mikulski Archive for Space Telescopes (MAST) using astroquery. This image was taken in March 2016, and so it is strongly affected by CTE losses. We download the flc
image for Option A and the raw
image for Option B into the current directory.
obs_table = Observations.query_criteria(obs_id='jd0q14ctq')
dl_table = Observations.download_products(obs_table['obsid'], mrp_only=False,
productSubGroupDescription=['FLC', 'RAW'])
for row in dl_table:
oldfname = row['Local Path']
unique_fname = np.unique(oldfname)
newfname = os.path.basename(oldfname)
print(row)
os.rename(oldfname, newfname)
shutil.rmtree('mastDownload')
Next, update and download the correct flat and dark reference files for the jd0q14ctq
dataset from the Calibration Reference Data System (CRDS). We use the CRDS command line tools to do this.
os.environ['CRDS_SERVER'] = 'https://hst-crds.stsci.edu'
os.environ['CRDS_PATH'] = './crds_cache'
os.environ['jref'] = './crds_cache/references/hst/acs/'
!crds bestrefs --update-bestrefs --sync-references=1 --files jd0q14ctq_flc.fits
Next, we obtain the filenames of the flat (PFLTFILE
), CTE-corrected dark (DRKCFILE
), and superbias (BIASFILE
) reference files from the image header. The flat will be used to add the effects of the flat field back into the image. If the data were post-flashed, then the flash file (FLSHFILE
) is needed as well. This is shown in the commented line below. The CTE-corrected dark will be used to add dark current to the flc
file and the synthetic data. The superbias will be used to repopulate the ERR
extensions of the forward-modeled image.
hdr = fits.getheader('jd0q14ctq_flc.fits')
flat = hdr['PFLTFILE'].split('$')[-1]
dkc = hdr['DRKCFILE'].split('$')[-1]
bias = hdr['BIASFILE'].split('$')[-1]
# flash = hdr['FLSHFILE'].split('$')[-1]
We open the flat and dark images and obtain the SCI
extensions of both CCDs, which are extension 1 for WFC2 and extension 4 for WFC1. We open the superbias image and obtain the ERR
extensions of both CCDs, which are extension 2 for WFC2 and extension 5 for WFC2. If the flash file is needed, obtain the SCI
extensions for both CCDs. This is shown in the commented out lines below.
# The jref environment variable gives the directory containing the reference files
flat_hdu = fits.open('{}/{}'.format(os.environ['jref'], flat))
flat_wfc1 = flat_hdu[4].data
flat_wfc2 = flat_hdu[1].data
dkc_hdu = fits.open('{}/{}'.format(os.environ['jref'], dkc))
dkc_wfc1 = dkc_hdu[4].data
dkc_wfc2 = dkc_hdu[1].data
# Darks can sometimes have negative pixels because they have been flash-corrected
# and CTE-corrected. Set all negative pixels to zero
dkc_wfc1[dkc_wfc1 < 0.] = 0.
dkc_wfc2[dkc_wfc2 < 0.] = 0.
bias_hdu = fits.open('{}/{}'.format(os.environ['jref'], bias))
err_bias_wfc1 = bias_hdu[5].data
err_bias_wfc2 = bias_hdu[2].data
dq_bias_wfc1 = bias_hdu[6].data
dq_bias_wfc2 = bias_hdu[3].data
# flash_dhu = fits.open('{}/{}'.format(os.environ['jref'], flash))
# flash_wfc1 = flash_hdu[4].data
# flash_wfc2 = flash_hdu[1].data
Top of Page
3. Create image of artificial stars#
We will need an image containing artificial stars on zero background for both options presented below. Artificial stars are typically generated using models that do not include the flat field, or are produced from data that have been flat-fielded. If this is the case, artificial stars should be added to the image at the flc
stage.
Users of this notebook may have a preferred method for generating artificial stars and adding them to data, so here we simply add several Gaussians to the image using utilities within photutils.datasets
in astropy
. These Gaussians are not representative of the true ACS/WFC PSF, and are added here for illustrative purposes only. Please note that artificial sources with peak values approaching or exceeding the WFC CCD full well value of about 80,000 electrons are not recommended for simulated data. Blooming of charge from saturated pixels is not implemented in this example.
There are many tools for generating artificial stars, including Tiny Tim, effective PSFs, or EPSFBuilder
in photutils
. A recent study of PSF models for ACS/WFC can be found here.
First, we generate a table of random Gaussian sources of typical brightness for our 47 Tuc field with \(\mathrm{FWHM}\sim2.5\) pixels. Because \(\mathrm{FWHM} = 2.355\sigma\), we will generate Gaussian sources with \(\sigma \sim 1.06\) pixels in both \(x\) and \(y\).
n_sources = 300
param_ranges = [('amplitude', [500, 30000]),
('x_mean', [0, 4095]),
('y_mean', [0, 2047]),
('x_stddev', [1.05, 1.07]),
('y_stddev', [1.05, 1.07]),
('theta', [0, np.pi])]
param_ranges = OrderedDict(param_ranges)
sources = datasets.make_random_gaussians_table(n_sources, param_ranges,
seed=12345)
print(sources)
Next, we get the shape of one of the flc
image SCI
extensions and make an image from the table of Gaussian sources. Note that this step may take a few minutes. Finally, we run the synthetic image through a Poisson sampler in order to simulate the Poisson noise of the scene.
wfc2 = fits.getdata('jd0q14ctq_flc.fits', ext=1)
shape = wfc2.shape
synth_stars_image = datasets.make_gaussian_sources_image(shape, sources)
synth_stars_image = np.random.poisson(synth_stars_image)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(synth_stars_image, vmin=0, vmax=200, interpolation='nearest',
cmap='Greys_r', origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=10,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
Option A: Start with an observed FLC image#
In this Option, we add synthetic stars to the scene in an flc
image and process it appropriately for use with the forward model. We use the flc
image because it is the closest approximation to a pristine image of the sky. Below we plot a portion of the downloaded 47 Tuc image. Stars of various magnitudes are visible, as well as cosmic rays.
flc = fits.getdata('jd0q14ctq_flc.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(flc, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r', origin='lower')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
Top of Page
4A. Add artificial stars#
Add the image of artificial stars generated above to both CCDs of the flc
image, and save it as a new file.
hdu = fits.open('jd0q14ctq_flc.fits')
wfc1 = hdu[4].data
wfc2 = hdu[1].data
wfc1 += synth_stars_image
wfc2 += synth_stars_image
hdu.writeto('jd0q14ctq_stars_flc.fits', overwrite=True)
Below we plot a section of the flc
image with a few artificial stars circled in red.
flc_stars = fits.getdata('jd0q14ctq_stars_flc.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(flc_stars, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
Top of Page
5A. Reverse the flat and dark correction#
First, calculate the total exposure time of the 47 Tuc image by combining the exposure time, flash time (if any), and 3 seconds of extra dark time to approximate instrument commanding overheads.
hdu = fits.open('jd0q14ctq_stars_flc.fits')
hdr = hdu[0].header
exptime = hdr['EXPTIME']
flashtime = hdr['FLASHDUR']
darktime = exptime + flashtime + 3
Next, we open the 47 Tuc image and obtain the SCI
extensions of both CCDs. We multiply by the flat and scale the CTE-corrected dark by the total exposure time. We also run the scaled dark image through a Poisson sampler to include Poisson noise in the dark scene. We then add the dark current to the image. We save the result, which is now effectively a blc_tmp
file. If the data are post-flashed, we also need to reverse the flash correction. We do this by multiplying the flash file by the flash duration, running it through a Poisson sampler, and adding it to the 47 Tuc image. The lines for this are commented out below. Note: It is not recommended to use a simulated exposure time that scales pixels in the dark or flash image to or above the full well depth of ~80,000 electrons.
wfc1 = hdu[4].data
wfc2 = hdu[1].data
wfc1 *= flat_wfc1
wfc2 *= flat_wfc2
wfc1 += np.random.poisson(dkc_wfc1*darktime)
wfc2 += np.random.poisson(dkc_wfc2*darktime)
# wfc1 += np.random.poisson(flash_wfc1*flashtime)
# wfc2 += np.random.poisson(flash_wfc2*flashtime)
hdu.writeto('jd0q14ctq_stars_pfl_dkc.fits', overwrite=True)
Finally, we update the header keyword PCTECORR
to PERFORM
, which is necessary for running the forward model.
fits.setval('jd0q14ctq_stars_pfl_dkc.fits', 'PCTECORR', value='PERFORM')
Top of Page
6A. Run CTE forward model#
We are now ready to run the CTE forward model, which simulates the effects of CTE losses while reading out the detector. In this example, we will use the acstools
module acscteforwardmodel
. Note that this step may take a few minutes. The resulting filename will be *_ctefmod.fits
. We rename the file to have the suffix *_blv_tmp.fits
so that it can be processed by CALACS
in a later step.
acscteforwardmodel.acscteforwardmodel('jd0q14ctq_stars_pfl_dkc.fits')
os.rename('jd0q14ctq_stars_pfl_dkc_ctefmod.fits', 'jd0q14ctq_stars_ctefmod_blv_tmp.fits')
After the forward model is run, the SCI
extensions of the image are equivalent to a blv_tmp
file, in principle. However, the ERR
extensions are the original flc
ERR
extensions plus 10% of the forward model correction. To ensure the ERR
extensions are accurate for a blv_tmp
file, we will set every pixel to zero and calculate new values for each pixel according to
\(\mathrm{ERR} = \sqrt{\mathrm{SCI} + \mathrm{RN}^2 + (\mathrm{ERR}_{\mathrm{superbias}}g)^2}\),
where \(\mathrm{SCI}\) is the pixel value in the SCI
extension (all negative pixels are set to zero), \(\mathrm{RN}\) is the readnoise, \(\mathrm{ERR}_{\mathrm{superbias}}\) is the pixel value in the ERR
extension of the superbias, and \(g\) is the gain.
First, we access the header and SCI
and ERR
extensions of the forward-modeled data.
hdu = fits.open('jd0q14ctq_stars_ctefmod_blv_tmp.fits')
sci_wfc1 = hdu[4].data
sci_wfc2 = hdu[1].data
err_wfc1 = hdu[5].data
err_wfc2 = hdu[2].data
hdr = hdu[0].header
Next, we take the readnoise and gain values for each quadrant from the header.
rn_A = hdr['READNSEA']
rn_B = hdr['READNSEB']
rn_C = hdr['READNSEC']
rn_D = hdr['READNSED']
gain_A = hdr['ATODGNA']
gain_B = hdr['ATODGNB']
gain_C = hdr['ATODGNC']
gain_D = hdr['ATODGND']
Finally, we make copies of the SCI
extensions in which to set all negative values to zero. We calculate the appropriate error for each quadrant and save them to the ERR
extensions of the forward-modeled image.
sci_wfc1_pos = np.copy(sci_wfc1)
sci_wfc2_pos = np.copy(sci_wfc2)
sci_wfc1_pos[sci_wfc1_pos < 0] = 0
sci_wfc2_pos[sci_wfc2_pos < 0] = 0
err_A = np.sqrt(sci_wfc1_pos[:, :2048] + rn_A**2 + (err_bias_wfc1[20:, 24:2072]*gain_A)**2)
err_B = np.sqrt(sci_wfc1_pos[:, 2048:] + rn_B**2 + (err_bias_wfc1[20:, 2072:-24]*gain_B)**2)
err_C = np.sqrt(sci_wfc2_pos[:, :2048] + rn_C**2 + (err_bias_wfc2[:-20, 24:2072]*gain_C)**2)
err_D = np.sqrt(sci_wfc2_pos[:, 2048:] + rn_D**2 + (err_bias_wfc2[:-20, 2072:-24]*gain_D)**2)
err_wfc1[:] = np.hstack((err_A, err_B))
err_wfc2[:] = np.hstack((err_C, err_D))
hdu.writeto('jd0q14ctq_stars_ctefmod_blv_tmp.fits', overwrite=True)
Top of Page
(Optional) 7A. Run CTE correction#
If desired, we now CTE correct the forward-modeled image. To do this, we need to update the PCTECORR
keyword to PERFORM
again and update the NEXTEND
keyword to 6, the number of extensions left after running the forward model. (This is because the forward model strips the distortion-related extensions from the input file, but does not update NEXTEND
.) Finally, run acscte
on the image. The resulting filename will be *_blc_tmp.fits
.
fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'PCTECORR', value='PERFORM')
fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'NEXTEND', value=6)
acscte.acscte('jd0q14ctq_stars_ctefmod_blv_tmp.fits')
8A. Apply flat and dark correction#
Finally, we flat-field and dark-correct the forward-modeled image using acs2d
to produce an flt
-like image. First, we update the keywords DARKCORR
and FLATCORR
to PERFORM
. All other relevant CALACS
header keyword switches are set to COMPLETE
because the original data was an flc
image. The resulting filename will be *_flt.fits
. Note: If the data were post-flashed, and the flash background was added back in during an earlier step, we must also set FLSHCORR
equal to PERFORM
. This option is shown below in a commented out line.
fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'DARKCORR', value='PERFORM')
fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'FLATCORR', value='PERFORM')
# fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'FLSHCORR', value='PERFORM')
acs2d.acs2d('jd0q14ctq_stars_ctefmod_blv_tmp.fits')
If the forward-modeled image was CTE-corrected in Step 7A, we run acs2d
on the CTE-corrected image. The resulting filename will be *_flc.fits
.
fits.setval('jd0q14ctq_stars_ctefmod_blc_tmp.fits', 'DARKCORR', value='PERFORM')
fits.setval('jd0q14ctq_stars_ctefmod_blc_tmp.fits', 'FLATCORR', value='PERFORM')
# fits.setval('jd0q14ctq_stars_ctefmod_blv_tmp.fits', 'FLSHCORR', value='PERFORM')
acs2d.acs2d('jd0q14ctq_stars_ctefmod_blc_tmp.fits')
The 47 Tuc image(s) are now prepared for further analysis appropriate for the user’s science. The cells below plot a portion of the final images, the flt
and, if produced, the flc
.
flt_stars = fits.getdata('jd0q14ctq_stars_ctefmod_flt.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(flt_stars, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
flc_stars = fits.getdata('jd0q14ctq_stars_ctefmod_flc.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(flc_stars, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
Top of Page
Option B: Start with a synthetic image#
In this Option, we start with the raw
file from the jd0q14ctq
dataset and process the SCI
extensions to make a completely synthetic dataset. We then process it appropriately for use in the forward model.
4B. Create a synthetic image#
We will create an image that is equivalent to a blc_tmp
file. This means that the image is not flat-fielded and includes sky background, Poisson noise from the sky, artificial stars or other sources, Poisson noise from the sources, dark current, and dark noise.
We first process the data with acsccd
within acstools
to create a blv_tmp
. This ensures that the error (ERR
) and data quality (DQ
) extensions are created and the header keywords for every extension are initially populated, which is necessary for the CTE forward model to run.
acsccd.acsccd('jd0q14ctq_raw.fits')
Next, we obtain the SCI
extensions from the blv_tmp
file, and set the pixels to zero.
hdu = fits.open('jd0q14ctq_blv_tmp.fits')
wfc1 = hdu[4].data
wfc2 = hdu[1].data
wfc1[:] = np.zeros(shape)
wfc2[:] = np.zeros(shape)
We then generate an image containing a user-selected sky background level (here we choose 40 electrons arbitrarily) and Poisson noise. Finally, we sum the noise image and the artificial star image, and save the result as a new file.
noise_image = datasets.make_noise_image(shape, distribution='poisson', mean=40, seed=12345)
wfc1 += noise_image + synth_stars_image
wfc2 += noise_image + synth_stars_image
hdu.writeto('synth.fits', overwrite=True)
Below we plot a portion of the synthetic image, with sources circled in red.
synth = fits.getdata('synth.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(synth, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
In order to properly remove the dark current after the forward model is run, we update the exposure time and flash duration header keywords, EXPTIME
and FLASHDUR
, with the desired simulated exposure time. The total exposure time used for the dark correction is the combination of exposure time, flash duration, and overhead (which depends on whether the data are post-flashed or not). Here we set EXPTIME
to 300 seconds and FLASHDUR
to 0 seconds. In principle one should retrieve the overheads from CCDTAB, but we provide a simpler approach here. Please note that the overhead times may change.
exptime = 300.
flashdur = 0.
hdu = fits.open('synth.fits')
hdr = hdu[0].header
hdr['EXPTIME'] = exptime
hdr['FLASHDUR'] = flashdur
if flashdur > 0:
overhead = 2.43
else:
overhead = 0.21
darktime = exptime + flashdur + overhead
We need to update the FITS header with the correct dark time for use in a later step. However, we leave off the overhead, because ACSCCD (run in a later step) will automatically append the overhead to the dark time in the header. We still use the darktime variable with overhead to manually add the dark current to our model below.
hdr['DARKTIME'] = darktime - overhead
Next, obtain the SCI
extensions of both CCDs. We then multiply by the flat and scale the CTE-corrected dark by a chosen exposure time. We also run the scaled dark image through a Poisson sampler to include Poisson noise in the dark scene. We then add the dark current to the image. If post-flash is desired, multiply the flash reference file by the flash duration, run it through a Poisson sampler, and add to the synthetic data. This is shown in the commented out lines below. We save the result, which is now effectively a blc_tmp
file. Note: It is not recommended to use a simulated exposure time that scales pixels in the dark or flash image to or above the full well depth of ~80,000 electrons.
Note that these reference files are specific to the anneal cycle in which these data were taken. If an observation date other than that listed in the DATE-OBS
header keyword is desired for the synthetic data, different reference files will be needed. These can be found by updating the DATE-OBS
header keyword in the synthetic image to the desired observation date, and rerunning the cell in Step 2 which uses CRDS bestrefs
to download the correct reference files.
wfc1 = hdu[4].data
wfc2 = hdu[1].data
wfc1 *= flat_wfc1
wfc2 *= flat_wfc2
wfc1 += np.random.poisson(dkc_wfc1*darktime)
wfc2 += np.random.poisson(dkc_wfc2*darktime)
# wfc1 += np.random.poisson(flash_wfc1*flashdur)
# wfc2 += np.random.poisson(flash_wfc2*flashdur)
hdu.writeto('synth_blc_tmp.fits', overwrite=True)
Top of Page
5B. Run CTE forward model#
We are now ready to run the CTE forward model, which simulates the effects of CTE losses while reading out the detector. In this example, we will use the acstools
module acscteforwardmodel
. Note that this step may take a few minutes. The resulting filename will be *_ctefmod.fits
. We rename this to *_blv_tmp.fits
in order to ensure the correct behavior from acs2d
in a later step.
acscteforwardmodel.acscteforwardmodel('synth_blc_tmp.fits')
os.rename('synth_blc_tmp_ctefmod.fits', 'synth_ctefmod_blv_tmp.fits')
At this point, we also add readnoise to the forward-modeled SCI
extensions to complete the readout simulation. We find the readnoise values for each quadrant of the image from the header keywords READNSEA
, READNSEB
, etc. We make a noise image for each quadrant, concatenate quadrants A and B and quadrants C and D, and add them to the synthetic image.
rn_A = hdr['READNSEA']
rn_B = hdr['READNSEB']
rn_C = hdr['READNSEC']
rn_D = hdr['READNSED']
img_rn_A = datasets.make_noise_image((shape[0], int(shape[1]/2)), distribution='gaussian',
mean=0., stddev=rn_A)
img_rn_B = datasets.make_noise_image((shape[0], int(shape[1]/2)), distribution='gaussian',
mean=0., stddev=rn_B)
img_rn_C = datasets.make_noise_image((shape[0], int(shape[1]/2)), distribution='gaussian',
mean=0., stddev=rn_C)
img_rn_D = datasets.make_noise_image((shape[0], int(shape[1]/2)), distribution='gaussian',
mean=0., stddev=rn_D)
wfc1_rn = np.hstack((img_rn_A, img_rn_B))
wfc2_rn = np.hstack((img_rn_C, img_rn_D))
hdu = fits.open('synth_ctefmod_blv_tmp.fits')
wfc1 = hdu[4].data
wfc2 = hdu[1].data
wfc1 += wfc1_rn
wfc2 += wfc2_rn
hdu.writeto('synth_ctefmod_rn_blv_tmp.fits', overwrite=True)
The SCI
extensions of this image are now equivalent to a blv_tmp
file, in principle. However, the ERR
extensions are the original blv_tmp
ERR
extensions plus 10% of the forward model correction. To ensure the ERR
extensions are accurate, we will calculate new values for each pixel according to
\(\mathrm{ERR} = \sqrt{\mathrm{SCI} + \mathrm{RN}^2 + (\mathrm{ERR}_{\mathrm{superbias}}g)^2}\),
where \(\mathrm{SCI}\) is the pixel value in the SCI
extension (all negative pixels are set to zero), \(\mathrm{RN}\) is the readnoise, \(\mathrm{ERR}_{\mathrm{superbias}}\) is the pixel value in the ERR
extension of the superbias, and \(g\) is the gain.
First, we access the header and SCI
and ERR
extensions of the forward-modeled data.
hdu = fits.open('synth_ctefmod_rn_blv_tmp.fits')
sci_wfc1 = hdu[4].data
sci_wfc2 = hdu[1].data
err_wfc1 = hdu[5].data
err_wfc2 = hdu[2].data
hdr = hdu[0].header
Next, we take the readnoise and gain values for each quadrant from the header.
rn_A = hdr['READNSEA']
rn_B = hdr['READNSEB']
rn_C = hdr['READNSEC']
rn_D = hdr['READNSED']
gain_A = hdr['ATODGNA']
gain_B = hdr['ATODGNB']
gain_C = hdr['ATODGNC']
gain_D = hdr['ATODGND']
Finally, we make copies of the SCI
extensions in which to set all negative values to zero. We calculate the appropriate error for each quadrant and save them to the ERR
extensions of the forward-modeled image.
sci_wfc1_pos = np.copy(sci_wfc1)
sci_wfc2_pos = np.copy(sci_wfc2)
sci_wfc1_pos[sci_wfc1_pos < 0] = 0
sci_wfc2_pos[sci_wfc2_pos < 0] = 0
# The superbias ERR arrays contain 20 rows of virtual overscan at the edge of
# each CCD furthest from the amplifier and 24 columns of physical prescan on the
# left and right edges.
err_A = np.sqrt(sci_wfc1_pos[:, :2048] + rn_A**2 + (err_bias_wfc1[20:, 24:2072]*gain_A)**2)
err_B = np.sqrt(sci_wfc1_pos[:, 2048:] + rn_B**2 + (err_bias_wfc1[20:, 2072:-24]*gain_B)**2)
err_C = np.sqrt(sci_wfc2_pos[:, :2048] + rn_C**2 + (err_bias_wfc2[:-20, 24:2072]*gain_C)**2)
err_D = np.sqrt(sci_wfc2_pos[:, 2048:] + rn_D**2 + (err_bias_wfc2[:-20, 2072:-24]*gain_D)**2)
err_wfc1[:] = np.hstack((err_A, err_B))
err_wfc2[:] = np.hstack((err_C, err_D))
hdu.writeto('synth_ctefmod_rn_blv_tmp.fits', overwrite=True)
We also repopulate the DQ extensions because they reflect the processing of the original blv_tmp
SCI
extensions. To do this, we reprocess the data with DQICORR
and SINKCORR
, both within acsccd
, and add in the DQ extensions of the appropriate superbias file. First, we rename the synthetic image to have a filename *_raw.fits
, or acsccd
will fail. Then, we update the DQICORR
and SINKCORR
header keywords to PERFORM
and run acsccd
. The output will be *_blv_tmp.fits
again.
This step will also add the proper overhead to DARKTIME
header keyword, but only if using CALACS v10.3.3 or later. We first check the version.
os.system('calacs.e --version')
If your version of CALCACS is prior to v10.3.3, please update. Now let’s run ACSCCD.
os.rename('synth_ctefmod_rn_blv_tmp.fits', 'synth_ctefmod_rn_raw.fits')
fits.setval('synth_ctefmod_rn_raw.fits', 'DQICORR', value='PERFORM')
fits.setval('synth_ctefmod_rn_raw.fits', 'SINKCORR', value='PERFORM')
acsccd.acsccd('synth_ctefmod_rn_raw.fits')
Next, obtain the DQ extensions of the superbias file, and add them to the new DQ extensions of the synthetic data with a bitwise_or
operator.
hdu = fits.open('synth_ctefmod_rn_blv_tmp.fits')
dq_wfc1 = hdu[6].data
dq_wfc2 = hdu[3].data
dq_wfc1[:] = np.bitwise_or(dq_wfc1, dq_bias_wfc1[20:, 24:-24])
dq_wfc2[:] = np.bitwise_or(dq_wfc2, dq_bias_wfc2[:-20, 24:-24])
Also, acsccd assumes the input image is in DN and converts it to counts by multiplying by the gain. We defined our model in electrons though, so the acsccd conversion to electrons was unnecessary. We divide by the gains to fix this and replace the image.
sci_wfc1 = hdu[4].data
sci_wfc2 = hdu[1].data
sci_wfc1[:, :2048] /= gain_A
sci_wfc1[:, 2048:] /= gain_B
sci_wfc2[:, :2048] /= gain_C
sci_wfc2[:, 2048:] /= gain_D
err_wfc1 = hdu[5].data
err_wfc2 = hdu[2].data
err_wfc1[:, :2048] /= gain_A
err_wfc1[:, 2048:] /= gain_B
err_wfc2[:, :2048] /= gain_C
err_wfc2[:, 2048:] /= gain_D
hdu.writeto('synth_ctefmod_rn_blv_tmp.fits', overwrite=True)
Top of Page
6B. (Optional) Run CTE correction#
If desired, we now CTE correct the forward-modeled image. To do this, we need to update the PCTECORR
keyword to PERFORM
again, and run acscte
on the image. The resulting filename will be *_blc_tmp.fits
.
fits.setval('synth_ctefmod_rn_blv_tmp.fits', 'PCTECORR', value='PERFORM')
acscte.acscte('synth_ctefmod_rn_blv_tmp.fits')
7B. Apply flat and dark correction#
Finally, we flat-field and dark-correct the forward-modeled image using acs2d
to produce an flt
-like image. We first ensure that the DARKCORR
, FLATCORR
, and if necessary, FLSHCORR
header keywords are set to PERFORM
. The resulting filename will be *_flt.fits
.
fits.setval('synth_ctefmod_rn_blv_tmp.fits', 'DARKCORR', value='PERFORM')
fits.setval('synth_ctefmod_rn_blv_tmp.fits', 'FLATCORR', value='PERFORM')
# fits.setval('synth_ctefmod_rn_blv_tmp.fits', 'FLSHCORR', value='PERFORM')
acs2d.acs2d('synth_ctefmod_rn_blv_tmp.fits')
If the forward-modeled image was CTE-corrected in Step 6, we run acs2d
on the CTE-corrected image. The resulting filename will be *_flc.fits
.
fits.setval('synth_ctefmod_rn_blc_tmp.fits', 'DARKCORR', value='PERFORM')
fits.setval('synth_ctefmod_rn_blc_tmp.fits', 'FLATCORR', value='PERFORM')
# fits.setval('synth_ctefmod_rn_blc_tmp.fits', 'FLSHCORR', value='PERFORM')
acs2d.acs2d('synth_ctefmod_rn_blc_tmp.fits')
The image(s) are now prepared for further analysis appropriate for the user’s science. The cells below plot a portion of the final images, the flt
and, if produced, the flc
.
synth_ctefmod = fits.getdata('synth_ctefmod_rn_flt.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(synth_ctefmod, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
synth_ctefmod_flc = fits.getdata('synth_ctefmod_rn_flc.fits', ext=1)
fig, ax = plt.subplots(1, 1, figsize=(9, 4))
ax.imshow(synth_ctefmod_flc, vmin=0, vmax=200, interpolation='nearest', cmap='Greys_r',
origin='lower')
ax.plot(sources['x_mean'], sources['y_mean'], marker='o', markersize=20,
markerfacecolor='none', markeredgecolor='red', linestyle='none')
ax.set_xlim(2000, 2800)
ax.set_ylim(1200, 1700)
For more help:#
More details may be found on the ACS website and in the ACS Instrument and Data Handbooks.
Please visit the HST Help Desk. Through the help desk portal, you can explore the HST Knowledge Base and request additional help from experts.
About this Notebook#
Author: Jenna Ryon, ACS Instrument Team
Updated On: 04/21/2022