Making WFI Mosaics with RomanCal#


Server Information#

IMPORTANT: To run this tutorial, please make sure you are logged in the RRN with a medium server.

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.

Imports#

Libraries used

  • romancal for creating association files and running the pipeline

  • astropy.visualization for creating image normalizations

  • roman_datamodels for opening Roman WFI ASDF files

  • glob for creating lists of files

  • os for checking if files exist

  • json for creating association files

  • s3fs for streaming files from an S3 bucket

import roman_datamodels as rdm
from romancal.pipeline import MosaicPipeline
from romancal.associations import asn_from_list
from astropy.visualization import simple_norm
import s3fs
import glob
import os
import json
import matplotlib.pyplot as plt

Introduction#

The purpose of this notebook is to combine multiple Level 2 (L2; calibrated rate image) data products into a Level 3 (L3; distortion-corrected, co-added image) product. Details about the Roman data levels can be found here. The pipeline for combining images is called the Mosaic Pipeline. Please review the RDox pages for details. It consists of the following steps:

  1. Flux step: Transform instrumental units of data numbers per second (DN/s) into physical surface brightness units of megaJanskys per steradian (MJy/sr).

  2. Skymatch step: Determine scaling factors necessary to match the flux levels in the overlapping sections of the input images. The images will be scaled by these factors later during the combination process.

  3. Outlier detection step: Examine the input images to detect and flag outliers in the pixel values for a given sky position.

  4. Resample step: Use the drizzle algorithm to combine the input data products. Reject outliers and other undesirable pixels based on data quality flags and oversample the pixels if indicated.

Note: L3 data products in the Roman Archive will be tessellated on the sky such that individual L3 files (“sky cells”) will be small with some overlap between adjacent cells. More information is available on the RDox pages on sky tessellation. In this tutorial, we will not utilize tessellation. The tutorial will be updated in the future to demonstrate the creation of both tessellated and non-tessellated products.


Tutorial Data#

In this tutorial, we use L2 WFI data files produced by RomanCal processing of L1 simulated files created with Roman I-Sim. If you have already worked through the tutorials Simulating WFI Imaging Data with Roman I-Sim and Calibrating WFI Exposures with RomanCal, you may already have these files saved locally. If not, these files are also available in the Nexus S3 bucket. For more information on how to access these data, see the Data Discovery and Access tutorial.

The L2 products used in this tutorial represent data from one WFI detector (WFI11) across four different pointings, each with small dithers. These dithers are optimized for gap-filling between detectors rather than for oversampling. Therefore, in this tutorial we will generate a mosaicked L3 product using the native pixel scale.

Making WFI Mosaics#

Generating the Association File#

Association table files (often abbreviated “association files” or “ASN files”) are JavaScript Object Notation (JSON) formatted files containing a list of the input products to be processed together, and the output products to be created. In the Mosaic Pipeline, association files specifically enumerate the input L2 products that are used to create a single L3 product. For more information, please visit the RDox pages on association files.

The association generator function asn_from_list() can be used to create a properly formatted JavaScript Object Notation (JSON) file for use with the Mosaic Pipeline. The code takes as input a list of L2 products and the name of the L3 output product. You can include either one or many L2 products as input. For use cases that do use a single input file, you can skip several pipeline steps (skymatch and outlier_detection).

If you have not completed the previous Exposure Pipeline tutorial, then you can download the data products from the Nexus S3 bucket. The code cell below will check if you have the files saved on disk and, if not, it will retrieve them from the S3 bucket.

# Copy the files from the S3 bucket if they are not in local storage
input_files = ['r0003201001001001004_0001_wfi11_f106_cal.asdf',
               'r0003201001001001004_0002_wfi11_f106_cal.asdf',
               'r0003201001001001004_0003_wfi11_f106_cal.asdf',
               'r0003201001001001004_0004_wfi11_f106_cal.asdf']

