#! /usr/bin/env python
import logging
from stdatamodels.jwst import datamodels
from jwst.assign_wcs.assign_wcs import load_wcs
from jwst.assign_wcs.nircam import imaging as nircam_imaging
from jwst.assign_wcs.niriss import imaging as niriss_imaging
from jwst.assign_wcs.util import (
MSAFileError,
update_fits_wcsinfo,
wcs_bbox_from_shape,
wfss_imaging_wcs,
)
from jwst.lib.exposure_types import IMAGING_TYPES
from jwst.stpipe import Step
log = logging.getLogger(__name__)
__all__ = ["AssignWcsStep"]
WFSS_TYPES = {"nrc_wfss", "nis_wfss"}
[docs]
class AssignWcsStep(Step):
"""Create a gWCS object and store it in ``Model.meta.wcs``."""
class_alias = "assign_wcs"
spec = """
sip_approx = boolean(default=True) # enables SIP approximation for imaging modes.
sip_max_pix_error = float(default=0.01) # max err for SIP fit, forward.
sip_degree = integer(max=6, default=None) # degree for forward SIP fit, None to use best fit.
sip_max_inv_pix_error = float(default=0.01) # max err for SIP fit, inverse.
sip_inv_degree = integer(max=6, default=None) # degree for inverse SIP fit, None to use best fit.
sip_npoints = integer(default=12) # number of points for SIP
slit_y_low = float(default=-.55) # The lower edge of a slit (NIRSpec only).
slit_y_high = float(default=.55) # The upper edge of a slit (NIRSpec only).
nrs_ifu_slice_wcs = boolean(default=False) # For NIRSpec IFU, create a full slice-based WCS instead of a top-level coordinate-based WCS. Used for diagnostic purposes only.
""" # noqa: E501
reference_file_types = [
"distortion",
"filteroffset",
"specwcs",
"regions",
"wavelengthrange",
"camera",
"collimator",
"disperser",
"fore",
"fpa",
"msa",
"ote",
"ifupost",
"ifufore",
"ifuslicer",
]
[docs]
def process(self, input_data):
"""
Run the assign_wcs step.
Reference file types for this step:
- camera: Camera model (NIRSPEC)
- collimator: Collimator Model (NIRSPEC)
- disperser: Disperser model (NIRSPEC)
- distortion: Spatial distortion model (FGS, MIRI, NIRCAM, NIRISS)
- filteroffset: Filter offsets (MIRI Imager)
- fore: Transform through the FORE optics (NIRSPEC)
- fpa: Transform in the FPA plane (NIRSPEC)
- ifufore: Transforms from the MSA plane to the plane of the IFU slicer (NIRSPEC)
- ifupost: Transforms from the slicer plane to the MSA plane (NIRSPEC)
- ifuslicer: Metrology of the IFU slicer (NIRSPEC)
- msa: Metrology of the MSA plane (NIRSPEC)
- ote: Transform through the Optical Telescope Element (NIRSPEC)
- specwcs: Wavelength calibration models (MIRI, NIRCAM, NIRISS)
- regions: Stores location of the regions on the detector (MIRI)
- wavelengthrange: Typical wavelength ranges (MIRI, NIRCAM, NIRISS, NIRSPEC)
Parameters
----------
input_data : JwstDataModel or str
Either a jwst data model or a string that is the path to one.
Returns
-------
result : JwstDataModel
The data model with the WCS information added.
"""
reference_file_names = {}
with datamodels.open(input_data) as input_model:
# If input type is not supported, log warning, set to 'skipped', exit
if not (
isinstance(input_model, datamodels.ImageModel)
or isinstance(input_model, datamodels.CubeModel)
or isinstance(input_model, datamodels.IFUImageModel)
):
log.warning("Input dataset type is not supported.")
log.warning("assign_wcs expects ImageModel, IFUImageModel or CubeModel as input.")
log.warning("Skipping assign_wcs step.")
result = input_model.copy()
result.meta.cal_step.assign_wcs = "SKIPPED"
return result
for reftype in self.reference_file_types:
reffile = self.get_reference_file(input_model, reftype)
reference_file_names[reftype] = reffile if reffile else ""
log.debug(f"reference files used in assign_wcs: {reference_file_names}")
# Get the MSA metadata file if needed and add to reffiles
if input_model.meta.exposure.type == "NRS_MSASPEC":
msa_metadata_file = input_model.meta.instrument.msa_metadata_file
if msa_metadata_file is not None and msa_metadata_file.strip() not in ["", "N/A"]:
msa_metadata_file = self.make_input_path(msa_metadata_file)
reference_file_names["msametafile"] = msa_metadata_file
else:
message = "MSA metadata file (MSAMETFL) is required for NRS_MSASPEC exposures."
log.error(message)
raise MSAFileError(message)
slit_y_range = [self.slit_y_low, self.slit_y_high]
result = load_wcs(
input_model,
reference_file_names,
slit_y_range,
nrs_ifu_slice_wcs=self.nrs_ifu_slice_wcs,
)
if not (
result.meta.exposure.type.lower() in (IMAGING_TYPES.union(WFSS_TYPES))
and self.sip_approx
):
return result
result_exptype = result.meta.exposure.type.lower()
# fit sip approx., degree is chosen by best fit
if result_exptype in IMAGING_TYPES:
try:
update_fits_wcsinfo(
result,
max_pix_error=self.sip_max_pix_error,
degree=self.sip_degree,
max_inv_pix_error=self.sip_max_inv_pix_error,
inv_degree=self.sip_inv_degree,
npoints=self.sip_npoints,
crpix=None,
)
except (ValueError, RuntimeError) as e:
log.warning(
"Failed to update 'meta.wcsinfo' with FITS SIP "
"approximation. Reported error is:"
)
log.warning(f'"{e.args[0]}"')
else: # WFSS modes
try:
# A bounding_box is needed for the imaging WCS
bbox = wcs_bbox_from_shape(result.data.shape)
if result_exptype == "nis_wfss":
imaging_func = niriss_imaging
else:
imaging_func = nircam_imaging
wfss_imaging_wcs(
result,
imaging_func,
bbox=bbox,
max_pix_error=self.sip_max_pix_error,
degree=self.sip_degree,
max_inv_pix_error=self.sip_max_inv_pix_error,
inv_degree=self.sip_inv_degree,
npoints=self.sip_npoints,
)
except (ValueError, RuntimeError) as e:
log.warning(
"Failed to update 'meta.wcsinfo' with FITS SIP "
"approximation. Reported error is:"
)
log.warning(f'"{e.args[0]}"')
return result