STIPS Tutorials

The Space Telescope Imaging Product Simulator (STIPS) is a versatile tool designed to simulate exposure level astronomical scenes from the Wide Field Instrument (WFI) for Roman. This article presents a series of tutorials demonstrating the range of functionalities, allowing users to develop simulations tailored to their scientific goals.



Tutorial #1: Basic STIPS Usage

The following tutorial describes a straightforward use of the STIPS software that can serve as the basis for other use cases. The Basic STIPS Usage Tutorial builds on the concepts introduced in the  STIPS Overview article and is designed to walk through the phases of using  STIPS at the most introductory level: creating a small scene, designing an observation, and generating a simulated image. 


A. Generating a Simple Astronomical Scene

STIPS provides functionalities to generate catalogs of stars or galaxies with user-specified input parameters. Users can also provide their own source catalogs, as described in Catalogs formatting on readthedocs.

In the example below, we show how to create an astronomical scene by passing the user-defined input catalogs to STIPS in the FITS format. The catalog contains the coordinates of the sources, the observed flux, and any necessary shape parameters. The code block below provides an example of the required input information to generate a catalog containing two point sources. The catalog is saved to a file called catalog.fits for later use.

Example STIPS Input Catalog
from astropy.io import fits

cols=[]
cols.append(fits.Column(name='id'   ,array=[1,2]            , format='K' )) # Object ID
cols.append(fits.Column(name='ra'   ,array=[90.02,90.03]    , format='D' )) # RA in degrees
cols.append(fits.Column(name='dec'  ,array=[29.98,29.97]    , format='D' )) # DEC in degrees
cols.append(fits.Column(name='flux' ,array=[0.00023,0.0004] , format='D' )) # Flux in `units`
cols.append(fits.Column(name='type' ,array=['point','point'], format='8A')) # `point` or `sersic`
cols.append(fits.Column(name='n'    ,array=[0,0]            , format='D' )) # Sersic profile index
cols.append(fits.Column(name='re'   ,array=[0,0]            , format='D' )) # Half-light radius in pixels
cols.append(fits.Column(name='phi'  ,array=[0,0]            , format='D' )) # Angle of PA in degrees
cols.append(fits.Column(name='ratio',array=[0,0]            , format='D' )) # Axial Ratio
cols.append(fits.Column(name='notes',array=['','']          , format='8A')) # Notes
cols.append(fits.Column(name='units',array=['j','j']        , format='8A')) # Units, 'j' for jansky

# Create output fits table
hdut = fits.BinTableHDU.from_columns(cols)
hdut.header['TYPE']='mixed'
hdut.header['FILTER']='F129'

# Write to disk
hdut.writeto('catalog.fits',overwrite=True)


B. Initial Observation Setup

Next, we prepare inputs for the scene we created above. We use the ObservationModule  to generate an observation object. We generate an obs dictionary containing the properties of the observation (e.g. the instrument and the detector setups) and parameters describing central coordinates (RA, Dec), orientation angle, and computational parameters for the simulation. 

The Observation Dictionary

An example  obs dictionary is specified in the code block below. In this example,  we will use 

  • The imaging filter F129
  • The WFI01 Sensor Chip Assembly (SCA)
  • Sky background from Pandeia
  • An observation ID of 42
  • An exposure time of 300 seconds

Together with the following single offset setting:

  • An offset ID of 1
  • No centering
  • No changes in RA, Dec, and PA from the center of the observation
Observation Dictionary
from stips.observation_module import ObservationModule

# Build observation parameters
obs = {'instrument'       : 'WFI',
       'filters'          : ['F129'],
       'detectors'        : 1,
       'background'       : 'pandeia',
       'observations_id'  : 42,
       'exptime'          : 300,
       'offsets'          : [{'offset_id'    : 1    ,
                              'offset_centre': False,
                              'offset_ra'    : 0.0  ,
                              'offset_dec'   : 0.0  ,
                              'offset_pa'    : 0.0  }]}

The Observation Object

An observation object combines the obs dictionary with the central coordinates (RA, Dec), orientation angle, and computational parameters for the simulation. Then, the setup can be initialized.

Initialization of the Observation Module
# Create observation object
obm = ObservationModule(obs,
                        ra    = 90,
                        dec   = 30,
                        pa    = 0,
                        seed  = 42,
                        cores = 6)


C. Generating a Simulated Image

The code block below combines the inputs required for STIPS that were generated above and finalizes the ObservationModule to run a simulation. The final simulated image will be saved under the name specified by the  fits_file variable, and the result is shown on the right.

Full Inputs for STIPS
import numpy as np
import matplotlib.pyplot as plt

# Initialize the local instrument
obm.nextObservation()
 
# Add catalog with sources
cat_name = obm.addCatalogue('catalog.fits')
 
# Add error to image
psf_file = obm.addError()
 