for f in input_files:
    if not os.path.exists(f):
        fs = s3fs.S3FileSystem(anon=True)
        asdf_dir_uri = "s3://stpubdata/roman/nexus/soc_simulations/tutorial_data/roman-2026.1/"
        asdf_file_uri = asdf_dir_uri + f
        fs.get(asdf_file_uri, f'./{f}')

Next we can create the association JSON file using the asn_from_list() function:

# Collect our input files and make the L3 output filename
input_files = glob.glob('r0003*_????_wfi11_f106_cal.asdf')
product_name = 'my_roman_mosaic'

# Create the ASN JSON object
asn = asn_from_list.asn_from_list([(i, 'science') for i in input_files], 
                                  product_name=product_name, 
                                  with_exptype=True, target='')

Now let’s take a look at the JSON object we just created. It should contain a listing of each of our input files as well as the output product name:

asn
{
    "asn_type": "None",
    "asn_rule": "DMS_ELPP_Base",
    "version_id": null,
    "code_version": "0.22.0",
    "target": "",
    "degraded_status": "No known degraded exposures in association.",
    "program": "noprogram",
    "data_release_id": "p",
    "constraints": "No constraints",
    "asn_id": "a3001",
    "asn_pool": "none",
    "skycell_wcs_info": "none",
    "products": [
        {
            "name": "my_roman_mosaic",
            "members": [
                {
                    "expname": "r0003201001001001004_0001_wfi11_f106_cal.asdf",
                    "exptype": "science"
                },
                {
                    "expname": "r0003201001001001004_0002_wfi11_f106_cal.asdf",
                    "exptype": "science"
                },
                {
                    "expname": "r0003201001001001004_0003_wfi11_f106_cal.asdf",
                    "exptype": "science"
                },
                {
                    "expname": "r0003201001001001004_0004_wfi11_f106_cal.asdf",
                    "exptype": "science"
                }
            ]
        }
    ]
}

We can also save the ASN file to disk:

# Write the ASN file to disk
_, serialized = asn.dump(format='json')
with open('my_roman_mosaic_asn.json', 'w') as f:
    f.write(serialized)

Running the Mosaic Pipeline#

Next we run the Mosaic Pipeline using the MosaicPipeline class. As with the L1 to L2 Exposure Pipeline, there are many optional arguments to customize the behavior of the pipeline. At this time, we will not delve into these optional parameters. The simulated input files are based on a simple gap-filling dither pattern rather than sub-pixel dithering optimized for the point spread function(PSF). As a result, we use the default native sampling of the WFI detectors. In the future, additional details may be provided to explore optimizing the Mosaic Pipeline output.

If we had only a single input image, this is where we would skip the skymatch and outlier detection steps by passing the optional argument steps={'skymatch': {'skip': True}, 'outlier_detection': {'skip': True}}. For now, we will skip the source catalog step that would normally make the single-band source catalog and segmentation map, which are Level 4 (L4; high-level extracted information) files.

Note: The following cell will take several minutes to complete.

result = MosaicPipeline.call('my_roman_mosaic_asn.json', save_results=True, resample_on_skycell=False, 
                             steps={'source_catalog': {'skip': True}})
