def ifu(input_model, reference_files): """ Create the WCS pipeline for a MIRI IFU observation. """ #reference_files = {'distortion': 'jwst_miri_distortion_00001.asdf', #files must hold 2 channels each #'specwcs': 'jwst_miri_specwcs_00001.asdf', #'regions': 'jwst_miri_regions_00001.asdf', #'v2v3': 'jwst_miri_v2v3_00001.asdf' #'wavelengthrange': 'jwst_miri_wavelengthrange_0001.asdf'} detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) alpha_beta = cf.Frame2D(name='alpha_beta_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('alpha', 'beta')) spec_local = cf.SpectralFrame(name='alpha_beta_spectral', axes_order=(2,), unit=(u.micron,), axes_names=('lambda',)) miri_focal = cf.CompositeFrame([alpha_beta, spec_local], name='alpha_beta') xyan_spatial = cf.Frame2D(name='Xan_Yan_spatial', axes_order=(0, 1), unit=(u.arcmin, u.arcmin), axes_names=('v2', 'v3')) spec = cf.SpectralFrame(name='Xan_Yan_spectral', axes_order=(2,), unit=(u.micron,), axes_names=('lambda',)) xyan = cf.CompositeFrame([xyan_spatial, spec], name='Xan_Yan') v23_spatial = cf.Frame2D(name='V2_V3_spatial', axes_order=(0, 1), unit=(u.arcmin, u.arcmin), axes_names=('v2', 'v3')) spec = cf.SpectralFrame(name='V2_v3_spectral', axes_order=(2,), unit=(u.micron,), axes_names=('lambda',)) v2v3 = cf.CompositeFrame([v23_spatial, spec], name='V2_V3') icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('RA', 'DEC')) sky = cf.CompositeFrame([icrs, spec], name='sky_and_wavelength') det2alpha_beta = (detector_to_alpha_beta(input_model, reference_files)).rename( "detector_to_alpha_beta") ab2xyan = (alpha_beta2XanYan(input_model, reference_files)).rename("alpha_beta_to_Xan_Yan") xyan2v23 = models.Identity(1) & (models.Shift(7.8) | models.Scale(-1)) & models.Identity(1) fitswcs_transform = pointing.create_fitswcs_transform(input_model) & models.Identity(1) pipeline = [(detector, det2alpha_beta), (miri_focal, ab2xyan), (xyan, xyan2v23), (v2v3, fitswcs_transform), (sky, None) ] return pipeline
def ifu(input_model, reference_files): """ Create the WCS pipeline for a MIRI IFU observation. Goes from 0-indexed detector pixels (0,0) middle of lower left reference pixel to V2,V3 in arcsec. Parameters ---------- input_model : `jwst.datamodels.ImagingModel` Data model. reference_files : dict Dictionary {reftype: reference file name}. """ #reference_files = {'distortion': 'jwst_miri_distortion_00001.asdf', #files must hold 2 channels each #'specwcs': 'jwst_miri_specwcs_00001.asdf', #'regions': 'jwst_miri_regions_00001.asdf', #'wavelengthrange': 'jwst_miri_wavelengthrange_0001.asdf'} # Define reference frames detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) alpha_beta = cf.Frame2D(name='alpha_beta_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('alpha', 'beta')) spec_local = cf.SpectralFrame(name='alpha_beta_spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) miri_focal = cf.CompositeFrame([alpha_beta, spec_local], name='alpha_beta') v23_spatial = cf.Frame2D(name='V2_V3_spatial', axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('v2', 'v3')) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) v2v3 = cf.CompositeFrame([v23_spatial, spec], name='v2v3') icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('RA', 'DEC')) world = cf.CompositeFrame([icrs, spec], name='world') # Define the actual transforms det2abl = (detector_to_abl(input_model, reference_files)).rename("detector_to_abl") abl2v2v3l = (abl_to_v2v3l(input_model, reference_files)).rename("abl_to_v2v3l") tel2sky = pointing.v23tosky(input_model) & models.Identity(1) # Put the transforms together into a single transform shape = input_model.data.shape det2abl.bounding_box = ((-0.5, shape[0] - 0.5), (-0.5, shape[1] - 0.5)) pipeline = [(detector, det2abl), (miri_focal, abl2v2v3l), (v2v3, tel2sky), (world, None)] return pipeline
def ifu(input_model, reference_files): """ The MIRI MRS WCS pipeline. It has the following coordinate frames: "detector", "alpha_beta", "v2v3", "world". It uses the "distortion", "regions", "specwcs" and "wavelengthrange" reference files. """ # Define coordinate frames. detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) alpha_beta = cf.Frame2D(name='alpha_beta_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('alpha', 'beta')) spec_local = cf.SpectralFrame(name='alpha_beta_spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) miri_focal = cf.CompositeFrame([alpha_beta, spec_local], name='alpha_beta') v23_spatial = cf.Frame2D(name='V2_V3_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('v2', 'v3')) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) v2v3 = cf.CompositeFrame([v23_spatial, spec], name='v2v3') icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('RA', 'DEC')) world = cf.CompositeFrame([icrs, spec], name='world') # Define the actual transforms det2abl = (detector_to_abl(input_model, reference_files)).rename("detector_to_abl") abl2v2v3l = (abl_to_v2v3l(input_model, reference_files)).rename("abl_to_v2v3l") tel2sky = pointing.v23tosky(input_model) & models.Identity(1) # Put the transforms together into a single transform shape = input_model.data.shape det2abl.bounding_box = ((-0.5, shape[0] - 0.5), (-0.5, shape[1] - 0.5)) pipeline = [(detector, det2abl), (miri_focal, abl2v2v3l), (v2v3, tel2sky), (world, None)] return pipeline
def niriss_soss_set_input(model, order_number): """ Get the right model given the order number. Parameters ---------- model - Input model order_number - the, well, order number desired Returns ------- WCS - the WCS corresponding to the order_number """ # Make sure the order number is correct. if order_number < 1 or order_number > 3: raise ValueError('Order must be between 1 and 3') # Return the correct transform based on the order_number obj = model.meta.wcs.forward_transform.get_model(order_number) # use the size of the input subarray7 detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) spec = cf.SpectralFrame(name='spectral', axes_order=(2,), unit=(u.micron,), axes_names=('wavelength',)) sky = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_names=('ra', 'dec'), axes_order=(0, 1), unit=(u.deg, u.deg), name='sky') world = cf.CompositeFrame([sky, spec], name='world') pipeline = [(detector, obj), (world, None) ] return wcs.WCS(pipeline)
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) tabular_gwcs = GWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) return tabular_gwcs
def generate_s3d_wcs(): """ create a fake gwcs for a cube """ # create input /output frames detector = cf.CoordinateFrame(name='detector', axes_order=(0,1,2), axes_names=['x', 'y', 'z'], axes_type=['spatial', 'spatial', 'spatial'], naxes=3, unit=['pix', 'pix', 'pix']) sky = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_names=("RA", "DEC")) spec = cf.SpectralFrame(name='spectral', unit=['um'], axes_names=['wavelength'], axes_order=(2,)) world = cf.CompositeFrame(name="world", frames=[sky, spec]) # create fake transform to at least get a bounding box # for the s3d jwst loader # shape 30,10,10 (spec, y, x) crpix1, crpix2, crpix3 = 5, 5, 15 # (x, y, spec) crval1, crval2, crval3 = 1, 1, 1 cdelt1, cdelt2, cdelt3 = 0.01, 0.01, 0.05 shift = models.Shift(-crpix2) & models.Shift(-crpix1) scale = models.Multiply(cdelt2) & models.Multiply(cdelt1) proj = models.Pix2Sky_TAN() skyrot = models.RotateNative2Celestial(crval2, 90 + crval1, 180) celestial = shift | scale | proj | skyrot wave_model = models.Shift(-crpix3) | models.Multiply(cdelt3) | models.Shift(crval3) transform = models.Mapping((2, 0, 1)) | celestial & wave_model | models.Mapping((1, 2, 0)) # bounding box based on shape (30,10,10) in test transform.bounding_box = ((0, 29), (0, 9), (0, 9)) # create final wcs pipeline = [(detector, transform), (world, None)] return WCS(pipeline)
def identity_gwcs_4d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel) & m.Multiply(1 * u.nm / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm) time_frame = cf.TemporalFrame(axes_order=(3, ), unit=u.s) frame = cf.CompositeFrame([sky_frame, wave_frame, time_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "z", "s"), unit=(u.pix, u.pix, u.pix, u.pix)) return gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame)
def identity_gwcs_3d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (TwoDScale(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01"), axes_names=("longitude", "latitude"), unit=(u.arcsec, u.arcsec), axis_physical_types=("custom:pos.helioprojective.lon", "custom:pos.helioprojective.lat")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm, axes_names=("wavelength", )) frame = cf.CompositeFrame([sky_frame, wave_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20, 30) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def _generate_wcs_transform(dispaxis): """Create mock gwcs.WCS object for resampled s2d data""" detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('RA', 'DEC')) spec = cf.SpectralFrame(name='spec', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) world = cf.CompositeFrame(name="world", frames=[icrs, spec]) if dispaxis == 1: mapping = models.Mapping((0, 1, 0)) if dispaxis == 2: mapping = models.Mapping((0, 1, 1)) transform = mapping | (models.Const1D(42) & models.Const1D(42) & (models.Shift(30) | models.Scale(0.1))) pipeline = [(detector, transform), (world, None)] wcs = WCS(pipeline) return wcs
def create_coord_frames(): gdetector = cf.Frame2D(name='grism_detector', axes_order=(0, 1), unit=(u.pix, u.pix)) detector = cf.Frame2D(name='full_detector', axes_order=(0, 1), axes_names=('dx', 'dy'), unit=(u.pix, u.pix)) v2v3_spatial = cf.Frame2D(name='v2v3_spatial', axes_order=(0, 1), axes_names=('v2', 'v3'), unit=(u.deg, u.deg)) v2v3vacorr_spatial = cf.Frame2D(name='v2v3vacorr_spatial', axes_order=(0, 1), axes_names=('v2', 'v3'), unit=(u.arcsec, u.arcsec)) sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs') spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('wavelength', )) frames = { 'grism_detector': gdetector, 'direct_image': cf.CompositeFrame([detector, spec], name='direct_image'), 'v2v3': cf.CompositeFrame([v2v3_spatial, spec], name='v2v3'), 'v2v3vacorr': cf.CompositeFrame([v2v3vacorr_spatial, spec], name='v2v3vacorr'), 'world': cf.CompositeFrame([sky_frame, spec], name='world') } return frames
def create_frames(): """ Create the coordinate frames in the NIRSPEC WCS pipeline. These are "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world". """ det = cf.Frame2D(name='detector', axes_order=(0, 1)) sca = cf.Frame2D(name='sca', axes_order=(0, 1)) gwa = cf.Frame2D(name="gwa", axes_order=(0, 1), unit=(u.rad, u.rad), axes_names=('alpha_in', 'beta_in')) msa_spatial = cf.Frame2D(name='msa_spatial', axes_order=(0, 1), unit=(u.m, u.m), axes_names=('x_msa', 'y_msa')) slit_spatial = cf.Frame2D(name='slit_spatial', axes_order=(0, 1), unit=("", ""), axes_names=('x_slit', 'y_slit')) sky = cf.CelestialFrame(name='sky', axes_order=(0, 1), reference_frame=coord.ICRS()) v2v3_spatial = cf.Frame2D(name='v2v3_spatial', axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('V2', 'V3')) # The oteip_to_v23 incorporates a scale to convert the spectral units from # meters to microns. So the v2v3 output frame will be in u.deg, u.deg, u.micron spec = cf.SpectralFrame(name='spectral', axes_order=(2,), unit=(u.micron,), axes_names=('wavelength',)) v2v3 = cf.CompositeFrame([v2v3_spatial, spec], name='v2v3') slit_frame = cf.CompositeFrame([slit_spatial, spec], name='slit_frame') msa_frame = cf.CompositeFrame([msa_spatial, spec], name='msa_frame') oteip_spatial = cf.Frame2D(name='oteip', axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('X_OTEIP', 'Y_OTEIP')) oteip = cf.CompositeFrame([oteip_spatial, spec], name='oteip') world = cf.CompositeFrame([sky, spec], name='world') return det, sca, gwa, slit_frame, msa_frame, oteip, v2v3, world
def lrs(input_model, reference_files): """ The LRS-FIXEDSLIT and LRS-SLITLESS WCS pipeline. Notes ----- It includes three coordinate frames - "detector", "v2v3" and "world". "v2v3" and "world" each have (spatial, spatial, spectral) components. Uses the "specwcs" and "distortion" reference files. """ # Define the various coordinate frames. # Original detector frame detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) # Spectral component spec = cf.SpectralFrame(name='spec', axes_order=(2,), unit=(u.micron,), axes_names=('lambda',)) # v2v3 spatial component v2v3_spatial = cf.Frame2D(name='v2v3_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('v2', 'v3')) v2v3vacorr_spatial = cf.Frame2D( name='v2v3vacorr_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('v2', 'v3') ) # v2v3 spatial+spectra v2v3 = cf.CompositeFrame([v2v3_spatial, spec], name='v2v3') v2v3vacorr = cf.CompositeFrame([v2v3vacorr_spatial, spec], name='v2v3vacorr') # 'icrs' frame which is the spatial sky component icrs = cf.CelestialFrame(name='icrs', reference_frame=coord.ICRS(), axes_order=(0, 1), unit=(u.deg, u.deg), axes_names=('RA', 'DEC')) # Final 'world' composite frame with spatial and spectral components world = cf.CompositeFrame(name="world", frames=[icrs, spec]) # Create the transforms dettotel = lrs_distortion(input_model, reference_files) v2v3tosky = pointing.v23tosky(input_model) teltosky = v2v3tosky & models.Identity(1) # Compute differential velocity aberration (DVA) correction: va_corr = pointing.dva_corr_model( va_scale=input_model.meta.velocity_aberration.scale_factor, v2_ref=input_model.meta.wcsinfo.v2_ref, v3_ref=input_model.meta.wcsinfo.v3_ref ) & models.Identity(1) # Put the transforms together into a single pipeline pipeline = [(detector, dettotel), (v2v3, va_corr), (v2v3vacorr, teltosky), (world, None)] return pipeline
def tabular_wcs(xarray): coordinateframe = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL',), axes_order=(0,)) specframe = cf.SpectralFrame(unit=xarray.unit, axes_order=(0,)) transform = Tabular1D(np.arange(len(xarray)), xarray.value) tabular_gwcs = gwcs.wcs.WCS([(coordinateframe, transform), (specframe, None)]) return tabular_gwcs
def test_composite_frame(tmpdir): icrs = coord.ICRS() fk5 = coord.FK5() cel1 = cf.CelestialFrame(reference_frame=icrs) cel2 = cf.CelestialFrame(reference_frame=fk5) spec1 = cf.SpectralFrame(name='freq', unit=[ u.Hz, ], axes_order=(2, )) spec2 = cf.SpectralFrame(name='wave', unit=[ u.m, ], axes_order=(2, )) comp1 = cf.CompositeFrame([cel1, spec1]) comp2 = cf.CompositeFrame([cel2, spec2]) comp = cf.CompositeFrame( [comp1, cf.SpectralFrame(axes_order=(3, ), unit=(u.m, ))]) tree = {'comp1': comp1, 'comp2': comp2, 'comp': comp} helpers.assert_roundtrip_tree(tree, tmpdir)
def gwcs_1d(): detector_frame = cf.CoordinateFrame( name="detector", naxes=1, axes_order=(0, ), axes_type=("pixel"), axes_names=("x"), unit=(u.pix)) spec_frame = cf.SpectralFrame(name="spectral", axes_order=(2, ), unit=u.nm) return WCS(forward_transform=Identity(1), input_frame=detector_frame, output_frame=spec_frame)
def frame_from_model(wcsinfo): """ Initialize a coordinate frame based on values in model.meta.wcsinfo. Parameters ---------- wcsinfo : `~stdatamodels.DataModel` or dict Either one of the JWST data models or a dict with model.meta.wcsinfo. Returns ------- frame : `~coordinate_frames.CoordinateFrame` """ if isinstance(wcsinfo, DataModel): wcsinfo = wcsinfo_from_model(wcsinfo) wcsaxes = wcsinfo['WCSAXES'] celestial_axes, spectral_axes, other = gwutils.get_axes(wcsinfo) cunit = wcsinfo['CUNIT'] frames = [] if celestial_axes: ref_frame = coords.ICRS() celestial = cf.CelestialFrame(name='sky', axes_order=tuple(celestial_axes), reference_frame=ref_frame, unit=cunit[celestial_axes], axes_names=('RA', 'DEC')) frames.append(celestial) if spectral_axes: spec = cf.SpectralFrame(name='spectral', axes_order=tuple(spectral_axes), unit=cunit[spectral_axes], axes_names=('wavelength', )) frames.append(spec) if other: # Make sure these are strings and not np.str_ objects. axes_names = tuple([str(name) for name in wcsinfo['CTYPE'][other]]) name = "_".join(wcsinfo['CTYPE'][other]) spatial = cf.Frame2D(name=name, axes_order=tuple(other), unit=cunit[other], axes_names=axes_names) frames.append(spatial) if wcsaxes == 2: return frames[0] elif wcsaxes == 3: world = cf.CompositeFrame(frames, name='world') return world else: raise ValueError("WCSAXES can be 2 or 3, got {0}".format(wcsaxes))
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ orig_array = u.Quantity(array) # TODO: Input arrays must be strictly ascending. This is not always the # case for a spectral axis (e.g. when in frequency space). Thus, we # convert to wavelength to create the wcs. if orig_array.unit.physical_type != 'length' and \ orig_array.unit.is_equivalent(u.AA, equivalencies=u.spectral()): array = orig_array.to(u.AA, equivalencies=u.spectral()) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) class SpectralGWCS(GWCS): def pixel_to_world(self, *args, **kwargs): if orig_array.unit == '': return u.Quantity(super().pixel_to_world_values( *args, **kwargs)) return super().pixel_to_world(*args, **kwargs).to( orig_array.unit, equivalencies=u.spectral()) tabular_gwcs = SpectralGWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) # Store the intended unit from the origin input array # tabular_gwcs._input_unit = orig_array.unit return tabular_gwcs
def lrs(input_model, reference_files): """ Create the WCS pipeline for a MIRI fixed slit observation. reference_files = {"specwcs": 'MIRI_FM_MIRIMAGE_P750L_DISTORTION_04.02.00.fits'} """ detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) focal_spatial = cf.Frame2D(name='focal', axes_order=(0, 1), unit=(u.arcmin, u.arcmin)) sky = cf.CelestialFrame(reference_frame=coord.ICRS()) spec = cf.SpectralFrame(name='wavelength', axes_order=(2, ), unit=(u.micron, ), axes_names=('lambda', )) focal = cf.CompositeFrame([focal_spatial, spec]) ref = fits.open(reference_files['specwcs']) ldata = ref[1].data if input_model.meta.exposure.type.lower() == 'mir_lrs-fixedslit': zero_point = ref[1].header['imx'], ref[1].header['imy'] elif input_model.meta.exposure.type.lower() == 'mir_lrs-slitless': #zero_point = ref[1].header['imysltl'], ref[1].header['imxsltl'] #zero point in reference file is wrong # This should eb moved eventually to the reference file. zero_point = [35, 442] #[35, 763] # account for subarray lrsdata = np.array([l for l in ldata]) x0 = lrsdata[:, 3] x1 = lrsdata[:, 5] y0 = lrsdata[:, 4] domain = [{ 'lower': x0.min() + zero_point[0], 'upper': x1.max() + zero_point[0] }, { 'lower': (y0.min() + zero_point[1]), 'upper': (y0.max() + zero_point[1]) }] log.info("Setting domain to {0}".format(domain)) lrs_wav_model = jwmodels.LRSWavelength(lrsdata, zero_point) ref.close() angle = np.arctan(0.00421924) spatial = models.Rotation2D(angle) det2focal = models.Mapping((0, 1, 0, 1)) | spatial & lrs_wav_model det2focal.meta['domain'] = domain pipeline = [(detector, det2focal), (focal, None)] #(sky, None) #] return pipeline
def gwcs_from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ orig_array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) # In order for the world_to_pixel transformation to automatically convert # input units, the equivalencies in the look up table have to be extended # with spectral unit information. SpectralTabular1D = type( "SpectralTabular1D", (Tabular1D, ), {'input_units_equivalencies': { 'x0': u.spectral() }}) forward_transform = SpectralTabular1D(np.arange(len(array)), lookup_table=array) # If our spectral axis is in descending order, we have to flip the lookup # table to be ascending in order for world_to_pixel to work. if len(array) == 0 or array[-1] > array[0]: forward_transform.inverse = SpectralTabular1D(array, lookup_table=np.arange( len(array))) else: forward_transform.inverse = SpectralTabular1D(array[::-1], lookup_table=np.arange( len(array))[::-1]) class SpectralGWCS(GWCS): def pixel_to_world(self, *args, **kwargs): if orig_array.unit == '': return u.Quantity(super().pixel_to_world_values( *args, **kwargs)) return super().pixel_to_world(*args, **kwargs).to( orig_array.unit, equivalencies=u.spectral()) tabular_gwcs = SpectralGWCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) # Store the intended unit from the origin input array # tabular_gwcs._input_unit = orig_array.unit return tabular_gwcs
def gwcs_3d(): detector_frame = cf.CoordinateFrame( name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), axes_names=("x", "y", "z"), unit=(u.pix, u.pix, u.pix)) sky_frame = cf.CelestialFrame(reference_frame=Helioprojective(), name='hpc') spec_frame = cf.SpectralFrame(name="spectral", axes_order=(2, ), unit=u.nm) out_frame = cf.CompositeFrame(frames=(sky_frame, spec_frame)) return WCS(forward_transform=spatial_like_model(), input_frame=detector_frame, output_frame=out_frame)
def niriss_soss(input_model, reference_files): """ The NIRISS SOSS pipeline includes 3 coordinate frames - detector, focal plane and sky reference_files={'specwcs': 'soss_wavelengths_configuration.asdf'} """ # Get the target RA and DEC, they will be used for setting the WCS RA and DEC based on a conversation # with Kevin Volk. try: target_ra = float(input_model['meta.target.ra']) target_dec = float(input_model['meta.target.dec']) except: # There was an error getting the target RA and DEC, so we are not going to continue. raise ValueError('Problem getting the TARG_RA or TARG_DEC from input model {}'.format(input_model)) # Define the frames detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) spec = cf.SpectralFrame(name='spectral', axes_order=(2,), unit=(u.micron,), axes_names=('wavelength',)) sky = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_names=('ra', 'dec'), axes_order=(0, 1), unit=(u.deg, u.deg), name='sky') world = cf.CompositeFrame([sky, spec], name='world') try: with AsdfFile.open(reference_files['specwcs']) as wl: wl1 = wl.tree[1].copy() wl2 = wl.tree[2].copy() wl3 = wl.tree[3].copy() except Exception as e: raise IOError('Error reading wavelength correction from {}'.format(reference_files['specwcs'])) cm_order1 = (Mapping((0, 1, 0, 1)) | (Const1D(target_ra) & Const1D(target_dec) & wl1)).rename('Order1') cm_order2 = (Mapping((0, 1, 0, 1)) | (Const1D(target_ra) & Const1D(target_dec) & wl2)).rename('Order2') cm_order3 = (Mapping((0, 1, 0, 1)) | (Const1D(target_ra) & Const1D(target_dec) & wl3)).rename('Order3') # Define the transforms, they should accept (x,y) and return (ra, dec, lambda) soss_model = NirissSOSSModel([1, 2, 3], [cm_order1, cm_order2, cm_order3]).rename('3-order SOSS Model') # Define the pipeline based on the frames and models above. pipeline = [(detector, soss_model), (world, None) ] return pipeline
def create_spectral_wcs(ra, dec, wavelength): """Assign a WCS for sky coordinates and a table of wavelengths Parameters ---------- ra: float The right ascension (in degrees) at the nominal location of the entrance aperture (slit). dec: float The declination (in degrees) at the nominal location of the entrance aperture. wavelength: ndarray The wavelength in microns at each pixel of the extracted spectrum. Returns: -------- wcs: a gwcs.wcs.WCS object This takes a float or sequence of float and returns a tuple of the right ascension, declination, and wavelength (or sequence of wavelengths) at the pixel(s) specified by the input argument. """ # Only the first coordinate is used. input_frame = cf.Frame2D(axes_order=(0, 1), unit=(u.pix, u.pix), name="pixel_frame") sky = cf.CelestialFrame(name='sky', axes_order=(0, 1), reference_frame=coord.ICRS()) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('wavelength', )) pixel = np.arange(len(wavelength), dtype=np.float) tab = Mapping((0, 0, 0)) | \ Const1D(ra) & Const1D(dec) & Tabular1D(pixel, wavelength) world = cf.CompositeFrame([sky, spec], name='world') pipeline = [(input_frame, tab), (world, None)] return WCS(pipeline)
def _create_fake_data(object_name): from astropy.table import Table astrofaker = pytest.importorskip('astrofaker') wavelength, flux = _get_spectrophotometric_data(object_name) wavecal = { 'degree': 1, 'domain': [0., wavelength.size - 1], 'c0': wavelength.mean(), 'c1': wavelength.mean() / 2, } wave_model = models.Chebyshev1D(**wavecal) wave_model.inverse = astromodels.make_inverse_chebyshev1d( wave_model, rms=0.01, max_deviation=0.03) hdu = fits.ImageHDU() hdu.header['CCDSUM'] = "1 1" hdu.data = flux[np.newaxis, :] # astrofaker needs 2D data _ad = astrofaker.create('GMOS-S') _ad.add_extension(hdu, pixel_scale=1.0) _ad[0].data = _ad[0].data.ravel() _ad[0].mask = np.zeros(_ad[0].data.size, dtype=np.uint16) # ToDo Requires mask _ad[0].variance = np.ones_like(_ad[0].data) # ToDo Requires Variance in_frame = cf.CoordinateFrame(naxes=1, axes_type=['SPATIAL'], axes_order=(0, ), unit=u.pix, axes_names=('x', ), name='pixels') out_frame = cf.SpectralFrame(unit=u.nm, name='world') _ad[0].wcs = gWCS([(in_frame, wave_model), (out_frame, None)]) _ad[0].hdr.set('NAXIS', 1) _ad[0].phu.set('OBJECT', object_name) _ad[0].phu.set('EXPTIME', 1.) _ad[0].hdr.set('BUNIT', "electron") assert _ad.object() == object_name assert _ad.exposure_time() == 1 return _ad
def from_array(array): """ Create a new WCS from provided tabular data. This defaults to being a GWCS object. """ array = u.Quantity(array) coord_frame = cf.CoordinateFrame(naxes=1, axes_type=('SPECTRAL', ), axes_order=(0, )) spec_frame = cf.SpectralFrame(unit=array.unit, axes_order=(0, )) forward_transform = Tabular1D(np.arange(len(array)), array.value) forward_transform.inverse = Tabular1D(array.value, np.arange(len(array))) tabular_gwcs = gwcs.wcs.WCS(forward_transform=forward_transform, input_frame=coord_frame, output_frame=spec_frame) return WCSWrapper(wcs=tabular_gwcs)
def niriss_soss_set_input(model, order_number): """ Extract a WCS fr a specific spectral order. Parameters ---------- model : `~jwst.datamodels.ImageModel` An instance of an ImageModel order_number : int the spectral order Returns ------- WCS - the WCS corresponding to the spectral order. """ # Make sure the spectral order is available. if order_number < 1 or order_number > 3: raise ValueError('Order must be between 1 and 3') # Return the correct transform based on the order_number obj = model.meta.wcs.forward_transform.get_model(order_number) # use the size of the input subarray detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('wavelength', )) sky = cf.CelestialFrame(reference_frame=coord.ICRS(), axes_names=('ra', 'dec'), axes_order=(0, 1), unit=(u.deg, u.deg), name='sky') world = cf.CompositeFrame([sky, spec], name='world') pipeline = [(detector, obj), (world, None)] return wcs.WCS(pipeline)
def frame_from_model(wcsinfo): wcsaxes = wcsinfo['WCSAXES'] spatial_axes, spectral_axes = gwutils.get_axes(wcsinfo) cunit = wcsinfo['CUNIT'] ref_frame = coords.ICRS() sky = cf.CelestialFrame(name='sky', axes_order=tuple(spatial_axes), reference_frame=ref_frame, unit=cunit[spatial_axes], axes_names=('RA', 'DEC')) if wcsaxes == 2: return sky elif wcsaxes == 3: spec = cf.SpectralFrame(name='spectral', axes_order=tuple(spectral_axes), unit=cunit[spectral_axes[0]], axes_names=('wavelength', )) world = cf.CompositeFrame([sky, spec], name='world') return world else: raise ValueError("WCSAXES can be 2 or 3, got {0}".format(wcsaxes))
def create_frames(): """ Create the coordinate frames in the NIRSPEC WCS pipeline. These are "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world". """ det = cf.Frame2D(name='detector', axes_order=(0, 1)) gwa = cf.Frame2D(name="gwa", axes_order=(0, 1), unit=(u.rad, u.rad), axes_names=('alpha_in', 'beta_in')) msa_spatial = cf.Frame2D(name='msa_spatial', axes_order=(0, 1), unit=(u.m, u.m), axes_names=('x_msa', 'y_msa')) slit_spatial = cf.Frame2D(name='slit_spatial', axes_order=(0, 1), unit=("", ""), axes_names=('x_slit', 'y_slit')) #sky = cf.CelestialFrame(name='sky', axes_order=(0, 1), reference_frame=coord.ICRS()) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.m, ), axes_names=('wavelength', )) v2v3_spatial = cf.Frame2D(name='V2V3_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('V2', 'V3')) v2v3 = cf.CompositeFrame([v2v3_spatial, spec], name='v2v3') slit_frame = cf.CompositeFrame([slit_spatial, spec], name='slit_frame') msa_frame = cf.CompositeFrame([msa_spatial, spec], name='msa_frame') oteip_spatial = cf.Frame2D(name='OTEIP_spatial', axes_order=(0, 1), unit=(u.arcsec, u.arcsec), axes_names=('X_OTEIP', 'Y_OTEIP')) oteip = cf.CompositeFrame([oteip_spatial, spec], name='oteip') #world = cf.CompositeFrame([sky, spec], name='world') return det, gwa, slit_frame, msa_frame, oteip, v2v3
def identity_gwcs_4d(): """ A simple 1-1 gwcs that converts from pixels to arcseconds """ identity = (TwoDScale(1 * u.arcsec / u.pixel) & m.Multiply(1 * u.nm / u.pixel) & m.Multiply(1 * u.s / u.pixel)) sky_frame = cf.CelestialFrame( axes_order=(0, 1), name='helioprojective', reference_frame=Helioprojective(obstime="2018-01-01"), unit=(u.arcsec, u.arcsec), axis_physical_types=("custom:pos.helioprojective.lon", "custom:pos.helioprojective.lat")) wave_frame = cf.SpectralFrame(axes_order=(2, ), unit=u.nm) time_frame = cf.TemporalFrame(Time("2020-01-01T00:00", format="isot", scale="utc"), axes_order=(3, ), unit=u.s) frame = cf.CompositeFrame([sky_frame, wave_frame, time_frame]) detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), axes_names=("x", "y", "z", "s"), unit=(u.pix, u.pix, u.pix, u.pix)) wcs = gwcs.wcs.WCS(forward_transform=identity, output_frame=frame, input_frame=detector_frame) wcs.pixel_shape = (10, 20, 30, 40) wcs.array_shape = wcs.pixel_shape[::-1] return wcs
def make_spectral(self): """ Decide how to make a spectral axes. """ name = self.header[f'DWNAME{self.n}'] self._frames.append( cf.SpectralFrame(axes_order=(self._i, ), axes_names=(name, ), unit=self.get_units(self._i), name=name)) if "WAVE" in self.header.get(f'CTYPE{self.n}', ''): transform = self.make_spectral_from_wcs() elif "FRAMEWAV" in self.header.keys(): transform = self.make_spectral_from_dataset() else: raise ValueError( "Could not parse spectral WCS information from this header." ) # pragma: no cover self._transforms.append(transform) self._i += 1
def build_interpolated_output_wcs(self, refmodel=None): """ Create a spatial/spectral WCS output frame using all the input models Creates output frame by linearly fitting RA, Dec along the slit and producing a lookup table to interpolate wavelengths in the dispersion direction. Parameters ---------- refmodel : `~jwst.datamodels.DataModel` The reference input image from which the fiducial WCS is created. If not specified, the first image in self.input_models is used. Returns ------- output_wcs : `~gwcs.WCS` object A gwcs WCS object defining the output frame WCS """ # for each input model convert slit x,y to ra,dec,lam # use first input model to set spatial scale # use center of appended ra and dec arrays to set up # center of final ra,dec # append all ra,dec, wavelength array for each slit # use first model to initialize wavelenth array # append wavelengths that fall outside the endpoint of # of wavelength array when looping over additional data all_wavelength = [] all_ra_slit = [] all_dec_slit = [] for im, model in enumerate(self.input_models): wcs = model.meta.wcs bb = wcs.bounding_box grid = wcstools.grid_from_bounding_box(bb) ra, dec, lam = np.array(wcs(*grid)) spectral_axis = find_dispersion_axis(model) spatial_axis = spectral_axis ^ 1 # Compute the wavelength array, trimming NaNs from the ends # In many cases, a whole slice is NaNs, so ignore those warnings warnings.simplefilter("ignore") wavelength_array = np.nanmedian(lam, axis=spectral_axis) warnings.resetwarnings() wavelength_array = wavelength_array[~np.isnan(wavelength_array)] # We need to estimate the spatial sampling to use for the output WCS. # Tt is assumed the spatial sampling is the same for all the input # models. So we can use the first input model to set the spatial # sampling. # Steps to do this for first input model: # 1. find the middle of the spectrum in wavelength # 2. Pull out the ra and dec at the center of the slit. # 3. Find the mean ra,dec and the center of the slit this will # represent the tangent point # 4. Convert ra,dec -> tangent plane projection: x_tan,y_tan # 5. using x_tan, y_tan perform a linear fit to find spatial sampling # first input model sets intializes wavelength array and defines # the spatial scale of the output wcs if im == 0: for iw in wavelength_array: all_wavelength.append(iw) lam_center_index = int( (bb[spectral_axis][1] - bb[spectral_axis][0]) / 2) if spatial_axis == 0: ra_center = ra[lam_center_index, :] dec_center = dec[lam_center_index, :] else: ra_center = ra[:, lam_center_index] dec_center = dec[:, lam_center_index] # find the ra and dec for this slit using center of slit ra_center_pt = np.nanmean(ra_center) dec_center_pt = np.nanmean(dec_center) if resample_utils.is_sky_like(model.meta.wcs.output_frame): # convert ra and dec to tangent projection tan = Pix2Sky_TAN() native2celestial = RotateNative2Celestial( ra_center_pt, dec_center_pt, 180) undist2sky1 = tan | native2celestial # Filter out RuntimeWarnings due to computed NaNs in the WCS warnings.simplefilter("ignore") # at this center of slit find x,y tangent projection - x_tan, y_tan x_tan, y_tan = undist2sky1.inverse(ra, dec) warnings.resetwarnings() else: # for non sky-like output frames, no need to do tangent plane projections # but we still use the same variables x_tan, y_tan = ra, dec # pull out data from center if spectral_axis == 0: x_tan_array = x_tan.T[lam_center_index] y_tan_array = y_tan.T[lam_center_index] else: x_tan_array = x_tan[lam_center_index] y_tan_array = y_tan[lam_center_index] x_tan_array = x_tan_array[~np.isnan(x_tan_array)] y_tan_array = y_tan_array[~np.isnan(y_tan_array)] # estimate the spatial sampling fitter = LinearLSQFitter() fit_model = Linear1D() xstop = x_tan_array.shape[0] / self.pscale_ratio xstep = 1 / self.pscale_ratio ystop = y_tan_array.shape[0] / self.pscale_ratio ystep = 1 / self.pscale_ratio pix_to_xtan = fitter(fit_model, np.arange(0, xstop, xstep), x_tan_array) pix_to_ytan = fitter(fit_model, np.arange(0, ystop, ystep), y_tan_array) # append all ra and dec values to use later to find min and max # ra and dec ra_use = ra.flatten() ra_use = ra_use[~np.isnan(ra_use)] dec_use = dec.flatten() dec_use = dec_use[~np.isnan(dec_use)] all_ra_slit.append(ra_use) all_dec_slit.append(dec_use) # now check wavelength array to see if we need to add to it this_minw = np.min(wavelength_array) this_maxw = np.max(wavelength_array) all_minw = np.min(all_wavelength) all_maxw = np.max(all_wavelength) if this_minw < all_minw: addpts = wavelength_array[wavelength_array < all_minw] for ip in range(len(addpts)): all_wavelength.append(addpts[ip]) if this_maxw > all_maxw: addpts = wavelength_array[wavelength_array > all_maxw] for ip in range(len(addpts)): all_wavelength.append(addpts[ip]) # done looping over set of models all_ra = np.hstack(all_ra_slit) all_dec = np.hstack(all_dec_slit) all_wave = np.hstack(all_wavelength) all_wave = all_wave[~np.isnan(all_wave)] all_wave = np.sort(all_wave, axis=None) # Tabular interpolation model, pixels -> lambda wavelength_array = np.unique(all_wave) # Check if the data is MIRI LRS FIXED Slit. If it is then # the wavelength array needs to be flipped so that the resampled # dispersion direction matches the disperion direction on the detector. if self.input_models[0].meta.exposure.type == 'MIR_LRS-FIXEDSLIT': wavelength_array = np.flip(wavelength_array, axis=None) step = 1 / self.pscale_ratio stop = wavelength_array.shape[0] / self.pscale_ratio points = np.arange(0, stop, step) pix_to_wavelength = Tabular1D(points=points, lookup_table=wavelength_array, bounds_error=False, fill_value=None, name='pix2wavelength') # Tabular models need an inverse explicitly defined. # If the wavelength array is decending instead of ascending, both # points and lookup_table need to be reversed in the inverse transform # for scipy.interpolate to work properly points = wavelength_array lookup_table = np.arange(0, stop, step) if not np.all(np.diff(wavelength_array) > 0): points = points[::-1] lookup_table = lookup_table[::-1] pix_to_wavelength.inverse = Tabular1D(points=points, lookup_table=lookup_table, bounds_error=False, fill_value=None, name='wavelength2pix') # For the input mapping, duplicate the spatial coordinate mapping = Mapping((spatial_axis, spatial_axis, spectral_axis)) # Sometimes the slit is perpendicular to the RA or Dec axis. # For example, if the slit is perpendicular to RA, that means # the slope of pix_to_xtan will be nearly zero, so make sure # mapping.inverse uses pix_to_ytan.inverse. The auto definition # of mapping.inverse is to use the 2nd spatial coordinate, i.e. Dec. if np.isclose(pix_to_ytan.slope, 0, atol=1e-8): mapping_tuple = (0, 1) # Account for vertical or horizontal dispersion on detector if spatial_axis: mapping.inverse = Mapping(mapping_tuple[::-1]) else: mapping.inverse = Mapping(mapping_tuple) # The final transform # redefine the ra, dec center tangent point to include all data # check if all_ra crosses 0 degress - this makes it hard to # define the min and max ra correctly all_ra = wrap_ra(all_ra) ra_min = np.amin(all_ra) ra_max = np.amax(all_ra) ra_center_final = (ra_max + ra_min) / 2.0 dec_min = np.amin(all_dec) dec_max = np.amax(all_dec) dec_center_final = (dec_max + dec_min) / 2.0 tan = Pix2Sky_TAN() if len(self.input_models ) == 1: # single model use ra_center_pt to be consistent # with how resample was done before ra_center_final = ra_center_pt dec_center_final = dec_center_pt if resample_utils.is_sky_like(model.meta.wcs.output_frame): native2celestial = RotateNative2Celestial(ra_center_final, dec_center_final, 180) undist2sky = tan | native2celestial # find the spatial size of the output - same in x,y x_tan_all, _ = undist2sky.inverse(all_ra, all_dec) else: x_tan_all, _ = all_ra, all_dec x_min = np.amin(x_tan_all) x_max = np.amax(x_tan_all) x_size = int(np.ceil((x_max - x_min) / np.absolute(pix_to_xtan.slope))) # single model use size of x_tan_array # to be consistent with method before if len(self.input_models) == 1: x_size = len(x_tan_array) # define the output wcs if resample_utils.is_sky_like(model.meta.wcs.output_frame): transform = mapping | (pix_to_xtan & pix_to_ytan | undist2sky) & pix_to_wavelength else: transform = mapping | (pix_to_xtan & pix_to_ytan) & pix_to_wavelength det = cf.Frame2D(name='detector', axes_order=(0, 1)) if resample_utils.is_sky_like(model.meta.wcs.output_frame): sky = cf.CelestialFrame(name='sky', axes_order=(0, 1), reference_frame=coord.ICRS()) else: sky = cf.Frame2D( name=f'resampled_{model.meta.wcs.output_frame.name}', axes_order=(0, 1)) spec = cf.SpectralFrame(name='spectral', axes_order=(2, ), unit=(u.micron, ), axes_names=('wavelength', )) world = cf.CompositeFrame([sky, spec], name='world') pipeline = [(det, transform), (world, None)] output_wcs = WCS(pipeline) # import ipdb; ipdb.set_trace() # compute the output array size in WCS axes order, i.e. (x, y) output_array_size = [0, 0] output_array_size[spectral_axis] = int( np.ceil(len(wavelength_array) / self.pscale_ratio)) output_array_size[spatial_axis] = int( np.ceil(x_size / self.pscale_ratio)) # turn the size into a numpy shape in (y, x) order self.data_size = tuple(output_array_size[::-1]) bounding_box = resample_utils.wcs_bbox_from_shape(self.data_size) output_wcs.bounding_box = bounding_box return output_wcs