# Call the final method
fits_file, mosaic_file, params = obm.finalize(mosaic=False)
print("Output FITS file is {}".format(fits_file))

data = fits.open(fits_file)[1].data

# Plot the simulated FITS image
fig, ax = plt.subplots(2, 1, figsize=(16, 10))

vmin = np.percentile(data, 5)
vmax = np.percentile(data, 95)

ax[0].imshow(data,
             origin="lower",
             cmap="grey",
             vmin=vmin,
             vmax=vmax)

ax[1].imshow(data,
             origin="lower",
             cmap="grey",
             vmin=vmin,
             vmax=vmax)

ax[1].set_xlim(2300, 3300)
ax[1].set_ylim(700, 1700)

ax[0].set_xlabel("X [pixels]")
ax[0].set_ylabel("Y [pixels]")

ax[1].set_xlabel("X [pixels]")
ax[1].set_ylabel("Y [pixels]")

plt.suptitle("Output Simulated FITS image")
ax[1].set_title("Zoomed portion of simulated image")

plt.tight_layout()
plt.show()

Result of STIPS Basic Use Tutorial

This output FITS image was simulated with STIPS using this tutorial. 






Tutorial #2: Generating a Scene from a Simulated Catalog 

 In this example, we show users how to build a simulated catalog using STIPS built-in modules. The user must set the parameters for the stellar and galactic populations in the scene. 


A. Generating an Astronomical Scene

Stellar Source Catalog

Below is an example showing how to generate a stellar population defined by stellar_parameters

Code Generating a Stellar Population
obs_prefix = 'notebook_example'
obs_ra = 150.0
obs_dec = -2.5

from stips.scene_module import SceneModule

scm = SceneModule(out_prefix=obs_prefix, ra=obs_ra, dec=obs_dec)

stellar_parameters = {
                      'n_stars': 100,
                      'age_low': 7.5e12, 
                      'age_high': 7.5e12,
                      'z_low': -2.0, 
                      'z_high': -2.0,
                      'imf': 'salpeter', 
                      'alpha': -2.35,
                      'binary_fraction': 0.1,
                      'clustered': True,
                      'distribution': 'invpow',
                      'radius': 100.0, 
                      'radius_units': 'pc',
                      'distance_low': 20.0, 
                      'distance_high': 20.0,
                      'offset_ra': 0.0, 
                      'offset_dec': 0.0
                     }

stellar_cat_file = scm.CreatePopulation(stellar_parameters)
print("Stellar population saved to file {}".format(stellar_cat_file))

Galaxy Source Catalog

Below is an example showing how to generate a galaxy population defined by galaxy_parameters

Code Generating a Background Galaxy Population
 galaxy_parameters = {
                     'n_gals': 10,
                     'z_low': 0.0, 
                     'z_high': 0.2,
                     'rad_low': 0.01, 
                     'rad_high': 2.0,
                     'sb_v_low': 30.0, 
                     'sb_v_high': 25.0,
                     'distribution': 'uniform', 
                     'clustered': False,
                     'radius': 200.0, 
                     'radius_units': 'arcsec',
                     'offset_ra': 0.0, 
                     'offset_dec': 0.0,
                    }

galaxy_cat_file = scm.CreateGalaxies(galaxy_parameters)
print("Galaxy population saved to file {}".format(galaxy_cat_file))


B. Generating the Scene 

The stellar source catalog can now be observed. In this example, we will use:

  • The F129 filter
  • The WFI03 SCA
  • Distortion on
  • A uniform sky background level of 0.24 counts/s/pixel
  • An ID of 1
  • An exposure time of 1500 seconds

Together with the following single offset setting:

  • An ID of 1
  • No centering
  • Changes in RA of 2 degrees, and PA of 0.5 degrees from the center of the observation
  • No change in DEC

And the following residual settings:

  • Flatfield residual on
  • Dark current residual on
  • Cosmic ray removal residual off
  • Poisson noise residual off
  • Readnoise residual off
Code to Generate the Astronomical Scene in Example #2
# Set the parameters for the offset, residuals, and observation
offset_1 = {
          'offset_id': 1,
          'offset_centre': False,
          'offset_ra': 2.0,
          'offset_dec': 0.0,
          'offset_pa': 0.5
          }
 
residuals_1 = {
             'residual_flat': True,
             'residual_dark': True,
             'residual_cosmic': False,
             'residual_poisson': False,
             'residual_readnoise': False
            }
 
observation_parameters_1 = {
                          'instrument': 'WFI',
                          'filters': ['F129'],
                          'detectors': 3,
                          'distortion': True,
                          'background': 0.24,
                          'observations_id': 1,
                          'exptime': 1500,
                          'offsets': [offset_1]
                          }
 