2026-06-01 19:35:13,211 - CRDS - ERROR -  Error determining best reference for 'pars-fluxstep'  =   No match found.
2026-06-01 19:35:13,212 - CRDS - ERROR -  Error determining best reference for 'pars-skymatchstep'  =   No match found.
2026-06-01 19:35:13,214 - CRDS - ERROR -  Error determining best reference for 'pars-outlierdetectionstep'  =   No match found.
2026-06-01 19:35:13,215 - CRDS - ERROR -  Error determining best reference for 'pars-resamplestep'  =   No match found.
2026-06-01 19:35:13,217 - CRDS - ERROR -  Error determining best reference for 'pars-sourcecatalogstep'  =   Unknown reference type 'pars-sourcecatalogstep'
2026-06-01 19:35:13,218 - CRDS - ERROR -  Error determining best reference for 'pars-mosaicpipeline'  =   Unknown reference type 'pars-mosaicpipeline'
2026-06-01 19:35:13,223 - stpipe.step - INFO - MosaicPipeline instance created.
2026-06-01 19:35:13,223 - stpipe.step - INFO - FluxStep instance created.
2026-06-01 19:35:13,225 - stpipe.step - INFO - SkyMatchStep instance created.
2026-06-01 19:35:13,226 - stpipe.step - INFO - OutlierDetectionStep instance created.
2026-06-01 19:35:13,228 - stpipe.step - INFO - ResampleStep instance created.
2026-06-01 19:35:13,229 - stpipe.step - INFO - SourceCatalogStep instance created.
2026-06-01 19:35:13,398 - stpipe.step - INFO - Step MosaicPipeline running with args ('my_roman_mosaic_asn.json',).
2026-06-01 19:35:13,407 - stpipe.step - INFO - Step MosaicPipeline parameters are:
  pre_hooks: []
  post_hooks: []
  output_file: None
  output_dir: None
  output_ext: .asdf
  output_use_model: False
  output_use_index: True
  save_results: True
  skip: False
  suffix: None
  search_output_file: True
  input_dir: ''
  update_version: False
  on_disk: False
  resample_on_skycell: False
  steps:
    flux:
      pre_hooks: []
      post_hooks: []
      output_file: None
      output_dir: None
      output_ext: .asdf
      output_use_model: False
      output_use_index: True
      save_results: False
      skip: False
      suffix: None
      search_output_file: True
      input_dir: ''
      update_version: False
    skymatch:
      pre_hooks: []
      post_hooks: []
      output_file: None
      output_dir: None
      output_ext: .asdf
      output_use_model: False
      output_use_index: True
      save_results: False
      skip: False
      suffix: None
      search_output_file: True
      input_dir: ''
      update_version: False
      skymethod: match
      match_down: True
      subtract: False
      stepsize: None
      skystat: mode
      dqbits: ~DO_NOT_USE+NON_SCIENCE
      lower: None
      upper: None
      nclip: 5
      lsigma: 4.0
      usigma: 4.0
      binwidth: 0.1
    outlier_detection:
      pre_hooks: []
      post_hooks: []
      output_file: None
      output_dir: None
      output_ext: .asdf
      output_use_model: False
      output_use_index: True
      save_results: False
      skip: False
      suffix: None
      search_output_file: True
      input_dir: ''
      update_version: False
      weight_type: ivm
      pixfrac: 1.0
      kernel: square
      fillval: NaN
      maskpt: 0.7
      snr: 5.0 4.0
      scale: 1.2 0.7
      backg: 0.0
      save_intermediate_results: False
      resample_data: True
      resample_on_skycell: True
      good_bits: ~DO_NOT_USE+NON_SCIENCE
      in_memory: True
      pixmap_stepsize: 10.0
      pixmap_order: 3
    resample:
      pre_hooks: []
      post_hooks: []
      output_file: None
      output_dir: None
      output_ext: .asdf
      output_use_model: False
      output_use_index: True
      save_results: False
      skip: False
      suffix: None
      search_output_file: True
      input_dir: ''
      update_version: False
      pixfrac: 1.0
      kernel: square
      fillval: NAN
      weight_type: ivm
      output_shape: None
      crpix: None
      crval: None
      rotation: None
      pixel_scale_ratio: 1.0
      pixel_scale: None
      output_wcs: ''
      resample_on_skycell: True
      in_memory: True
      good_bits: ~DO_NOT_USE+NON_SCIENCE
      include_var_flat: False
      propagate_dq: False
      pixmap_stepsize: 10.0
      pixmap_order: 3
    source_catalog:
      pre_hooks: []
      post_hooks: []
      output_file: None
      output_dir: None
      output_ext: .asdf
      output_use_model: False
      output_use_index: True
      save_results: False
      skip: True
      suffix: cat
      search_output_file: True
      input_dir: ''
      update_version: False
      bkg_boxsize: 1000
      kernel_fwhm: 2.0
      snr_threshold: 3.0
      npixels: 25
      deblend: False
      fit_psf: True
      forced_segmentation: ''
