Skip to content

Commit

Permalink
JP-3567: SIP coefficient accuracy and consistency (spacetelescope#8529)
Browse files Browse the repository at this point in the history
  • Loading branch information
melanieclarke authored Jun 8, 2024
1 parent 4fcb915 commit d6435f1
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 57 deletions.
8 changes: 7 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ assign_wcs
data file to properly handle background and virtual slits, and assign appropriate
meta data to them for use downstream. [#8442, #8533]

- Update default parameters to increase the accuracy of the SIP approximation
in the output FITS WCS. [#8529]

associations
------------

Expand Down Expand Up @@ -252,7 +255,10 @@ tweakreg
- Improve error handling in the absolute alignment. [#8450, #8477]

- Change code default to use IRAF StarFinder instead of
DAO StarFinder [#8487]
DAO StarFinder. [#8487]

- Add new step parameters to control SIP approximation in the output FITS WCS,
matching the default values used in the ``assign_wcs`` step. [#8529]

- Added a check for ``(abs_)separation`` and ``(abs_)tolerance`` parameters
that ``separation`` > ``sqrt(2) * tolerance`` that will now log an error
Expand Down
4 changes: 2 additions & 2 deletions docs/jwst/assign_wcs/arguments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ the behavior of the processing.
``--sip_degree`` (integer, max=6, default=None)
Polynomial degree for the forward SIP fit. "None" uses the best fit.

``--sip_max_pix_error`` (float, default=0.1)
``--sip_max_pix_error`` (float, default=0.01)
Maximum error for the SIP forward fit, in units of pixels. Ignored if
``sip_degree`` is set to an explicit value.

``--sip_inv_degree`` (integer, max=6, default=None)
Polynomial degree for the inverse SIP fit. "None" uses the best fit.

``--sip_max_inv_pix_error`` (float, default=0.1)
``--sip_max_inv_pix_error`` (float, default=0.01)
Maximum error for the SIP inverse fit, in units of pixels. Ignored if
``sip_inv_degree`` is set to an explicit value.

Expand Down
8 changes: 5 additions & 3 deletions docs/jwst/assign_wcs/main.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ create and populate the WCS object for the exposure.

For image display with software like DS9 that relies on specific WCS information, a SIP-based
approximation to the WCS is fit. The results are FITS keywords stored in
``model.meta.wcsinfo``. This is not an exact fit, but is accurate to ~0.25 pixel and is sufficient
for display purposes. This step, which occurs for imaging modes, is performed by default, but
can be switched off, and parameters controlling the SIP fit can also be adjusted.
``model.meta.wcsinfo``. This is not an exact fit, but is accurate to ~0.01 pixel by default,
and is sufficient for display purposes. This step, which occurs for imaging modes, is
performed by default, but can be switched off, and parameters controlling the SIP fit can
also be adjusted. Note that if these parameters are changed, the equivalent parameters
for the ``tweakreg`` step should be adjusted to match.

The ``assign_wcs`` step can accept either a ``rate`` product, which is the result of averaging
over all integrations in an exposure, or a ``rateints`` product, which is a 3D cube of
Expand Down
28 changes: 27 additions & 1 deletion docs/jwst/tweakreg/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,40 @@ Parameters used for absolute astrometry to a reference catalog.
that apply to ``fitgeometry`` also apply to ``abs_fitgeometry``.

* ``abs_nclip``: A non-negative integer number of clipping iterations
to use in the fit. (Default = 3)
to use in the fit. (Default=3)

* ``abs_sigma``: A positive `float` indicating the clipping limit, in sigma
units, used when performing fit. (Default=3.0)

* ``save_abs_catalog``: A boolean specifying whether or not to write out the
astrometric catalog used for the fit as a separate product. (Default=False)

**SIP approximation parameters:**

Parameters used to provide a SIP-based approximation to the WCS,
for FITS display. These parameter values should match the ones used
in the ``assign_wcs`` step.

* ``sip_approx``: A boolean flag to enable the computation of a SIP
approximation. (Default=True)

* ``sip_degree``: A positive `int`, specifying the polynomial degree for
the forward SIP fit. `None` uses the best fit; the maximum value allowed
is 6. (Default=None)

* ``sip_max_pix_error``: A positive `float`, specifying the maximum
error for the SIP forward fit, in units of pixels. Ignored if
``sip_degree`` is set to an explicit value. (Default=0.01)

* ``sip_inv_degree``: A positive `int`, specifying the polynomial degree for
the inverse SIP fit. `None` uses the best fit; the maximum value allowed
is 6. (Default=None)

* ``sip_max_inv_pix_error``: A positive `float`, specifying the maximum
error for the SIP inverse fit, in units of pixels. Ignored if
``sip_inv_degree`` is set to an explicit value. (Default=0.01)

* ``sip_npoints``: Number of points for the SIP fit. (Default=12).

Further Documentation
---------------------
Expand Down
6 changes: 4 additions & 2 deletions jwst/assign_wcs/assign_wcs_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ class AssignWcsStep(Step):

spec = """
sip_approx = boolean(default=True) # enables SIP approximation for imaging modes.
sip_max_pix_error = float(default=0.1) # max err for SIP fit, forward.
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.1) # max err for SIP fit, inverse.
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.
Expand Down Expand Up @@ -131,6 +131,8 @@ def process(self, input, *args, **kwargs):
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:
Expand Down
6 changes: 4 additions & 2 deletions jwst/assign_wcs/tests/test_wcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ def test_sip_approx(tmp_path):
im = ImageModel(hdu1)

pipe = AssignWcsStep()
result = pipe.call(im)
result = pipe.call(im, sip_max_pix_error=0.1, sip_degree=3,
sip_max_inv_pix_error=0.1, sip_inv_degree=3,
sip_npoints=12)

# check that result.meta.wcsinfo has correct
# values after SIP approx.
Expand Down Expand Up @@ -278,4 +280,4 @@ def test_sip_approx(tmp_path):
result.write(path)

with open(path) as result_read:
result.meta.wcsinfo == result_read.meta.wcsinfo
assert result.meta.wcsinfo == result_read.meta.wcsinfo
55 changes: 25 additions & 30 deletions jwst/assign_wcs/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,8 +1254,10 @@ def in_ifu_slice(slice_wcs, ra, dec, lam):
return onslice_ind


def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
crpix=None, projection='TAN', imwcs=None, **kwargs):
def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None,
max_inv_pix_error=0.01, inv_degree=None,
npoints=12, crpix=None, projection='TAN',
imwcs=None, **kwargs):
"""
Update ``datamodel.meta.wcsinfo`` based on a FITS WCS + SIP approximation
of a GWCS object. By default, this function will approximate
Expand Down Expand Up @@ -1307,6 +1309,23 @@ def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
to be fit to the WCS transformation. In this case
``max_pixel_error`` is ignored.
max_inv_pix_error : float, None, optional
Maximum allowed inverse error over the domain of the pixel array
in pixel units. With the default value of `None` no inverse
is generated.
inv_degree : int, iterable, None, optional
Degree of the SIP polynomial. Default value `None` indicates that
all allowed degree values (``[1...6]``) will be considered and
the lowest degree that meets accuracy requerements set by
``max_pix_error`` will be returned. Alternatively, ``degree`` can be
an iterable containing allowed values for the SIP polynomial degree.
This option is similar to default `None` but it allows caller to
restrict the range of allowed SIP degrees used for fitting.
Finally, ``degree`` can be an integer indicating the exact SIP degree
to be fit to the WCS transformation. In this case
``max_inv_pixel_error`` is ignored.
npoints : int, optional
The number of points in each dimension to sample the bounding box
for use in the SIP fit. Minimum number of points is 3.
Expand Down Expand Up @@ -1346,24 +1365,6 @@ def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
Other Parameters
----------------
max_inv_pix_error : float, None, optional
Maximum allowed inverse error over the domain of the pixel array
in pixel units. With the default value of `None` no inverse
is generated.
inv_degree : int, iterable, None, optional
Degree of the SIP polynomial. Default value `None` indicates that
all allowed degree values (``[1...6]``) will be considered and
the lowest degree that meets accuracy requerements set by
``max_pix_error`` will be returned. Alternatively, ``degree`` can be
an iterable containing allowed values for the SIP polynomial degree.
This option is similar to default `None` but it allows caller to
restrict the range of allowed SIP degrees used for fitting.
Finally, ``degree`` can be an integer indicating the exact SIP degree
to be fit to the WCS transformation. In this case
``max_inv_pixel_error`` is ignored.
bounding_box : tuple, None, optional
A pair of tuples, each consisting of two numbers
Represents the range of pixel values in both dimensions
Expand All @@ -1372,20 +1373,17 @@ def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
verbose : bool, optional
Print progress of fits.
Returns
-------
FITS header with all SIP WCS keywords
Raises
------
ValueError
If the WCS is not at least 2D, an exception will be raised. If the
specified accuracy (both forward and inverse, both rms and maximum)
is not achieved an exception will be raised.
Notes
-----
Expand All @@ -1409,15 +1407,11 @@ def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
# make a copy of kwargs:
kwargs = {k: v for k, v in kwargs.items()}

# override default values for "other parameters":
max_inv_pix_error = kwargs.pop('max_inv_pix_error', None)
inv_degree = kwargs.pop('inv_degree', None)
if inv_degree is None:
inv_degree = range(1, _MAX_SIP_DEGREE)

# limit default 'degree' range to _MAX_SIP_DEGREE:
# limit default 'degree' ranges to _MAX_SIP_DEGREE:
if degree is None:
degree = range(1, _MAX_SIP_DEGREE)
if inv_degree is None:
inv_degree = range(1, _MAX_SIP_DEGREE)

hdr = imwcs.to_fits_sip(
max_pix_error=max_pix_error,
Expand All @@ -1426,6 +1420,7 @@ def update_fits_wcsinfo(datamodel, max_pix_error=0.01, degree=None, npoints=32,
inv_degree=inv_degree,
npoints=npoints,
crpix=crpix,
projection=projection,
**kwargs
)

Expand Down
68 changes: 63 additions & 5 deletions jwst/tweakreg/tests/test_tweakreg.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
from copy import deepcopy
import json
import os
from copy import deepcopy

import asdf
from astropy.modeling.models import Shift
from astropy.table import Table
import numpy as np
import pytest
from astropy.wcs import WCS
from astropy.modeling.models import Shift
from astropy.table import Table
from gwcs.wcstools import grid_from_bounding_box
from stdatamodels.jwst.datamodels import ImageModel

from jwst.datamodels import ModelContainer
from jwst.tweakreg import tweakreg_step
from jwst.tweakreg import tweakreg_catalog
from jwst.tweakreg.utils import _wcsinfo_from_wcs_transform
from stdatamodels.jwst.datamodels import ImageModel
from jwst.datamodels import ModelContainer


BKG_LEVEL = 0.001
Expand Down Expand Up @@ -340,3 +342,59 @@ def patched_construct_wcs_corrector(model, catalog, _seen=[]):

with pytest.raises(ValueError, match="done testing"):
step(str(asn_path))

@pytest.mark.parametrize("with_shift", [True, False])
def test_sip_approx(example_input, with_shift):
"""
Test the output FITS WCS.
"""
if with_shift:
# shift 9 pixels so that the sources in one of the 2 images
# appear at different locations (resulting in a correct wcs update)
example_input[1].data[:-9] = example_input[1].data[9:]
example_input[1].data[-9:] = BKG_LEVEL

# assign images to different groups (so they are aligned to each other)
example_input[0].meta.group_id = 'a'
example_input[1].meta.group_id = 'b'

# call th step with override SIP approximation parameters
step = tweakreg_step.TweakRegStep()
step.sip_approx = True
step.sip_max_pix_error = 0.1
step.sip_degree = 3
step.sip_max_inv_pix_error = 0.1
step.sip_inv_degree = 3
step.sip_npoints = 12

# run the step on the example input modified above
result = step(example_input)

# output wcs differs by a small amount due to the shift above:
# project one point through each wcs and compare the difference
abs_delta = abs(result[1].meta.wcs(0, 0)[0] - result[0].meta.wcs(0, 0)[0])
if with_shift:
assert abs_delta > 1E-5
else:
assert abs_delta < 1E-12

# the first wcs is identical to the input and
# does not have SIP approximation keywords --
# they are normally set by assign_wcs
assert np.allclose(result[0].meta.wcs(0, 0)[0], example_input[0].meta.wcs(0, 0)[0])
for key in ['ap_order', 'bp_order']:
assert key not in result[0].meta.wcsinfo.instance

# for the second, SIP approximation should be present
for key in ['ap_order', 'bp_order']:
assert result[1].meta.wcsinfo.instance[key] == 3

# evaluate fits wcs and gwcs for the approximation, make sure they agree
wcs_info = result[1].meta.wcsinfo.instance
grid = grid_from_bounding_box(result[1].meta.wcs.bounding_box)
gwcs_ra, gwcs_dec = result[1].meta.wcs(*grid)
fits_wcs = WCS(wcs_info)
fitswcs_res = fits_wcs.pixel_to_world(*grid)

assert np.allclose(fitswcs_res.ra.deg, gwcs_ra)
assert np.allclose(fitswcs_res.dec.deg, gwcs_dec)
35 changes: 24 additions & 11 deletions jwst/tweakreg/tweakreg_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ class TweakRegStep(Step):
abs_separation = float(default=1) # Minimum object separation in arcsec when performing absolute astrometry
abs_tolerance = float(default=0.7) # Matching tolerance for xyxymatch in arcsec when performing absolute astrometry
# SIP approximation options, should match assign_wcs
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
# stpipe general options
output_use_model = boolean(default=True) # When saving use `DataModel.meta.filename`
"""
Expand Down Expand Up @@ -508,17 +516,22 @@ def process(self, input):

# Also update FITS representation in input exposures for
# subsequent reprocessing by the end-user.
try:
update_fits_wcsinfo(
image_model,
max_pix_error=0.01,
npoints=16
)
except (ValueError, RuntimeError) as e:
self.log.warning(
"Failed to update 'meta.wcsinfo' with FITS SIP "
f'approximation. Reported error is:\n"{e.args[0]}"'
)
if self.sip_approx:
try:
update_fits_wcsinfo(
image_model,
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:
self.log.warning(
"Failed to update 'meta.wcsinfo' with FITS SIP "
f'approximation. Reported error is:\n"{e.args[0]}"'
)

return images

Expand Down

0 comments on commit d6435f1

Please sign in to comment.