# Create observation object
obm_1 = ObservationModule(observation_parameters_1,
                          out_prefix=obs_prefix,
                          ra=obs_ra,
                          dec=obs_dec,
                          residual=residuals_1)
 
# nextObservation is called to move between different combinations of offset and filter.
# It must be called once in order to initialize the observation module to the first observation before adding catalogs.
obm_1.nextObservation()
 
# Add the scene catalogues
output_stellar_catalogues = obm_1.addCatalogue(stellar_cat_file)
output_galaxy_catalogues = obm_1.addCatalogue(galaxy_cat_file)
 
print("Output Catalogues are {} and {}".format(output_stellar_catalogues, output_galaxy_catalogues))
 
# Add in the error residuals
obm_1.addError()
 
# Finalize the observation
fits_file, mosaic_file, params = obm_1.finalize(mosaic=False)

print("Output FITS file is {}".format(fits_file))
print("Output Mosaic file is {}".format(mosaic_file))
print("Observation Parameters are {}".format(params))

data = fits.open(fits_file)[1].data

vmin = np.percentile(data, 5)
vmax = np.percentile(data, 95)

plt.imshow(data,
           origin="lower",
           vmin=vmin,
           vmax=vmax)

plt.xlabel("X [pixels]")
plt.ylabel("Y [pixels]")

plt.show()

Result of the Astronomical Scene Tutorial


Result of Tutorial #2. Image may look different due to random seed values on the source generators.







Tutorial #3: Modifying Observation Parameters 

We can use the results of Tutorial #2 as the first of a set of observations with different properties (e.g., dither, noise residuals).


The function nextObservation()is required to move between different observations. This function must be called at least once, in order to initialize the observation module to the first observation before catalogs are added.  The obs_prefix , obs_ra , and obs_dec values are set during initial setup of STIPS . Then, we can modify the offsets so that the same underlying astronomical scene is observed with different parameters. The code below adds an offset of 10 degrees in the RA and a rotation of 27 degrees, with a read noise residual instead of a dark current residual. This process can be repeated to generate sets of observations with different observing parameters, for example, to construct complex mosaics or compare results with different noise residuals.

Example Code with Different Observation Parameters
# Set the parameters for the offset, residuals, and observation
offset_2 = {
          'offset_id': 2,
          'offset_centre': False,
          'offset_ra': 10.0,
          'offset_dec': 0.0,
          'offset_pa': 27
          }
 
residuals_2 = {
             'residual_flat': True,
             'residual_dark': False,
             'residual_cosmic': False,
             'residual_poisson': False,
             'residual_readnoise': True
            }
 
observation_parameters_2 = {
                          'instrument': 'WFI',
                          'filters': ['F129'],
                          'detectors': 3,
                          'distortion': True,
                          'background': 0.24,
                          'observations_id': 1,
                          'exptime': 1500,
                          'offsets': [offset_2]
                          }
 
# Create observation object
obm_2 = ObservationModule(observation_parameters_2,
                          out_prefix=obs_prefix,
                          ra=obs_ra, dec=obs_dec,
                          residual=residuals_2)
 
# Initialize the local instrument
obm_2.nextObservation()




Tutorial #4: PSFs and Adding Sources 

STIPS offers a variety of customizable options through the use of utility functions.  As an example, we describe the use of the makePSF module. This module transforms the input PSFs from WebbPSF into effective PSFs, which are empirical models defining what fraction of a point source's flux will land in a particular pixel. The generated PSF models can then be used to create new scenes or to add sources to existing scenes.


A. Generating effective PSF models 

STIPS enables users to manually produce PSF models for a given set of detector positions and use them to insert point sources into an existing scene. PSFs at specific locations can be generated by providing the input PSF library and detector positions to the makePSF module within STIPS . The generated PSFs are returned as a 2D array.

STIPS performs two types of interpolation to obtain the exact PSF model at user-specified locations. First, it applies bilinear interpolation of the 3x3 PSF library array to compute the best PSF at the specified integer SCA pixels. Then, it performs bicubic interpolations over the supersampled pixel grid of this PSF to generate the best PSF at the specified SCA sub-pixel positions.

As an example, the file psf_WFI_2.0.0_F129_sca01.fits contains the library PSF for filter F129 and SCA 1. This file is available in the notebooks directory of the STIPS GitHub repository.

The library PSF can be loaded using the following code:

Code to Access the Library PSF
import stips
 
with fits.open('psf_WFI_2.0.0_F129_sca01.fits') as hdul:
    test_psf = stips.utilities.makePSF.make_epsf(hdul[0].data[0])
 
vmin = np.percentile(test_psf, 5)
vmax = np.percentile(test_psf, 95)

plt.imshow(test_psf,
           origin='lower',
           vmin=vmin,
           vmax=vmax)

plt.xlabel('X [pixels]')
plt.ylabel('Y [pixels]')

plt.show()