2026-06-01 19:35:13,407 - romancal.pipeline.mosaic_pipeline - INFO - Starting Roman mosaic level calibration pipeline ...
2026-06-01 19:35:13,606 - stpipe.step - INFO - Step flux running with args (<romancal.datamodels.library.ModelLibrary object at 0x7ffb7da0bed0>,).
2026-06-01 19:35:15,422 - stpipe.step - INFO - Step flux done
2026-06-01 19:35:15,593 - stpipe.step - INFO - Step skymatch running with args (<romancal.datamodels.library.ModelLibrary object at 0x7ffb7da0bed0>,).
2026-06-01 19:35:15,885 - stcal.skymatch.skymatch - INFO -  
2026-06-01 19:35:15,886 - stcal.skymatch.skymatch - INFO - ***** stcal.skymatch.skymatch.skymatch() started on 2026-06-01 19:35:15.885733
2026-06-01 19:35:15,887 - stcal.skymatch.skymatch - INFO -  
2026-06-01 19:35:15,887 - stcal.skymatch.skymatch - INFO - Sky computation method: 'match'
2026-06-01 19:35:15,887 - stcal.skymatch.skymatch - INFO - Sky matching direction: DOWN
2026-06-01 19:35:15,888 - stcal.skymatch.skymatch - INFO - Sky subtraction from image data: OFF
2026-06-01 19:35:15,888 - stcal.skymatch.skymatch - INFO -  
2026-06-01 19:35:15,890 - stcal.skymatch.skymatch - INFO - ----  Computing differences in sky values in overlapping regions.
2026-06-01 19:35:18,575 - stcal.skymatch.skymatch - INFO -    *  Image ID=r0003201001001001004_0001_wfi11_f106_cal.asdf. Sky background: 0.00340952
2026-06-01 19:35:18,575 - stcal.skymatch.skymatch - INFO -    *  Image ID=r0003201001001001004_0002_wfi11_f106_cal.asdf. Sky background: 0
2026-06-01 19:35:18,576 - stcal.skymatch.skymatch - INFO -    *  Image ID=r0003201001001001004_0003_wfi11_f106_cal.asdf. Sky background: 0.00231594
2026-06-01 19:35:18,576 - stcal.skymatch.skymatch - INFO -    *  Image ID=r0003201001001001004_0004_wfi11_f106_cal.asdf. Sky background: 0.00439232
2026-06-01 19:35:18,577 - stcal.skymatch.skymatch - INFO -  
2026-06-01 19:35:18,577 - stcal.skymatch.skymatch - INFO - ***** stcal.skymatch.skymatch.skymatch() ended on 2026-06-01 19:35:18.577092
2026-06-01 19:35:18,578 - stcal.skymatch.skymatch - INFO - ***** stcal.skymatch.skymatch.skymatch() TOTAL RUN TIME: 0:00:02.691359
2026-06-01 19:35:18,578 - stcal.skymatch.skymatch - INFO -  
2026-06-01 19:35:18,582 - stpipe.step - INFO - Step skymatch done
2026-06-01 19:35:18,759 - stpipe.step - INFO - Step outlier_detection running with args (<romancal.datamodels.library.ModelLibrary object at 0x7ffb7da0bed0>,).
2026-06-01 19:35:18,796 - stcal.resample.resample - INFO - Output pixel scale: 0.10772630117450682 arcsec.
2026-06-01 19:35:18,796 - stcal.resample.resample - INFO - Driz parameter kernel: square
2026-06-01 19:35:18,797 - stcal.resample.resample - INFO - Driz parameter pixfrac: 1.0
2026-06-01 19:35:18,797 - stcal.resample.resample - INFO - Driz parameter fillval: NaN
2026-06-01 19:35:18,798 - stcal.resample.resample - INFO - Driz parameter weight_type: ivm
2026-06-01 19:35:18,798 - stcal.resample.resample - INFO - Evaluating the full pixel map every 10.0 pixels
2026-06-01 19:35:18,798 - stcal.resample.resample - INFO - Interpolating the rest with order 3 bivariate splines
2026-06-01 19:35:19,463 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:35:22,313 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:35:25,171 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:35:27,997 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:35:30,173 - stcal.alignment.util - INFO - Update S_REGION to POLYGON ICRS  271.070347920 -0.419189523 271.070346866 -0.222320204 270.869256760 -0.222319306 270.869253946 -0.419188624
2026-06-01 19:35:57,378 - stcal.outlier_detection.utils - INFO - Blotting (4088, 4088) <-- (6579, 6720)
2026-06-01 19:35:58,487 - romancal.outlier_detection.utils - INFO - 3598 pixels marked as outliers
2026-06-01 19:35:59,073 - stcal.outlier_detection.utils - INFO - Blotting (4088, 4088) <-- (6579, 6720)
2026-06-01 19:36:00,205 - romancal.outlier_detection.utils - INFO - 3799 pixels marked as outliers
2026-06-01 19:36:00,784 - stcal.outlier_detection.utils - INFO - Blotting (4088, 4088) <-- (6579, 6720)
2026-06-01 19:36:01,910 - romancal.outlier_detection.utils - INFO - 3629 pixels marked as outliers
2026-06-01 19:36:02,489 - stcal.outlier_detection.utils - INFO - Blotting (4088, 4088) <-- (6579, 6720)
2026-06-01 19:36:03,609 - romancal.outlier_detection.utils - INFO - 3237 pixels marked as outliers
2026-06-01 19:36:03,616 - stpipe.step - INFO - Step outlier_detection done
2026-06-01 19:36:03,800 - stpipe.step - INFO - Step resample running with args (<romancal.datamodels.library.ModelLibrary object at 0x7ffb7da0bed0>,).
2026-06-01 19:36:03,837 - stcal.resample.resample - INFO - Output pixel scale: 0.10772630117450682 arcsec.
2026-06-01 19:36:03,837 - stcal.resample.resample - INFO - Driz parameter kernel: square
2026-06-01 19:36:03,838 - stcal.resample.resample - INFO - Driz parameter pixfrac: 1.0
2026-06-01 19:36:03,838 - stcal.resample.resample - INFO - Driz parameter fillval: NAN
2026-06-01 19:36:03,838 - stcal.resample.resample - INFO - Driz parameter weight_type: ivm
2026-06-01 19:36:03,839 - stcal.resample.resample - INFO - Evaluating the full pixel map every 10.0 pixels
2026-06-01 19:36:03,839 - stcal.resample.resample - INFO - Interpolating the rest with order 3 bivariate splines
2026-06-01 19:36:04,547 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:36:09,071 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:09,084 - py.warnings - WARNING - /home/runner/micromamba/envs/ci-env/lib/python3.13/site-packages/stcal/resample/resample.py:1259: RuntimeWarning: invalid value encountered in sqrt
  data=np.sqrt(variance),
