diff --git a/changes/1585.mosaic_pipeline.rst b/changes/1585.mosaic_pipeline.rst new file mode 100644 index 000000000..977888ff6 --- /dev/null +++ b/changes/1585.mosaic_pipeline.rst @@ -0,0 +1 @@ +Roundtrip L3 wcsinfo especially when skycell specifications are used diff --git a/romancal/pipeline/mosaic_pipeline.py b/romancal/pipeline/mosaic_pipeline.py index 7294ef3dd..fb5f2315f 100644 --- a/romancal/pipeline/mosaic_pipeline.py +++ b/romancal/pipeline/mosaic_pipeline.py @@ -13,6 +13,7 @@ from astropy import units as u from astropy.modeling import models from gwcs import WCS, coordinate_frames +from stcal.alignment import util as wcs_util import romancal.datamodels.filetype as filetype from romancal.datamodels import ModelLibrary @@ -114,13 +115,13 @@ def process(self, input): # check to see if there exists a skycell on disk if not create it if not isfile(skycell_file_name): - # extract the wcs info from the record for generate_tan_wcs + # extract the wcs info from the record for skycell_to_wcs log.info( "Creating skycell image at ra: %f dec %f", float(skycell_record["ra_center"]), float(skycell_record["dec_center"]), ) - skycell_wcs = generate_tan_wcs(skycell_record) + skycell_wcs = skycell_to_wcs(skycell_record) # skycell_wcs.bounding_box = bounding_box # For resample to use an external grid we need to pass it the skycell gwcs object @@ -128,6 +129,7 @@ def process(self, input): wcs_tree = {"wcs": skycell_wcs} wcs_file = asdf.AsdfFile(wcs_tree) wcs_file.write_to("skycell_wcs.asdf") + self.resample.output_wcs = "skycell_wcs.asdf" self.resample.output_shape = ( int(skycell_record["nx"]), @@ -162,41 +164,96 @@ def process(self, input): return result -def generate_tan_wcs(skycell_record): - """extract the wcs info from the record for generate_tan_wcs - we need the scale, ra, dec, bounding_box""" +def skycell_to_wcs(skycell_record): + """From a skycell record, generate a GWCS + + Parameters + ---------- + skycell_record : dict + A skycell record, or row, from the skycell patches table. + + Returns + ------- + wcsobj : wcs.GWCS + The GWCS object from the skycell record. + """ + wcsinfo = dict() + + # The scale is given in arcseconds per pixel. Convert to degrees. + wcsinfo["pixel_scale"] = float(skycell_record["pixel_scale"]) / 3600.0 + + # Remaining components of the wcsinfo block + wcsinfo["ra_ref"] = float(skycell_record["ra_projection_center"]) + wcsinfo["dec_ref"] = float(skycell_record["dec_projection_center"]) + wcsinfo["x_ref"] = float(skycell_record["x0_projection"]) + wcsinfo["y_ref"] = float(skycell_record["y0_projection"]) + wcsinfo["orientat"] = float(skycell_record["orientat_projection_center"]) + wcsinfo["rotation_matrix"] = None - scale = float(skycell_record["pixel_scale"]) - ra_center = float(skycell_record["ra_projection_center"]) - dec_center = float(skycell_record["dec_projection_center"]) - shiftx = float(skycell_record["x0_projection"]) - shifty = float(skycell_record["y0_projection"]) + # Bounding box of the skycell. Note that the center of the pixels are at (0.5, 0.5) bounding_box = ( (-0.5, -0.5 + skycell_record["nx"]), (-0.5, -0.5 + skycell_record["ny"]), ) - # components of the model - # shift = models.Shift(shiftx) & models.Shift(shifty) + wcsobj = wcsinfo_to_wcs(wcsinfo, bounding_box=bounding_box) + return wcsobj + + +def wcsinfo_to_wcs(wcsinfo, bounding_box=None, name="wcsinfo"): + """Create a GWCS from the L3 wcsinfo meta + + Parameters + ---------- + wcsinfo : dict or MosaicModel.meta.wcsinfo + The L3 wcsinfo to create a GWCS from. - # select a scale for the skycell image, this will come from INS and may - # be optimized for the different survey programs - scale_x = scale - scale_y = scale - # set the pixelsscale to 0.1 arcsec/pixel - pixelscale = models.Scale(scale_x / 3600.0) & models.Scale(scale_y / 3600.0) + bounding_box : None or 4-tuple + The bounding box in detector/pixel space. Form of input is: + (x_left, x_right, y_bottom, y_top) - pixelshift = models.Shift(-1.0 * shiftx) & models.Shift(-1.0 * shifty) + name : str + Value of the `name` attribute of the GWCS object. + + Returns + ------- + wcs : wcs.GWCS + The GWCS object created. + """ + pixelshift = models.Shift(-wcsinfo["x_ref"], name="crpix1") & models.Shift( + -wcsinfo["y_ref"], name="crpix2" + ) + pixelscale = models.Scale(wcsinfo["pixel_scale"], name="cdelt1") & models.Scale( + wcsinfo["pixel_scale"], name="cdelt2" + ) tangent_projection = models.Pix2Sky_TAN() - celestial_rotation = models.RotateNative2Celestial(ra_center, dec_center, 180.0) - det2sky = pixelshift | pixelscale | tangent_projection | celestial_rotation + celestial_rotation = models.RotateNative2Celestial( + wcsinfo["ra_ref"], wcsinfo["dec_ref"], 180.0 + ) + + matrix = wcsinfo.get("rotation_matrix", None) + if matrix: + matrix = np.array(matrix) + else: + orientat = wcsinfo.get("orientat", 0.0) + matrix = wcs_util.calc_rotation_matrix( + np.deg2rad(orientat), v3i_yangle=0.0, vparity=1 + ) + matrix = np.reshape(matrix, (2, 2)) + rotation = models.AffineTransformation2D(matrix, name="pc_rotation_matrix") + det2sky = ( + pixelshift | rotation | pixelscale | tangent_projection | celestial_rotation + ) + detector_frame = coordinate_frames.Frame2D( name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix) ) sky_frame = coordinate_frames.CelestialFrame( reference_frame=coordinates.ICRS(), name="icrs", unit=(u.deg, u.deg) ) - wcsobj = WCS([(detector_frame, det2sky), (sky_frame, None)]) - wcsobj.bounding_box = bounding_box + wcsobj = WCS([(detector_frame, det2sky), (sky_frame, None)], name=name) + + if bounding_box: + wcsobj.bounding_box = bounding_box return wcsobj diff --git a/romancal/pipeline/tests/test_mosaic_pipeline.py b/romancal/pipeline/tests/test_mosaic_pipeline.py new file mode 100644 index 000000000..79bcd2d9b --- /dev/null +++ b/romancal/pipeline/tests/test_mosaic_pipeline.py @@ -0,0 +1,133 @@ +"""Unit tests for the mosaic pipeline""" + +import numpy as np + +import romancal.pipeline.mosaic_pipeline as mp + + +def test_skycell_to_wcs(): + """Test integrity of skycell_to_wcs""" + + skycell = np.void( + ( + "r274dp63x31y81", + 269.7783307416819, + 66.04965143695566, + 1781.5, + 1781.5, + 355.9788, + 3564, + 3564, + 67715.5, + -110484.5, + 269.6657957545588, + 65.9968687812357, + 269.6483032937494, + 66.09523979539262, + 269.89132874168854, + 66.10234971630734, + 269.9079118635897, + 66.00394719483091, + 0.1, + 274.2857142857143, + 63.0, + 0.0, + 463181, + ), + dtype=[ + ("name", "