Visualization of the Example PSF

Visualization of one of the library PSF from the FITS file psf_WFI_2.0.0_F129_sca01.fits



B. Adding an Artificial Point Source 

We will now insert an artificial star into the astronomical scene defined in Example #2. For convenience, we provide the code used in Example #2 to generate the astronomical scene in the expandable box below.

STIPS Code from Example#2
offset_1 = {
          'offset_id': 1,
          'offset_centre': False,
          'offset_ra': 2.0,
          'offset_dec': 0.0,
          'offset_pa': 0.5
          }
 
residuals_1 = {
             'residual_flat': True,
             'residual_dark': True,
             'residual_cosmic': False,
             'residual_poisson': False,
             'residual_readnoise': False
            }
 
observation_parameters_1 = {
                          'instrument': 'WFI',
                          'filters': ['F129'],
                          'detectors': 3,
                          'distortion': True,
                          'background': 0.24,
                          'observations_id': 1,
                          'exptime': 1500,
                          'offsets': [offset_1]
                          }
 
# Create observation object
obm_1 = ObservationModule(observation_parameters_1,
                          out_prefix=obs_prefix,
                          ra=obs_ra,
                          dec=obs_dec,
                          residual=residuals_1)
 
# nextObservation is called to move between different combinations of offset and filter.
# It must be called once in order to initialize the observation module to the first observation before adding catalogs.
obm_1.nextObservation()
 
# Add the scene catalogues
output_stellar_catalogues = obm_1.addCatalogue(stellar_cat_file)
output_galaxy_catalogues = obm_1.addCatalogue(galaxy_cat_file)
 
print("Output Catalogues are {} and {}".format(output_stellar_catalogues, output_galaxy_catalogues))
 
# Add in the error residuals
obm_1.addError()
 
# Finalize the observation
fits_file, mosaic_file, params = obm_1.finalize(mosaic=False)

print("Output FITS file is {}".format(fits_file))
print("Output Mosaic file is {}".format(mosaic_file))
print("Observation Parameters are {}".format(params))
 

The library PSF can be added to the simulated scene by specifying the source addition parameters, Location (px) and Flux in Jansky, as demonstrated in the code below. The result of this code applied to the scene from Example #2 is shown on the right.

STIPS Code to Add an Artifical Point Source
import matplotlib.patches as patches

# Image created in the example above
with fits.open('notebook_example_1_0.fits') as result_file:
    result_data = result_file[1].data
 
# Open library PSF generated earlier in this section
with fits.open('psf_WFI_2.0.0_F129_sca01.fits') as hdul:
    test_psf = stips.utilities.makePSF.make_epsf(hdul[0].data[0])
 
# Default upscaling factor for STIPS
PSF_UPSCALE = 4
 
# Calculate center and size
psf_middle = int(np.round((test_psf[0].shape[0]-1) / 2))
boxsize = int(np.floor(psf_middle/PSF_UPSCALE))
 
added_source = stips.utilities.makePSF.place_source(2000, 
                                                    2000, 
                                                    3000, 
                                                    result_data, 
                                                    test_psf, 
                                                    boxsize=boxsize, 
                                                    psf_center=psf_middle)

# We will create a plot to compare the scene before+after adding the PSF
fig, ax = plt.subplots(2, 1, figsize=(8, 10))

ax[0].imshow(fits.getdata('notebook_example_1_0.fits'),
             vmax=1,
             origin="lower")

ax[1].imshow(added_source,
             vmax=1,
             origin="lower")

circle1 = patches.Circle((2000, 2000), 75,
                         fill=False,
                         edgecolor='red',
                         linewidth=1)

circle2 = patches.Circle((2000, 2000), 75,
                         fill=False,
                         edgecolor='red',
                         linewidth=1)

ax[0].add_patch(circle1)
ax[1].add_patch(circle2)

for subplot in ax.ravel():

    subplot.set_xlabel("X [pixels]")
    subplot.set_ylabel("Y [pixels]")

    subplot.set_xlim(1500, 2500)
    subplot.set_ylim(1500, 2500)

ax[0].set_title("Before adding PSF")
ax[1].set_title("After adding PSF")

plt.tight_layout()
plt.show()

Note that the default PSF_UPSCALE is 4.

Result of Adding an Artificial Source Tutorial 

 

Comparison of the astronomical scene before and after adding the PSF as described in Tutorial #4. 





For additional questions not answered in this article, please contact the Roman Help Desk at STScI.




References

In addition to this documentation, STIPS is described in the following references. Users are encouraged to cite this publication:

  1. STIPS Development Team et al 2024, "STIPS: The Nancy Grace Roman Space Telescope Imaging Product Simulator", PASP 136 124502





Latest Update

This documentation is written for STIPS version 2.2.2 (released on December 16, 2024).

Publication

 

Initial publication of the article.