2026-06-01 19:36:11,287 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:15,102 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:36:19,576 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:19,589 - py.warnings - WARNING - /home/runner/micromamba/envs/ci-env/lib/python3.13/site-packages/stcal/resample/resample.py:1259: RuntimeWarning: invalid value encountered in sqrt
  data=np.sqrt(variance),
2026-06-01 19:36:21,882 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:25,751 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:36:30,203 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:30,216 - py.warnings - WARNING - /home/runner/micromamba/envs/ci-env/lib/python3.13/site-packages/stcal/resample/resample.py:1259: RuntimeWarning: invalid value encountered in sqrt
  data=np.sqrt(variance),
2026-06-01 19:36:32,429 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:36,346 - stcal.resample.resample - INFO - Resampling science and variance data
2026-06-01 19:36:40,787 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:40,800 - py.warnings - WARNING - /home/runner/micromamba/envs/ci-env/lib/python3.13/site-packages/stcal/resample/resample.py:1259: RuntimeWarning: invalid value encountered in sqrt
  data=np.sqrt(variance),
2026-06-01 19:36:43,001 - stcal.resample.resample - INFO - Drizzling (4088, 4088) --> (6579, 6720)
2026-06-01 19:36:46,673 - romancal.resample.resample - INFO - Mean, max exposure times: 116.8, 265.6
2026-06-01 19:36:46,701 - stcal.alignment.util - INFO - Update S_REGION to POLYGON ICRS  271.070347920 -0.419189523 271.070346866 -0.222320204 270.869256760 -0.222319306 270.869253946 -0.419188624
2026-06-01 19:36:46,713 - stpipe.step - INFO - Step resample done
2026-06-01 19:36:46,899 - stpipe.step - INFO - Step source_catalog running with args (<roman_datamodels.datamodels._datamodels.MosaicModel object at 0x7ffb7d58ed90>,).
2026-06-01 19:36:46,900 - stpipe.step - INFO - Step skipped.
2026-06-01 19:36:49,759 - stpipe.step - INFO - Saved model in my_roman_mosaic_coadd.asdf
2026-06-01 19:36:49,760 - stpipe.step - INFO - Step MosaicPipeline done

