Understanding the Roman WFI ePSF Reference File#
Kernel Information and Read-Only Status#
To run this notebook, please select “Roman Research Nexus {VERSION}” kernel at the top right of your window. For example “Roman Research Nexus 2026.1”.
This notebook is read-only. You can run cells and make edits, but you must save changes to a different location. We recommend saving the notebook within your home directory, or to a new folder within your home (e.g. file > save notebook as > my-nbs/nb.ipynb). Note that a directory must exist before you attempt to add a notebook to it.
Introduction#
The purpose of this notebook is to understand the content and purpose of the Empirical PSF (PSF / ePSF) reference file.
The ePSF reference file contains high-fidelity empirical point spread function stamps. It is used by the source_catalog step for accurate photometry and morphology measurements.
More details about this and other reference files can be found in the Reference File Information.
Local Run Settings#
If you want to run the notebook in your local machine, refer to the information in local installation instructions before proceeding with the notebook. The instructions provide important information about setting up your environment and installing dependencies.
Imports#
Libraries used:
astropy for image normalization
copy for making copies of Python objects
crds for access to calibration reference files
matplotlib and mpl_toolkits for plotting images
numpy for array manipulation
roman_datamodels for opening Roman WFI ASDF files
os for operating system functions
import os
from astropy.visualization import simple_norm
import copy
import matplotlib.pyplot as plt
from matplotlib import colors, colormaps as cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
import roman_datamodels as rdm
The Calibration Reference Data System (CRDS)#
The reference files, developed and validated by STScI’s Science Operations Center, are continually updated as new WFI data become available. For more information about how CRDS works and how it assigns the most appropriate reference file for each calibration step, refer to the notebook Understanding CRDS and How to Select Calibration Reference files.
IMPORTANT NOTE: Reference files are a work in progress and will be updated several times before Roman launch. If you notice irregularities or missing information, please understand that they may be a known issue. If you have questions, please contact the Roman Help Desk.
import crds
Now let’s dive into this reference file type.
Empirical PSF Reference File#
The ePSF reference file contains empirical PSF stamps (including versions with and without inter-pixel capacitance). It is used for source detection and photometry.
For more details, see the romancal documentation.
Before proceeding, let’s check the environmental variables set for CRDS
print(f"CRDS server location: {os.environ.get('CRDS_SERVER_URL')}")
print(f"CRDS context file: {os.environ.get('CRDS_CONTEXT')}")
CRDS server location: https://roman-crds.stsci.edu
CRDS context file: roman-edit
If we want to change the context, we can do it in the next cell. In this case, we choose context roman_0058.pmap.
os.environ['CRDS_CONTEXT']='roman_0058.pmap'
Retrieving Reference Files#
As you run the exposure pipeline, the most up-to-date reference files will be automatically selected for each step. However, if you would like to use a specific reference file, retrieve it using the CRDS Python API and feed it to the Exposure Level and Mosaic Pipeline, see the notebook Understanding CRDS and How to Select Calibration Reference files for more details.
In CRDS the PSF reference files are refered as the Empirial PSF or ePSF. The keywords that will identify the best reference file to use are:
ROMAN.META.INSTRUMENT.NAME
ROMAN.META.INSTRUMENT.OPTICAL_ELEMENT
ROMAN.META.EXPOSURE.START_TIME
These keywords may be combined into a single dictionary to find and download the file using crds.getreferences().
meta = {'ROMAN.META.INSTRUMENT.NAME': 'WFI',
'ROMAN.META.INSTRUMENT.OPTICAL_ELEMENT': 'F158',
'ROMAN.META.EXPOSURE.START_TIME': '2026-01-01 00:00:00'
}
ref_files = crds.getreferences(meta, reftypes=['epsf'], observatory='roman')
ref_files
CRDS - INFO - Syncing 30 files
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_specpsf_0001.rmap 572 bytes (1 / 30 files) (0 / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_skycells_0002.rmap 712 bytes (2 / 30 files) (572 / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_sflat_0001.rmap 548 bytes (3 / 30 files) (1.3 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_saturation_0003.rmap 2.4 K bytes (4 / 30 files) (1.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_relflux_0001.rmap 555 bytes (5 / 30 files) (4.3 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_refpix_0003.rmap 2.3 K bytes (6 / 30 files) (4.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_readnoise_0006.rmap 3.4 K bytes (7 / 30 files) (7.1 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_photom_0004.rmap 2.3 K bytes (8 / 30 files) (10.5 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_optmodel_0001.rmap 586 bytes (9 / 30 files) (12.9 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_matable_0004.rmap 690 bytes (10 / 30 files) (13.4 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_mask_0003.rmap 2.3 K bytes (11 / 30 files) (14.1 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_linearity_0005.rmap 2.4 K bytes (12 / 30 files) (16.4 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_ipc_0003.rmap 3.3 K bytes (13 / 30 files) (18.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_inverselinearity_0005.rmap 2.6 K bytes (14 / 30 files) (22.1 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_integralnonlinearity_0002.rmap 2.8 K bytes (15 / 30 files) (24.7 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_gain_0003.rmap 2.3 K bytes (16 / 30 files) (27.5 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_flat_0006.rmap 8.5 K bytes (17 / 30 files) (29.7 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_etc_0002.rmap 2.3 K bytes (18 / 30 files) (38.2 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_epsf_0004.rmap 16.3 K bytes (19 / 30 files) (40.5 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_dustmap_0003.rmap 855 bytes (20 / 30 files) (56.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_distortion_0002.rmap 3.6 K bytes (21 / 30 files) (57.7 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_detectorstatus_0002.rmap 773 bytes (22 / 30 files) (61.2 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_darkdecaysignal_0002.rmap 783 bytes (23 / 30 files) (62.0 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_dark_0011.rmap 4.4 K bytes (24 / 30 files) (62.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_area_0002.rmap 2.3 K bytes (25 / 30 files) (67.2 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_apcorr_0003.rmap 2.3 K bytes (26 / 30 files) (69.5 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_abvegaoffset_0002.rmap 2.5 K bytes (27 / 30 files) (71.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_absflux_0001.rmap 555 bytes (28 / 30 files) (74.3 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_wfi_0056.imap 2.4 K bytes (29 / 30 files) (74.8 K / 77.6 K bytes)
CRDS - INFO - Fetching /home/runner/crds_cache/mappings/roman/roman_0058.pmap 344 bytes (30 / 30 files) (77.3 K / 77.6 K bytes)
CRDS - INFO - Syncing 1 files
CRDS - INFO - Fetching /home/runner/crds_cache/references/roman/wfi/roman_wfi_epsf_0015.asdf 178.5 M bytes (1 / 1 files) (0 / 178.5 M bytes)
{'epsf': '/home/runner/crds_cache/references/roman/wfi/roman_wfi_epsf_0015.asdf'}
Examining Reference Files#
Reference files use roman_datamodels just like WFI science data products and can be accessed in the same way (see the tutorial Working with ASDF for more information). Let’s take a closer look at the files we retrieved from our crds.getreferences() example:
epsf = rdm.open(ref_files['epsf'])
epsf.info()
root (AsdfObject)
├─asdf_library (Software)
│ ├─author (str): The ASDF Developers
│ ├─homepage (str): http://github.com/asdf-format/asdf
│ ├─name (str): asdf
│ └─version (str): 4.1.0
├─history (AsdfDictNode)
│ └─extensions (AsdfListNode)
│ ├─0 (ExtensionMetadata) ...
│ ├─1 (ExtensionMetadata) ...
│ └─2 (ExtensionMetadata) ...
└─roman (EpsfRef) # ePSF Reference File Schema
├─meta (AsdfDictNode)
│ ├─author (str): T. Desjardins # Author
│ ├─defocus (AsdfListNode) # Defocus Waves ...
│ ├─description (str) # Description
│ ├─instrument (AsdfDictNode) ...
│ ├─origin (str): STSCI # Organization
│ └─8 not shown
├─psf (NDArrayType) # ePSF Stamps ...
├─extended_psf (NDArrayType) # Extended ePSF Stamp ...
├─extended_psf_noipc (NDArrayType) ...
└─psf_noipc (NDArrayType) ...
Some nodes not shown.
We see that the gain reference file contains metadata plus the data, a 2D array with the gain value (electrons per DN) for each pixel.
Basic Statistics#
Now lets get some basic statistics on the cube (or a representative slice)
for name in ['psf', 'extended_psf', 'psf_noipc', 'extended_psf_noipc']:
if hasattr(epsf, name):
data = getattr(epsf, name)
print(f"\n{name} shape: {data.shape}")
print(f" Min: {data.min():.2e} Max: {data.max():.2e}")
print(f" Mean: {data.mean():.2e}")
psf shape: (3, 3, 9, 361, 361)
Min: 8.61e-09 Max: 2.32e-02
Mean: 7.50e-06
extended_psf shape: (3641, 3641)
Min: 1.05e-13 Max: 2.19e-02
Mean: 7.46e-08
psf_noipc shape: (3, 3, 9, 361, 361)
Min: 7.81e-09 Max: 2.36e-02
Mean: 7.54e-06
extended_psf_noipc shape: (3641, 3641)
Min: 8.24e-14 Max: 2.22e-02
Mean: 7.50e-08
print("PSF array shape:", epsf.psf.shape)
print("Suggested slices to try:")
for d0 in range(epsf.psf.shape[0]):
for d1 in range(epsf.psf.shape[1]):
for d2 in [0, 4, 8]: # edges and middle
print(f" epsf.psf[{d0}, {d1}, {d2}]")
PSF array shape: (3, 3, 9, 361, 361)
Suggested slices to try:
epsf.psf[0, 0, 0]
epsf.psf[0, 0, 4]
epsf.psf[0, 0, 8]
epsf.psf[0, 1, 0]
epsf.psf[0, 1, 4]
epsf.psf[0, 1, 8]
epsf.psf[0, 2, 0]
epsf.psf[0, 2, 4]
epsf.psf[0, 2, 8]
epsf.psf[1, 0, 0]
epsf.psf[1, 0, 4]
epsf.psf[1, 0, 8]
epsf.psf[1, 1, 0]
epsf.psf[1, 1, 4]
epsf.psf[1, 1, 8]
epsf.psf[1, 2, 0]
epsf.psf[1, 2, 4]
epsf.psf[1, 2, 8]
epsf.psf[2, 0, 0]
epsf.psf[2, 0, 4]
epsf.psf[2, 0, 8]
epsf.psf[2, 1, 0]
epsf.psf[2, 1, 4]
epsf.psf[2, 1, 8]
epsf.psf[2, 2, 0]
epsf.psf[2, 2, 4]
epsf.psf[2, 2, 8]
Visualization#
Let’s check this reference file
fig = plt.figure(figsize=(14, 10))
# Extended PSF (large stamp)
if hasattr(epsf, 'extended_psf'):
ax1 = plt.subplot(2, 2, 1)
data = epsf.extended_psf
norm = simple_norm(data, stretch='log', percent=99.5)
im1 = ax1.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax1.set_title('Extended PSF')
plt.colorbar(im1, ax=ax1, fraction=0.046, pad=0.04)
# Extended PSF no IPC
if hasattr(epsf, 'extended_psf_noipc'):
ax2 = plt.subplot(2, 2, 2)
data = epsf.extended_psf_noipc
norm = simple_norm(data, stretch='log', percent=99.5)
im2 = ax2.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax2.set_title('Extended PSF (no IPC)')
plt.colorbar(im2, ax=ax2, fraction=0.046, pad=0.04)
# Central slice of the small PSF stamp (e.g., central oversampled PSF)
if hasattr(epsf, 'psf'):
ax3 = plt.subplot(2, 2, 3)
data = epsf.psf[1, 1, 4] # middle slice (adjust indices as needed)\
data = epsf.psf[2,2,0]
norm = simple_norm(data, stretch='log', percent=99.5)
im3 = ax3.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax3.set_title('PSF Stamp (central slice)')
plt.colorbar(im3, ax=ax3, fraction=0.046, pad=0.04)
# Same for no IPC
if hasattr(epsf, 'psf_noipc'):
ax4 = plt.subplot(2, 2, 4)
data = epsf.psf_noipc[1, 1, 4]
norm = simple_norm(data, stretch='log', percent=99.5)
im4 = ax4.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax4.set_title('PSF Stamp no IPC (central slice)')
plt.colorbar(im4, ax=ax4, fraction=0.046, pad=0.04)
plt.tight_layout()
plt.show()
fig = plt.figure(figsize=(14, 10))
# Extended PSF (large stamp)
if hasattr(epsf, 'extended_psf'):
ax1 = plt.subplot(3, 2, 1)
data = epsf.extended_psf
norm = simple_norm(data, stretch='log', percent=99.5)
im1 = ax1.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax1.set_title('Extended PSF')
plt.colorbar(im1, ax=ax1, fraction=0.046, pad=0.04)
# Extended PSF no IPC
if hasattr(epsf, 'extended_psf_noipc'):
ax2 = plt.subplot(3,2,2)
data = epsf.extended_psf_noipc
norm = simple_norm(data, stretch='log', percent=99.5)
im2 = ax2.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax2.set_title('Extended PSF (no IPC)')
plt.colorbar(im2, ax=ax2, fraction=0.046, pad=0.04)
# Central slice of the small PSF stamp
if hasattr(epsf, 'psf'):
ax3 = plt.subplot(3,2, 3)
data = epsf.psf[1, 1, 4] # middle slice (adjust indices as needed)
norm = simple_norm(data, stretch='log', percent=99.5)
im3 = ax3.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax3.set_title('PSF Stamp (central slice)')
plt.colorbar(im3, ax=ax3, fraction=0.046, pad=0.04)
# Central slice of the small PSF stamp
if hasattr(epsf, 'psf'):
ax5 = plt.subplot(3,2,5)
data = epsf.psf[0,0, 0] # middle slice (adjust indices as needed)
norm = simple_norm(data, stretch='log', percent=99.5)
im5 = ax5.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax5.set_title('PSF Stamp (No defocus [0,0,0])')
plt.colorbar(im5, ax=ax5, fraction=0.046, pad=0.04)
# Central slice of the small PSF stamp
if hasattr(epsf, 'psf'):
ax6 = plt.subplot(3,2,6)
data = epsf.psf[2, 2, 8] # middle slice (adjust indices as needed)
norm = simple_norm(data, stretch='log', percent=99.5)
im6 = ax6.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax6.set_title('PSF Stamp (Defocused [2,2,8])')
plt.colorbar(im6, ax=ax6, fraction=0.046, pad=0.04)
# Same for no IPC
if hasattr(epsf, 'psf_noipc'):
ax4 = plt.subplot(3, 2, 4)
data = epsf.psf_noipc[1, 1, 4]
norm = simple_norm(data, stretch='log', percent=99.5)
im4 = ax4.imshow(data, cmap='viridis', norm=norm, origin='lower')
ax4.set_title('PSF Stamp no IPC (central slice)')
plt.colorbar(im4, ax=ax4, fraction=0.046, pad=0.04)
plt.tight_layout()
plt.show()
Note: The small PSF stamps have shape (3, 3, 9, 361, 361). The first three dimensions are parameters (defocus, other conditions, oversampling). You can explore other slices by changing the indices [1, 1, 4].
About this Notebook#
Author: R. Diaz
Updated On: 2026-07-06
| Top of page |
|
|