Now let’s open the mosaic file we’ve created and take a look at what’s inside:

mos_file = rdm.open('my_roman_mosaic_coadd.asdf')
mos_file.info()
root (AsdfObject)
├─asdf_library (Software)
│ ├─author (str): The ASDF Developers
│ ├─homepage (str): http://github.com/asdf-format/asdf
│ ├─name (str): asdf
│ └─version (str): 5.3.0
├─history (AsdfDictNode)
│ └─extensions (AsdfListNode)
│   ├─0 (ExtensionMetadata) ...
│   ├─1 (ExtensionMetadata) ...
│   ├─2 (ExtensionMetadata) ...
│   ├─3 (ExtensionMetadata) ...
│   ├─4 (ExtensionMetadata) ...
│   └─3 not shown
└─roman (WfiMosaic) # The schema for WFI Level 3 mosaics.
  ├─meta (AsdfDictNode) # Common Level 3 Metadata ...
  ├─data (NDArrayType) # Science Data (MJy / steradian) ...
  ├─context (NDArrayType) # Context Data ...
  ├─err (NDArrayType) # Error Data (MJy / steradian) ...
  ├─weight (NDArrayType) # Weight Data ...
  ├─var_poisson (NDArrayType) # Poisson Variability (MJy^2 / steradian^2) ...
  ├─var_rnoise (NDArrayType) # Read Noise Variance (MJy^2 / steradian^2 ...
  └─individual_image_cal_logs (AsdfListNode) ...
Some nodes not shown.
norm = simple_norm(mos_file.data, 'asinh', vmin=0.01, vmax=3.5)
fig, ax = plt.subplots(figsize=(20, 15))
ax.imshow(mos_file.data, origin='lower', norm=norm)
<matplotlib.image.AxesImage at 0x7ffb7c116900>
../../_images/dcbfd2f0b342f9e8d0084a17a263107d78b38f9011d12c17d9cfbd76f8ea62b0.png

An L3 file contains several extensions that may be of interest:

  • data: The mosiac image in units of MegaJanskys per steradian

  • context: An image showing the input images that contributed to each output pixel

  • err: The error image

  • weight: A weight map showing the relative coverage on the sky from the input images and how they contributed to the final mosaic

For more information on the how arrays are computed by the drizzle algorithm, we suggest consulting the DrizzlePac Handbook. While the DrizzlePac Handbook is written for HST, many of the same terms and principles apply to Roman as the underlying algorithm is the same.

Additional Resources#


About this Notebook#

Author: Tyler Desjardins
Updated On: 2026-04-17

Top of page roman_logo