def test_simple_gwcs(): # https://gwcs.readthedocs.io/en/latest/#getting-started shift_by_crpix = models.Shift(-(2048 - 1) * u.pix) & models.Shift(-(1024 - 1) * u.pix) matrix = np.array([[1.290551569736E-05, 5.9525007864732E-06], [5.0226382102765E-06, -1.2644844123757E-05]]) rotation = models.AffineTransformation2D(matrix * u.deg, translation=[0, 0] * u.deg) rotation.input_units_equivalencies = {"x": u.pixel_scale(1 * (u.deg / u.pix)), "y": u.pixel_scale(1 * (u.deg / u.pix))} rotation.inverse = models.AffineTransformation2D(np.linalg.inv(matrix) * u.pix, translation=[0, 0] * u.pix) rotation.inverse.input_units_equivalencies = {"x": u.pixel_scale(1 * (u.pix / u.deg)), "y": u.pixel_scale(1 * (u.pix / u.deg))} tan = models.Pix2Sky_TAN() celestial_rotation = models.RotateNative2Celestial( 5.63056810618 * u.deg, -72.05457184279 * u.deg, 180 * u.deg) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = "linear_transform" detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix)) sky_frame = cf.CelestialFrame(reference_frame=ICRS(), name='icrs', unit=(u.deg, u.deg)) pipeline = [(detector_frame, det2sky), (sky_frame, None)] w = gwcs.wcs.WCS(pipeline) result = wcs_utils.get_compass_info(w, (1024, 2048), r_fac=0.25) assert_allclose(result[:-1], (1024.0, 512.0, 1131.0265005852038, 279.446189124443, 1262.0057201165127, 606.2863901330095, 155.2870478938214, -86.89813081941797)) assert not result[-1]
def test1(tmpdir, ret=False): shift_by_crpix = models.Shift(-2048 * u.pix) & models.Shift(-1024 * u.pix) matrix = np.array([[1.290551569736E-05, 5.9525007864732E-06], [5.0226382102765E-06, -1.2644844123757E-05]]) rotation = models.AffineTransformation2D(matrix * u.deg, translation=[0, 0] * u.deg) rotation.input_units_equivalencies = { "x": u.pixel_scale(1 * u.deg / u.pix), "y": u.pixel_scale(1 * u.deg / u.pix) } rotation.inverse = models.AffineTransformation2D( np.linalg.inv(matrix) * u.pix, translation=[0, 0] * u.pix) rotation.inverse.input_units_equivalencies = { "x": u.pixel_scale(1 * u.pix / u.deg), "y": u.pixel_scale(1 * u.pix / u.deg) } tan = models.Pix2Sky_TAN() celestial_rotation = models.RotateNative2Celestial(5.63056810618 * u.deg, -72.05457184279 * u.deg, 180 * u.deg) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = "linear_transform" detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix)) sky_frame = cf.CelestialFrame(reference_frame=coords.ICRS(), name='icrs', unit=(u.deg, u.deg)) pipeline = [(detector_frame, det2sky), (sky_frame, None)] wcsobj = gwcs.wcs.WCS(pipeline) wcs_set = WcsSet(default=wcsobj, extra=wcsobj) tree = {'wcs_set': wcs_set} if ret: return wcs_set helpers.assert_roundtrip_tree(tree, tmpdir)
def gwcs_simple_imaging_units(): shift_by_crpix = models.Shift(-2048 * u.pix) & models.Shift(-1024 * u.pix) matrix = np.array([[1.290551569736E-05, 5.9525007864732E-06], [5.0226382102765E-06, -1.2644844123757E-05]]) rotation = models.AffineTransformation2D(matrix * u.deg, translation=[0, 0] * u.deg) rotation.input_units_equivalencies = { "x": u.pixel_scale(1 * u.deg / u.pix), "y": u.pixel_scale(1 * u.deg / u.pix) } rotation.inverse = models.AffineTransformation2D( np.linalg.inv(matrix) * u.pix, translation=[0, 0] * u.pix) rotation.inverse.input_units_equivalencies = { "x": u.pixel_scale(1 * u.pix / u.deg), "y": u.pixel_scale(1 * u.pix / u.deg) } tan = models.Pix2Sky_TAN() celestial_rotation = models.RotateNative2Celestial(5.63056810618 * u.deg, -72.05457184279 * u.deg, 180 * u.deg) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = "linear_transform" detector_frame = cf.Frame2D(name="detector", axes_names=("x", "y"), unit=(u.pix, u.pix)) sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs', unit=(u.deg, u.deg)) pipeline = [(detector_frame, det2sky), (sky_frame, None)] return wcs.WCS(pipeline)
def gwcs_7d_complex_mapping(): """ Useful features of this WCS (axes indices here are 0-based): - includes two celestial axes: input (0, 1) maps to world (2 - RA, 1 - Dec) - includes one separable frame with one axis: 4 -> 2 - includes one frame with 3 input and 4 output axes (1 degenerate), with separable world axes (3, 5) and (0, 6). """ offx = models.Shift(-64, name='x_translation') offy = models.Shift(-32, name='y_translation') cd = np.array([[1.2906, 0.59532], [0.50222, -1.2645]]) aff = models.AffineTransformation2D(matrix=1e-5 * cd, name='rotation') aff2 = models.AffineTransformation2D(matrix=cd, name='rotation2') wcslin = (offx & offy) | aff tan = models.Pix2Sky_TAN(name='tangent_projection') n2c = models.RotateNative2Celestial(5.630568, -72.0546, 180, name='skyrot') icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=(2, 1)) spec = cf.SpectralFrame(name='wave', unit=[u.m], axes_order=(4, ), axes_names=('lambda', )) cmplx = cf.CoordinateFrame( name="complex", naxes=4, axes_order=(3, 5, 0, 6), axis_physical_types=(['em.wl', 'em.wl', 'time', 'time']), axes_type=("SPATIAL", "SPATIAL", "TIME", "TIME"), axes_names=("x", "y", "t", 'tau'), unit=(u.m, u.m, u.second, u.second)) comp_frm = cf.CompositeFrame(frames=[icrs, spec, cmplx], name='TEST 7D') wcs_forward = ( (wcslin & models.Shift(-3.14) & models.Scale(2.7) & aff2) | (tan & models.Identity(1) & models.Identity(1) & models.Identity(2)) | (n2c & models.Identity(1) & models.Identity(1) & models.Identity(2)) | models.Mapping((3, 1, 0, 4, 2, 5, 3))) detector_frame = cf.CoordinateFrame(name="detector", naxes=6, axes_order=(0, 1, 2, 3, 4, 5), axes_type=("pixel", "pixel", "pixel", "pixel", "pixel", "pixel"), unit=(u.pix, u.pix, u.pix, u.pix, u.pix, u.pix)) pipeline = [('detector', wcs_forward), (comp_frm, None)] w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame) w.bounding_box = ((0, 15), (0, 31), (0, 20), (0, 10), (0, 10), (0, 1)) w.array_shape = (2, 11, 11, 21, 32, 16) w.pixel_shape = (16, 32, 21, 11, 11, 2) return w
def homothetic_det2sky(input_center, angle, scale, output_center): """ Create the homothetic transform from a .pcf file. The forward direction is sky to detector. Parameters ---------- input_center : ndarray of shape (1,2) or iterable (x, y) coordinate of the input rotation center angle : float Rotation angle in degrees scale : ndarray of shape (1,2) or iterable Scaling factors in x, y directions output_center : ndarray of shape (1,2) or iterable (x, y) coordinate of the output rotation center """ input_center = np.array(input_center, dtype=np.float) output_center = np.array(output_center, dtype=np.float) scale = np.array(scale, dtype=np.float) angle = np.deg2rad(angle) rotmat_sky2det = [[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]] scaling = np.array([[1 / scale[0], 0], [0, 1 / scale[1]]]) mat_sky2det = np.dot(rotmat_sky2det, scaling) aff = models.AffineTransformation2D(matrix=mat_sky2det) transform = models.Shift(-output_center[0]) & models.Shift(-output_center[1]) | \ aff | models.Shift(input_center[0]) & models.Shift(input_center[1]) return transform
def time_init_7_no_units(): m = (models.Shift(-10.5) & models.Shift(-13.2) | models.AffineTransformation2D(matrix=[[1, 0], [0, 1]], translation=[0, 0]) | models.Scale(.01) & models.Scale(.04) | models.Pix2Sky_TAN() | models.RotateNative2Celestial(5.6, -72.05, 180))
def setup(self): aff = models.AffineTransformation2D(matrix=[[1, 0], [0, 1]], translation=[0, 0]) self.model = (models.Shift(-10.5) & models.Shift(-13.2) | aff | models.Scale(.01) & models.Scale(.04) | models.Pix2Sky_TAN() | models.RotateNative2Celestial(5.6, -72.05, 180))
def from_fits(cls, header): if not isinstance(header, fits.Header): raise TypeError("expected a FIST Header") fitswcs = util.read_wcs_from_header(header) wcsaxes = fitswcs['WCSAXES'] if fitswcs['has_cd']: pc = fitswcs['CD'] else: pc = fitswcs['PC'] # get the part of the PC matrix corresponding to the imaging axes sky_axes = None if pc.shape != (2, 2): sky_axes, _ = util.get_axes(fitswcs) i, j = sky_axes sky_pc = np.zeros((2,2)) sky_pc[0, 0] = pc[i, i] sky_pc[0, 1] = pc[i, j] sky_pc[1, 0] = pc[j, i] sky_pc[1, 1] = pc[j, j] pc = sky_pc.copy() if sky_axes is not None: crpix = [] cdelt = [] for i in sky_axes: crpix.append(fitswcs['CRPIX'][i]) cdelt.append(fitswcs['CDELT'][i]) else: cdelt = fitswcs['CDELT'] crpix = fitswcs['CRPIX'] translation = astmodels.Shift(-crpix[0], name='offset_x') & astmodels.Shift(-crpix[1], name='offset_y') rotation = astmodels.AffineTransformation2D(matrix=pc, name='orient') scale = astmodels.Scale(cdelt[0], name='scale_x') & astmodels.Scale(cdelt[1], name='scale_y') return cls(translation, rotation, scale)
def generate_celestial_transform(crpix: Union[Iterable[float], u.Quantity], cdelt: Union[Iterable[float], u.Quantity], pc: Union[ArrayLike, u.Quantity], crval: Union[Iterable[float], u.Quantity], lon_pole: Union[float, u.Quantity] = None, projection: Model = m.Pix2Sky_TAN()) -> CompoundModel: """ Create a simple celestial transform from FITS like parameters. Supports unitful or unitless parameters, but if any parameters have units all must have units, if parameters are unitless they are assumed to be in degrees. Parameters ---------- crpix The reference pixel (a length two array). crval The world coordinate at the reference pixel (a length two array). pc The rotation matrix for the affine transform. If specifying parameters with units this should have celestial (``u.deg``) units. lon_pole The longitude of the celestial pole, defaults to 180 degrees. projection The map projection to use, defaults to ``TAN``. Notes ----- This function has not been tested with more complex projections. Ensure that your lon_pole is correct for your projection. """ spatial_unit = None if hasattr(crval[0], "unit"): spatial_unit = crval[0].unit # TODO: Note this assumption is only valid for certain projections. if lon_pole is None: lon_pole = 180 if spatial_unit is not None: lon_pole = u.Quantity(lon_pole, unit=spatial_unit) # Make translation unitful if all parameters have units translation = (0, 0) if spatial_unit is not None: translation *= pc.unit # If we have units then we need to convert all things to Quantity # as they might be Parameter classes crpix = u.Quantity(crpix) cdelt = u.Quantity(cdelt) crval = u.Quantity(crval) lon_pole = u.Quantity(lon_pole) pc = u.Quantity(pc) shift = m.Shift(-crpix[0]) & m.Shift(-crpix[1]) scale = m.Multiply(cdelt[0]) & m.Multiply(cdelt[1]) rot = m.AffineTransformation2D(pc, translation=translation) skyrot = m.RotateNative2Celestial(crval[0], crval[1], lon_pole) return shift | scale | rot | projection | skyrot
def setup(self): aff = models.AffineTransformation2D(matrix=[[1, 0], [0, 1]] * u.arcsec, translation=[0, 0] * u.arcsec) aff.input_units_equivalencies = {'x': u.pixel_scale(1 * u.arcsec/u.pix), 'y': u.pixel_scale(1 * u.arcsec/u.pix)} self.model = (models.Shift(-10.5 * u.pix) & models.Shift(-13.2 * u.pix) | aff | models.Scale(.01 * u.arcsec) & models.Scale(.04 * u.deg) | models.Pix2Sky_TAN() | models.RotateNative2Celestial(5.6 * u.deg, -72.05 * u.deg, 180 * u.deg))
def test_transforms_compound(tmpdir): tree = { 'compound': astmodels.Shift(1) & astmodels.Shift(2) | astmodels.Sky2Pix_TAN() | astmodels.Rotation2D() | astmodels.AffineTransformation2D([[2, 0], [0, 2]], [42, 32]) + astmodels.Rotation2D(32) } helpers.assert_roundtrip_tree(tree, tmpdir)
def test_auto_inline_recursive(tmpdir): from astropy.modeling import models aff = models.AffineTransformation2D(matrix=[[1, 2], [3, 4]]) tree = {'test': aff} def check_asdf(asdf): assert len(list(asdf.blocks.internal_blocks)) == 0 helpers.assert_roundtrip_tree(tree, tmpdir, check_asdf, None, {'auto_inline': 64})
def test_to_fits_sip_pc_normalization(gwcs_simple_imaging_units, matrix_type): y, x = np.mgrid[:1024:10, :1024:10] xflat = np.ravel(x[1:-1, 1:-1]) yflat = np.ravel(y[1:-1, 1:-1]) bounding_box = ((0, 1024), (0, 1024)) # create a simple imaging WCS without distortions: cdmat = np.array([[1.29e-5, 5.95e-6], [5.02e-6, -1.26e-5]]) aff = models.AffineTransformation2D(matrix=cdmat, name='rotation') offx = models.Shift(-501, name='x_translation') offy = models.Shift(-501, name='y_translation') wcslin = (offx & offy) | aff n2c = models.RotateNative2Celestial(5.63, -72.05, 180, name='sky_rotation') tan = models.Pix2Sky_TAN(name='tangent_projection') wcs_forward = wcslin | tan | n2c sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky') pipeline = [('detector', wcs_forward), (sky_cs, None)] wcs_lin = wcs.WCS( input_frame=cf.Frame2D(name='detector'), output_frame=sky_cs, forward_transform=pipeline ) _, _, celestial_group = wcs_lin._separable_groups(detect_celestial=True) fits_wcs = wcs_lin._to_fits_sip( celestial_group=celestial_group, keep_axis_position=False, bounding_box=bounding_box, max_pix_error=0.1, degree=None, max_inv_pix_error=0.1, inv_degree=None, npoints=32, crpix=None, projection='TAN', matrix_type=matrix_type, verbose=False ) fitssip = astwcs.WCS(fits_wcs) fitsvalx, fitsvaly = fitssip.wcs_pix2world(xflat, yflat, 0) inv_fitsvalx, inv_fitsvaly = fitssip.wcs_world2pix(fitsvalx, fitsvaly, 0) gwcsvalx, gwcsvaly = wcs_lin(xflat, yflat) assert_allclose(gwcsvalx, fitsvalx, atol=4e-11, rtol=0) assert_allclose(gwcsvaly, fitsvaly, atol=4e-11, rtol=0) assert_allclose(xflat, inv_fitsvalx, atol=5e-9, rtol=0) assert_allclose(yflat, inv_fitsvaly, atol=5e-9, rtol=0)
def gwcs_spec_cel_time_4d(): """ A complex 4D mixed celestial + spectral + time WCS. """ # spectroscopic frame: wave_model = models.Shift(-5) | models.Multiply(3.7) | models.Shift(20) wave_model.bounding_box = (7, 50) wave_frame = cf.SpectralFrame(name='wave', unit=u.m, axes_order=(0, ), axes_names=('lambda', )) # time frame: time_model = models.Identity(1) # models.Linear1D(10, 0) time_frame = cf.TemporalFrame(Time("2010-01-01T00:00"), name='time', unit=u.s, axes_order=(3, )) # Values from data/acs.hdr: crpix = (12, 13) crval = (5.63, -72.05) cd = [[1.291E-05, 5.9532E-06], [5.02215E-06, -1.2645E-05]] aff = models.AffineTransformation2D(matrix=cd, name='rotation') offx = models.Shift(-crpix[0], name='x_translation') offy = models.Shift(-crpix[1], name='y_translation') wcslin = models.Mapping((1, 0)) | (offx & offy) | aff tan = models.Pix2Sky_TAN(name='tangent_projection') n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') cel_model = wcslin | tan | n2c icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=(2, 1)) wcs_forward = wave_model & cel_model & time_model comp_frm = cf.CompositeFrame(frames=[wave_frame, icrs, time_frame], name='TEST 4D FRAME') detector_frame = cf.CoordinateFrame(name="detector", naxes=4, axes_order=(0, 1, 2, 3), axes_type=("pixel", "pixel", "pixel", "pixel"), unit=(u.pix, u.pix, u.pix, u.pix)) w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame) w.bounding_box = ((0, 63), (0, 127), (0, 255), (0, 9)) w.array_shape = (10, 256, 128, 64) w.pixel_shape = (64, 128, 256, 10) return w
def test_auto_inline_recursive(tmpdir): astropy = pytest.importorskip('astropy') from astropy.modeling import models aff = models.AffineTransformation2D(matrix=[[1, 2], [3, 4]]) tree = {'test': aff} def check_asdf(asdf): assert len(list(asdf.blocks.internal_blocks)) == 0 helpers.assert_roundtrip_tree(tree, tmpdir, asdf_check_func=check_asdf, write_options={'auto_inline': 64})
def test_overwrite(tmpdir): # This is intended to reproduce the following issue: # https://github.com/asdf-format/asdf/issues/100 tmpfile = os.path.join(str(tmpdir), 'test.asdf') aff = models.AffineTransformation2D(matrix=[[1, 2], [3, 4]]) f = asdf.AsdfFile() f.tree['model'] = aff f.write_to(tmpfile) model = f.tree['model'] ff = asdf.AsdfFile() ff.tree['model'] = model ff.write_to(tmpfile)
def gwcs_cube_with_separable_time(request): """ A mixed celestial + time WCS. """ cube_size = (64, 32, 128) axes_order = request.param time_axes_order = (axes_order.index(2), ) cel_axes_order = (axes_order.index(0), axes_order.index(1)) detector_frame = cf.CoordinateFrame(name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), unit=(u.pix, u.pix, u.pix)) # time frame: time_model = models.Identity(1) # models.Linear1D(10, 0) time_frame = cf.TemporalFrame(Time("2010-01-01T00:00"), name='time', unit=u.s, axes_order=time_axes_order) # Values from data/acs.hdr: crpix = (12, 13) crval = (5.63, -72.05) cd = [[1.291E-05, 5.9532E-06], [5.02215E-06, -1.2645E-05]] aff = models.AffineTransformation2D(matrix=cd, name='rotation') offx = models.Shift(-crpix[0], name='x_translation') offy = models.Shift(-crpix[1], name='y_translation') wcslin = models.Mapping((1, 0)) | (offx & offy) | aff tan = models.Pix2Sky_TAN(name='tangent_projection') n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') cel_model = wcslin | tan | n2c icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=cel_axes_order) wcs_forward = (cel_model & time_model) | models.Mapping(axes_order) comp_frm = cf.CompositeFrame(frames=[icrs, time_frame], name='TEST 3D FRAME WITH TIME') w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame) w.bounding_box = tuple((0, k - 1) for k in cube_size) w.pixel_shape = cube_size w.array_shape = w.pixel_shape[::-1] return w
def gwcs_cube_with_separable_spectral(request): cube_size = (128, 64, 100) axes_order = request.param spectral_axes_order = (axes_order.index(2), ) cel_axes_order = (axes_order.index(0), axes_order.index(1)) # Values from data/acs.hdr: crpix = (64, 32) crval = (5.63056810618, -72.0545718428) cd = [[1.29058667557984E-05, 5.95320245884555E-06], [5.02215195623825E-06, -1.2645010396976E-05]] aff = models.AffineTransformation2D(matrix=cd, name='rotation') offx = models.Shift(-crpix[0], name='x_translation') offy = models.Shift(-crpix[1], name='y_translation') wcslin = (offx & offy) | aff tan = models.Pix2Sky_TAN(name='tangent_projection') n2c = models.RotateNative2Celestial(*crval, 180, name='sky_rotation') icrs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky', axes_order=cel_axes_order) spec = cf.SpectralFrame(name='wave', unit=[ u.m, ], axes_order=spectral_axes_order, axes_names=('lambda', )) comp_frm = cf.CompositeFrame(frames=[icrs, spec], name='TEST 3D FRAME WITH SPECTRAL AXIS') wcs_forward = ((wcslin & models.Identity(1)) | (tan & models.Identity(1)) | (n2c & models.Identity(1)) | models.Mapping(axes_order)) detector_frame = cf.CoordinateFrame(name="detector", naxes=3, axes_order=(0, 1, 2), axes_type=("pixel", "pixel", "pixel"), unit=(u.pix, u.pix, u.pix)) w = wcs.WCS(forward_transform=wcs_forward, output_frame=comp_frm, input_frame=detector_frame) w.bounding_box = tuple((0, k - 1) for k in cube_size) w.pixel_shape = cube_size w.array_shape = w.pixel_shape[::-1] return w, axes_order
def setup_class(self): hdr = fits.Header.fromtextfile(get_pkg_data_filename("data/acs.hdr"), endcard=False) #warnings.filterwarnings("ignore", message="^The WCS transformation has more axes (2)", # module="astropy.wcs.wcs") self.fitsw = astwcs.WCS(hdr) a_coeff = hdr['A_*'] a_order = a_coeff.pop('A_ORDER') b_coeff = hdr['B_*'] b_order = b_coeff.pop('B_ORDER') crpix = [hdr['CRPIX1'], hdr['CRPIX2']] distortion = models.SIP( crpix, a_order, b_order, a_coeff, b_coeff, name='sip_distorion') + models.Identity(2) cdmat = np.array([[hdr['CD1_1'], hdr['CD1_2']], [hdr['CD2_1'], hdr['CD2_2']]]) aff = models.AffineTransformation2D(matrix=cdmat, name='rotation') offx = models.Shift(-hdr['CRPIX1'], name='x_translation') offy = models.Shift(-hdr['CRPIX2'], name='y_translation') wcslin = (offx & offy) | aff phi = hdr['CRVAL1'] lon = hdr['CRVAL2'] theta = 180 n2c = models.RotateNative2Celestial(phi, lon, theta, name='sky_rotation') tan = models.Pix2Sky_TAN(name='tangent_projection') sky_cs = cf.CelestialFrame(reference_frame=coord.ICRS(), name='sky') det = cf.Frame2D('detector') wcs_forward = wcslin | tan | n2c pipeline = [('detector', distortion), ('focal', wcs_forward), (sky_cs, None)] self.wcs = wcs.WCS(input_frame=det, output_frame=sky_cs, forward_transform=pipeline) nx, ny = (5, 2) x = np.linspace(0, 1, nx) y = np.linspace(0, 1, ny) self.xv, self.yv = np.meshgrid(x, y)
def create_asdf_ref_files(fname): #f = fits.open('../j94f05bgq_flt.fits') f = fits.open(fname) from gwcs import util whdr = util.read_wcs_from_header(f[1].header) crpix = whdr['CRPIX'] shift = models.Shift(crpix[0]) & models.Shift(crpix[1]) rotation = models.AffineTransformation2D(matrix=whdr['PC']) cdelt = whdr['CDELT'] scale = models.Scale(cdelt[0]) & models.Scale(cdelt[1]) #print util.get_projcode(whdr['CTYPE'][0]) tan = models.Pix2Sky_TAN() crval = whdr['CRVAL'] n2c = models.RotateNative2Celestial(crval[0], crval[1], 180) foc2sky = (shift | rotation | scale | tan | n2c).rename('foc2sky') fasdf = AsdfFile() fasdf.tree = {'model': foc2sky} fasdf.write_to('foc2sky.asdf')
def homothetic_sky2det(input_center, angle, scale, output_center, name=""): """ Create the homothetic transform from a .pcf file. The forward direction is sky to detector. Parameters ---------- input_center : ndarray of shape (1,2) or iterable (x, y) coordinate of the input rotation center angle : float Rotation angle in degrees scale : ndarray of shape (1,2) or iterable Scaling factors in x, y directions output_center : ndarray of shape (1,2) or iterable (x, y) coordinate of the output rotation center """ input_center = np.array(input_center, dtype=np.float) output_center = np.array(output_center, dtype=np.float) scale = np.array(scale, dtype=np.float) angle = np.deg2rad(angle) rotmat_sky2det = [[np.cos(angle), np.sin(angle)], [-np.sin(angle), np.cos(angle)]] scaling = np.array([[scale[0], 0], [0, scale[1]]]) mat_sky2det = np.dot(scaling, rotmat_sky2det) aff = models.AffineTransformation2D(matrix=mat_sky2det, name="{0}_affine".format(name)) transform = models.Shift(-input_center[0], name="{0}_xincen_s2d".format(name)) & \ models.Shift(-input_center[1], name="{0}_yincen_s2d".format(name)) | aff | \ models.Shift(output_center[0], name="{0}_xoutcen_s2d".format(name)) & \ models.Shift(output_center[1], name="{0}_youtcen_s2d".format(name)) return transform
def fpa2asdf(fpafile, author, description, useafter): """ Create an asdf reference file with the FPA description. The CDP2 delivery includes a fits file - "FPA.fpa" which is the input to this function. This file is converted to asdf and is a reference file of type "FPA". fpa2asdf('Ref_Files/CoordTransform/Description/FPA.fpa', 'fpa.asdf') Parameters ---------- fpafile : str A fits file with FPA description (FPA.fpa) outname : str Name of output ASDF file. """ with open(fpafile) as f: lines = [l.strip() for l in f.readlines()] # NRS1 ind = lines.index("*SCA491_PitchX") nrs1_pitchx = float(lines[ind + 1]) ind = lines.index("*SCA491_PitchY") nrs1_pitchy = float(lines[ind + 1]) ind = lines.index("*SCA491_RotAngle") nrs1_angle = float(lines[ind + 1]) ind = lines.index("*SCA491_PosX") nrs1_posx = float(lines[ind + 1]) ind = lines.index("*SCA491_PosY") nrs1_posy = float(lines[ind + 1]) # NRS2 ind = lines.index("*SCA492_PitchX") nrs2_pitchx = float(lines[ind + 1]) ind = lines.index("*SCA492_PitchY") nrs2_pitchy = float(lines[ind + 1]) ind = lines.index("*SCA492_RotAngle") nrs2_angle = float(lines[ind + 1]) ind = lines.index("*SCA492_PosX") nrs2_posx = float(lines[ind + 1]) ind = lines.index("*SCA492_PosY") nrs2_posy = float(lines[ind + 1]) # NRS1 Sky to Detector scaling = np.array([[1 / nrs1_pitchx, 0], [0, 1 / nrs1_pitchy]]) rotmat = models.Rotation2D._compute_matrix(-nrs1_angle) matrix = np.dot(scaling, rotmat) aff = models.AffineTransformation2D(matrix, name='fpa_affine_s2d') nrs1_sky2det = models.Shift(-nrs1_posx, name='fpa_x_s2d') & \ models.Shift(-nrs1_posy, name='fpa_y_s2d') | aff # NRS1 Detector to Sky rotmat = models.Rotation2D._compute_matrix(-nrs1_angle) scaling = np.array([[nrs1_pitchx, 0], [0, nrs1_pitchy]]) matrix = np.dot(rotmat, scaling) aff = models.AffineTransformation2D(matrix, name='fpa_affine_d2s') nrs1_det2sky = aff | models.Shift(nrs1_posx, name='fpa_x_d2s') & \ models.Shift(nrs1_posy, name='fpa_y_d2s') nrs1_det2sky.inverse = nrs1_sky2det # NRS2 Sky to Detector scaling = np.array([[-1 / nrs2_pitchx, 0], [0, -1 / nrs2_pitchy]]) rotmat = models.Rotation2D._compute_matrix(-nrs2_angle) matrix = np.dot(scaling, rotmat) aff = models.AffineTransformation2D(matrix, name='fpa_affine_s2d') nrs2_sky2det = models.Shift(-nrs2_posx, name='fpa_x_s2d') & \ models.Shift(-nrs2_posy, name='fpa_y_s2d') | aff # NRS2 Detector to Sky rotmat = models.Rotation2D._compute_matrix(nrs2_angle) scaling = np.array([[-nrs2_pitchx, 0], [0, -nrs2_pitchy]]) matrix = np.dot(scaling, rotmat) aff = models.AffineTransformation2D(matrix, name='fpa_affine_d2s') nrs2_det2sky = aff | models.Shift(nrs2_posx, name='fpa_x_d2s') & \ models.Shift(nrs2_posy, name='fpa_y_d2s') nrs2_det2sky.inverse = nrs2_sky2det fpa_model = FPAModel() fpa_model.nrs1_model = nrs1_det2sky fpa_model.nrs2_model = nrs2_det2sky fpa_model.meta.author = author fpa_model.meta.description = description fpa_model.meta.useafter = useafter fpa_model.meta.pedigree = "GROUND" return fpa_model
def fitswcs_linear(header): """ Create a WCS linear transform from a FITS header. Parameters ---------- header : astropy.io.fits.Header or dict FITS Header or dict with basic FITS WCS keywords. """ if isinstance(header, fits.Header): wcs_info = read_wcs_from_header(header) elif isinstance(header, dict): wcs_info = header else: raise TypeError("Expected a FITS Header or a dict.") pc = wcs_info['PC'] # get the part of the PC matrix corresponding to the imaging axes sky_axes, spec_axes, unknown = get_axes(wcs_info) if pc.shape != (2, 2): if sky_axes: i, j = sky_axes elif unknown and len(unknown) == 2: i, j = unknown sky_pc = np.zeros((2, 2)) sky_pc[0, 0] = pc[i, i] sky_pc[0, 1] = pc[i, j] sky_pc[1, 0] = pc[j, i] sky_pc[1, 1] = pc[j, j] pc = sky_pc.copy() sky_axes.extend(unknown) if sky_axes: crpix = [] cdelt = [] for i in sky_axes: crpix.append(wcs_info['CRPIX'][i]) cdelt.append(wcs_info['CDELT'][i]) else: cdelt = wcs_info['CDELT'] crpix = wcs_info['CRPIX'] # if wcsaxes == 2: rotation = astmodels.AffineTransformation2D(matrix=pc, name='pc_matrix') # elif wcsaxes == 3 : # rotation = AffineTransformation3D(matrix=matrix) # else: # raise DimensionsError("WCSLinearTransform supports only 2 or 3 dimensions, " # "{0} given".format(wcsaxes)) translation_models = [ astmodels.Shift(-(shift - 1), name='crpix' + str(i + 1)) for i, shift in enumerate(crpix) ] translation = functools.reduce(lambda x, y: x & y, translation_models) if not wcs_info['has_cd']: # Do not compute scaling since CDELT* = 1 if CD is present. scaling_models = [ astmodels.Scale(scale, name='cdelt' + str(i + 1)) for i, scale in enumerate(cdelt) ] scaling = functools.reduce(lambda x, y: x & y, scaling_models) wcs_linear = translation | rotation | scaling else: wcs_linear = translation | rotation return wcs_linear
def wcs_from_footprints(dmodels, refmodel=None, transform=None, domain=None): """ Create a WCS from a list of input data models. A fiducial point in the output coordinate frame is created from the footprints of all WCS objects. For a spatial frame this is the center of the union of the footprints. For a spectral frame the fiducial is in the beginning of the footprint range. If ``refmodel`` is None, the first WCS object in the list is considered a reference. The output coordinate frame and projection (for celestial frames) is taken from ``refmodel``. If ``transform`` is not suplied, a compound transform is created using CDELTs and PC. If ``domain`` is not supplied, the domain of the new WCS is computed from the domains of all input WCSs. Parameters ---------- dmodels : list of `~jwst.datamodels.DataModel` A list of data models. refmodel : `~jwst.datamodels.DataModel`, optional This model's WCS is used as a reference. WCS. The output coordinate frame, the projection and a scaling and rotation transform is created from it. If not supplied the first model in the list is used as ``refmodel``. transform : `~astropy.modeling.core.Model`, optional A transform, passed to :class_method:`~gwcs.WCS.wcs_from_fiducial` If not supplied Scaling | Rotation is computed from ``refmodel``. domain : list of dicts, optional Domain of the new WCS. If not supplied it is computed from the domain of all inputs. """ wcslist = [im.meta.wcs for im in dmodels] if not isiterable(wcslist): raise ValueError( "Expected 'wcslist' to be an iterable of WCS objects.") if not all([isinstance(w, WCS) for w in wcslist]): raise TypeError( "All items in wcslist are to be instances of gwcs.WCS.") if refmodel is None: refmodel = dmodels[0] else: if not isinstance(refmodel, DataModel): raise TypeError( "Expected refmodel to be an instance of DataModel.") fiducial = compute_fiducial(wcslist, domain) prj = astmodels.Pix2Sky_TAN() if transform is None: transform = [] wcsinfo = pointing.wcsinfo_from_model(refmodel) sky_axes, _ = gwutils.get_axes(wcsinfo) rotation = astmodels.AffineTransformation2D(np.array(wcsinfo['PC'])) transform.append(rotation) if sky_axes: scale = wcsinfo['CDELT'][sky_axes].mean() scales = astmodels.Scale(scale) & astmodels.Scale(scale) transform.append(scales) if transform: transform = functools.reduce(lambda x, y: x | y, transform) out_frame = refmodel.meta.wcs.output_frame wnew = wcs_from_fiducial(fiducial, coordinate_frame=out_frame, projection=prj, transform=transform) footprints = [w.footprint() for w in wcslist] domain_bounds = np.hstack( [wnew.backward_transform(*f) for f in footprints]) for axs in domain_bounds: axs -= axs.min() domain = [] for axis in out_frame.axes_order: axis_min, axis_max = domain_bounds[axis].min( ), domain_bounds[axis].max() domain.append({ 'lower': axis_min, 'upper': axis_max, 'includes_lower': True, 'includes_upper': True }) #ax1, ax2 = domain[sky_axes] # change when domain is a bounding_box ax1, ax2 = domain offset1 = (ax1['upper'] - ax1['lower']) / 2 offset2 = (ax2['upper'] - ax2['lower']) / 2 offsets = astmodels.Shift(-offset1) & astmodels.Shift(-offset2) wnew.insert_transform('detector', offsets, after=True) wnew.domain = domain return wnew
def make_gwcs(shape, galactic=False): """ Create a simple celestial gWCS object in the ICRS coordinate frame. This function requires the `gwcs <https://github.com/spacetelescope/gwcs>`_ package. Parameters ---------- shape : 2-tuple of int The shape of the 2D array to be used with the output `~gwcs.wcs.WCS` object. galactic : bool, optional If `True`, then the output WCS will be in the Galactic coordinate frame. If `False` (default), then the output WCS will be in the ICRS coordinate frame. Returns ------- wcs : `gwcs.wcs.WCS` object The generalized world coordinate system (WCS) transformation. See Also -------- make_wcs, make_imagehdu Notes ----- The `make_wcs` function returns an equivalent WCS transformation to this one, but as an `astropy.wcs.WCS` object. Examples -------- >>> from photutils.datasets import make_gwcs >>> shape = (100, 100) >>> gwcs = make_gwcs(shape) >>> print(gwcs) From Transform -------- ---------------- detector linear_transform icrs None """ from gwcs import wcs as gwcs_wcs from gwcs import coordinate_frames as cf rho = np.pi / 3. scale = 0.1 / 3600. # 0.1 arcsec/pixel in deg/pix shift_by_crpix = (models.Shift((-shape[1] / 2) + 1) & models.Shift((-shape[0] / 2) + 1)) cd_matrix = np.array([[-scale * np.cos(rho), scale * np.sin(rho)], [scale * np.sin(rho), scale * np.cos(rho)]]) rotation = models.AffineTransformation2D(cd_matrix, translation=[0, 0]) rotation.inverse = models.AffineTransformation2D(np.linalg.inv(cd_matrix), translation=[0, 0]) tan = models.Pix2Sky_TAN() celestial_rotation = models.RotateNative2Celestial(197.8925, -1.36555556, 180.0) det2sky = shift_by_crpix | rotation | tan | celestial_rotation det2sky.name = 'linear_transform' detector_frame = cf.Frame2D(name='detector', axes_names=('x', 'y'), unit=(u.pix, u.pix)) if galactic: sky_frame = cf.CelestialFrame(reference_frame=coord.Galactic(), name='galactic', unit=(u.deg, u.deg)) else: sky_frame = cf.CelestialFrame(reference_frame=coord.ICRS(), name='icrs', unit=(u.deg, u.deg)) pipeline = [(detector_frame, det2sky), (sky_frame, None)] return gwcs_wcs.WCS(pipeline)
def fitswcs_image(header): """ Make a complete transform from CRPIX-shifted pixels to sky coordinates from FITS WCS keywords. A Mapping is inserted at the beginning, which may be removed later Parameters ---------- header : `astropy.io.fits.Header` or dict FITS Header or dict with basic FITS WCS keywords. """ if isinstance(header, fits.Header): wcs_info = read_wcs_from_header(header) elif isinstance(header, dict): wcs_info = header else: raise TypeError("Expected a FITS Header or a dict.") crpix = wcs_info['CRPIX'] cd = wcs_info['CD'] # get the part of the PC matrix corresponding to the imaging axes sky_axes, spec_axes, unknown = get_axes(wcs_info) if not sky_axes: if len(unknown) == 2: sky_axes = unknown else: # No sky here return pixel_axes = _get_contributing_axes(wcs_info, sky_axes) if len(pixel_axes) > 2: raise ValueError( "More than 2 pixel axes contribute to the sky coordinates") translation_models = [ models.Shift(-(crpix[i] - 1), name='crpix' + str(i + 1)) for i in pixel_axes ] translation = functools.reduce(lambda x, y: x & y, translation_models) transforms = [translation] # If only one axis is contributing to the sky (e.g., slit spectrum) # then it must be that there's an extra axis in the CD matrix, so we # create a "ghost" orthogonal axis here so an inverse can be defined # Modify the CD matrix in case we have to use a backup Matrix Model later if len(pixel_axes) == 1: cd[sky_axes[0], -1] = -cd[sky_axes[1], pixel_axes[0]] cd[sky_axes[1], -1] = cd[sky_axes[0], pixel_axes[0]] sky_cd = cd[np.ix_(sky_axes, pixel_axes + [-1])] affine = models.AffineTransformation2D(matrix=sky_cd, name='cd_matrix') # TODO: replace when PR#10362 is in astropy #rotation = models.fix_inputs(affine, {'y': 0}) rotation = models.Mapping( (0, 0)) | models.Identity(1) & models.Const1D(0) | affine rotation.inverse = affine.inverse | models.Mapping((0, ), n_inputs=2) else: sky_cd = cd[np.ix_(sky_axes, pixel_axes)] rotation = models.AffineTransformation2D(matrix=sky_cd, name='cd_matrix') transforms.append(rotation) projection = gwutils.fitswcs_nonlinear(wcs_info) if projection: transforms.append(projection) sky_model = functools.reduce(lambda x, y: x | y, transforms) sky_model.name = 'SKY' sky_model.meta.update({'input_axes': pixel_axes, 'output_axes': sky_axes}) return sky_model
def _attach_static_distortion(self, adinputs=None): """ This primitive modifies the WCS of its input AD objects to include the static distortion read from the lookup table. It will ignore any ADs which have CoordinateFrames named "static" on all the extensions, and raise an OSError if any ADs have "static" on some (but not all) extensions. """ log = self.log # Check the inputs haven't been mosaicked or tiled mosaic_kws = { self.timestamp_keys[prim] for prim in ("mosaicDetectors", "tileArrays") } if any(mosaic_kws.intersection(ad.phu) for ad in adinputs): raise ValueError(f"Inputs to {self.myself()} must not have been " "mosaicked or tiled.") static_corrections = gsdi.STATIC_CORRECTIONS sdmodels = [] sdsubmodels = {} for direction in ("forward", "backward"): sdsubmodels[direction] = [] for component in static_corrections[direction]: model_type = getattr(models, component["model"]) for arr in component["parameters"]: for ordinate in "xy": pars = arr[ordinate] max_xpower = max([int(k[1]) for k in pars]) max_ypower = max([int(k[-1]) for k in pars]) if component["model"] == "Polynomial2D": degree = {"degree": max(max_xpower, max_ypower)} else: degree = { "xdegree": max_xpower, "ydegree": max_ypower } sdsubmodels[direction].append( model_type(**degree, **pars)) for index, pixref in enumerate(static_corrections["pixel_references"]): sdmodel = (models.Mapping((0, 1, 0, 1)) | (reduce( Model.__add__, sdsubmodels["forward"][index * 2::8]) & reduce( Model.__add__, sdsubmodels["forward"][index * 2 + 1::8]))) sdmodel.inverse = (models.Mapping((0, 1, 0, 1)) | (reduce( Model.__add__, sdsubmodels["backward"][index * 2::8]) & reduce( Model.__add__, sdsubmodels["backward"][index * 2 + 1::8]))) xref, yref = sdmodel.inverse(0, 0) if 0 < xref < 2048 and 0 < yref < 2048: ref_location = (index, xref - 1, yref - 1 ) # store 0-indexed pixel location sdmodels.append(sdmodel) for ad in adinputs: applied_static = sum("static" in ext.wcs.available_frames for ext in ad) if applied_static not in (0, len(ad)): raise OSError( f"Some (but not all) extensions in {ad.filename}" " have had the static disortion correction applied") # No-op silently if applied_static == len(ad): continue ref_wcs = ad[ref_location[0]].wcs ra, dec = ref_wcs(*ref_location[1:]) for ext, arrsec, sdmodel in zip(ad, ad.array_section(), sdmodels): # Include ROI shift sdmodel = (models.Shift(arrsec.x1 + 1) & models.Shift(arrsec.y1 + 1) | sdmodel) static_frame = cf.Frame2D(unit=(u.arcsec, u.arcsec), name="static") sky_model = models.Scale(1 / 3600) & models.Scale(1 / 3600) # the PA header keyword isn't good enough wcs_dict = adwcs.gwcs_to_fits(ad[ref_location[0]].nddata) pa = np.arctan2(wcs_dict["CD2_1"] - wcs_dict["CD1_2"], wcs_dict["CD1_1"] + wcs_dict["CD2_2"]) if abs(pa) > 0.00175: # radians ~ 0.01 degrees # Rotation2D() breaks things because it explicitly converts # the Column objects to Quantity objects, which retain units # of "pix" that Pix2Sky objects to. The AffineTransformation2D # does some multiplications (like Scale) which are agnostic to # the presence or absence of units. sky_model |= models.AffineTransformation2D( matrix=[[math.cos(pa), -math.sin(pa)], [math.sin(pa), math.cos(pa)]]) sky_model |= models.Pix2Sky_TAN( ) | models.RotateNative2Celestial(ra, dec, 180) ext.wcs = gWCS([(ext.wcs.input_frame, sdmodel), (static_frame, sky_model), (ext.wcs.output_frame, None)]) return adinputs
def wcs_from_footprints(dmodels, refmodel=None, transform=None, bounding_box=None, domain=None): """ Create a WCS from a list of input data models. A fiducial point in the output coordinate frame is created from the footprints of all WCS objects. For a spatial frame this is the center of the union of the footprints. For a spectral frame the fiducial is in the beginning of the footprint range. If ``refmodel`` is None, the first WCS object in the list is considered a reference. The output coordinate frame and projection (for celestial frames) is taken from ``refmodel``. If ``transform`` is not suplied, a compound transform is created using CDELTs and PC. If ``bounding_box`` is not supplied, the bounding_box of the new WCS is computed from bounding_box of all input WCSs. Parameters ---------- dmodels : list of `~jwst.datamodels.DataModel` A list of data models. refmodel : `~jwst.datamodels.DataModel`, optional This model's WCS is used as a reference. WCS. The output coordinate frame, the projection and a scaling and rotation transform is created from it. If not supplied the first model in the list is used as ``refmodel``. transform : `~astropy.modeling.core.Model`, optional A transform, passed to :meth:`~gwcs.wcstools.wcs_from_fiducial` If not supplied Scaling | Rotation is computed from ``refmodel``. bounding_box : tuple, optional Bounding_box of the new WCS. If not supplied it is computed from the bounding_box of all inputs. """ if domain is not None: warnings.warning( "'domain' was deprecated in 0.8 and will be removed from next" "version. Use 'bounding_box' instead.") bb = _domain_to_bounding_box(domain) else: bb = bounding_box wcslist = [im.meta.wcs for im in dmodels] if not isiterable(wcslist): raise ValueError( "Expected 'wcslist' to be an iterable of WCS objects.") if not all([isinstance(w, WCS) for w in wcslist]): raise TypeError( "All items in wcslist are to be instances of gwcs.WCS.") if refmodel is None: refmodel = dmodels[0] else: if not isinstance(refmodel, DataModel): raise TypeError( "Expected refmodel to be an instance of DataModel.") fiducial = compute_fiducial(wcslist, bb) prj = astmodels.Pix2Sky_TAN() if transform is None: transform = [] wcsinfo = pointing.wcsinfo_from_model(refmodel) sky_axes, spec, other = gwutils.get_axes(wcsinfo) rotation = astmodels.AffineTransformation2D(wcsinfo['PC']) transform.append(rotation) if sky_axes: cdelt1, cdelt2 = wcsinfo['CDELT'][sky_axes] scale = np.sqrt(np.abs(cdelt1 * cdelt2)) scales = astmodels.Scale(scale) & astmodels.Scale(scale) transform.append(scales) if transform: transform = functools.reduce(lambda x, y: x | y, transform) out_frame = refmodel.meta.wcs.output_frame wnew = wcs_from_fiducial(fiducial, coordinate_frame=out_frame, projection=prj, transform=transform) footprints = [w.footprint().T for w in wcslist] domain_bounds = np.hstack( [wnew.backward_transform(*f) for f in footprints]) for axs in domain_bounds: axs -= axs.min() bounding_box = [] for axis in out_frame.axes_order: axis_min, axis_max = domain_bounds[axis].min( ), domain_bounds[axis].max() bounding_box.append((axis_min, axis_max)) bounding_box = tuple(bounding_box) ax1, ax2 = np.array(bounding_box)[sky_axes] offset1 = (ax1[1] - ax1[0]) / 2 offset2 = (ax2[1] - ax2[0]) / 2 offsets = astmodels.Shift(-offset1) & astmodels.Shift(-offset2) wnew.insert_transform('detector', offsets, after=True) wnew.bounding_box = bounding_box return wnew
def wcs_from_footprints(dmodels, refmodel=None, transform=None, bounding_box=None, pscale_ratio=None): """ Create a WCS from a list of input data models. A fiducial point in the output coordinate frame is created from the footprints of all WCS objects. For a spatial frame this is the center of the union of the footprints. For a spectral frame the fiducial is in the beginning of the footprint range. If ``refmodel`` is None, the first WCS object in the list is considered a reference. The output coordinate frame and projection (for celestial frames) is taken from ``refmodel``. If ``transform`` is not suplied, a compound transform is created using CDELTs and PC. If ``bounding_box`` is not supplied, the bounding_box of the new WCS is computed from bounding_box of all input WCSs. Parameters ---------- dmodels : list of `~jwst.datamodels.DataModel` A list of data models. refmodel : `~jwst.datamodels.DataModel`, optional This model's WCS is used as a reference. WCS. The output coordinate frame, the projection and a scaling and rotation transform is created from it. If not supplied the first model in the list is used as ``refmodel``. transform : `~astropy.modeling.core.Model`, optional A transform, passed to :meth:`~gwcs.wcstools.wcs_from_fiducial` If not supplied Scaling | Rotation is computed from ``refmodel``. bounding_box : tuple, optional Bounding_box of the new WCS. If not supplied it is computed from the bounding_box of all inputs. pscale_ratio : float, optional Ratio of input to output pixel scale. """ bb = bounding_box wcslist = [im.meta.wcs for im in dmodels] if not isiterable(wcslist): raise ValueError("Expected 'wcslist' to be an iterable of WCS objects.") if not all([isinstance(w, WCS) for w in wcslist]): raise TypeError("All items in wcslist are to be instances of gwcs.WCS.") if refmodel is None: refmodel = dmodels[0] else: if not isinstance(refmodel, DataModel): raise TypeError("Expected refmodel to be an instance of DataModel.") fiducial = compute_fiducial(wcslist, bb) ref_fiducial = compute_fiducial([refmodel.meta.wcs]) prj = astmodels.Pix2Sky_TAN() if transform is None: transform = [] wcsinfo = pointing.wcsinfo_from_model(refmodel) sky_axes, spec, other = gwutils.get_axes(wcsinfo) # Need to put the rotation matrix (List[float, float, float, float]) # returned from calc_rotation_matrix into the correct shape for # constructing the transformation roll_ref = np.deg2rad(refmodel.meta.wcsinfo.roll_ref) v3yangle = np.deg2rad(refmodel.meta.wcsinfo.v3yangle) vparity = refmodel.meta.wcsinfo.vparity pc = np.reshape( calc_rotation_matrix(roll_ref, v3yangle, vparity=vparity), (2, 2) ) rotation = astmodels.AffineTransformation2D(pc) transform.append(rotation) if sky_axes: scale = compute_scale(refmodel.meta.wcs, ref_fiducial, pscale_ratio=pscale_ratio) transform.append(astmodels.Scale(scale) & astmodels.Scale(scale)) if transform: transform = functools.reduce(lambda x, y: x | y, transform) out_frame = refmodel.meta.wcs.output_frame input_frame = dmodels[0].meta.wcs.input_frame wnew = wcs_from_fiducial(fiducial, coordinate_frame=out_frame, projection=prj, transform=transform, input_frame=input_frame) footprints = [w.footprint().T for w in wcslist] domain_bounds = np.hstack([wnew.backward_transform(*f) for f in footprints]) for axs in domain_bounds: axs -= (axs.min() + .5) output_bounding_box = [] for axis in out_frame.axes_order: axis_min, axis_max = domain_bounds[axis].min(), domain_bounds[axis].max() output_bounding_box.append((axis_min, axis_max)) output_bounding_box = tuple(output_bounding_box) ax1, ax2 = np.array(output_bounding_box)[sky_axes] offset1 = (ax1[1] - ax1[0]) / 2 offset2 = (ax2[1] - ax2[0]) / 2 offsets = astmodels.Shift(-offset1) & astmodels.Shift(-offset2) wnew.insert_transform('detector', offsets, after=True) wnew.bounding_box = output_bounding_box return wnew
def fpa2asdf(fpafile, outname, ref_kw): """ Create an asdf reference file with the FPA description. The CDP2 delivery includes a fits file - "FPA.fpa" which is the input to this function. This file is converted to asdf and is a reference file of type "FPA". nirspec_fs_ref_tools.fpa2asdf('Ref_Files/CoordTransform/Description/FPA.fpa', 'fpa.asdf') Parameters ---------- fpafile : str A fits file with FPA description (FPA.fpa) outname : str Name of output ASDF file. """ with open(fpafile) as f: lines = [l.strip() for l in f.readlines()] # NRS1 ind = lines.index("*SCA491_PitchX") nrs1_pitchx = float(lines[ind + 1]) ind = lines.index("*SCA491_PitchY") nrs1_pitchy = float(lines[ind + 1]) ind = lines.index("*SCA491_RotAngle") nrs1_angle = float(lines[ind + 1]) ind = lines.index("*SCA491_PosX") nrs1_posx = float(lines[ind + 1]) ind = lines.index("*SCA491_PosY") nrs1_posy = float(lines[ind + 1]) # NRS2 ind = lines.index("*SCA492_PitchX") nrs2_pitchx = float(lines[ind + 1]) ind = lines.index("*SCA492_PitchY") nrs2_pitchy = float(lines[ind + 1]) ind = lines.index("*SCA492_RotAngle") nrs2_angle = float(lines[ind + 1]) ind = lines.index("*SCA492_PosX") nrs2_posx = float(lines[ind + 1]) ind = lines.index("*SCA492_PosY") nrs2_posy = float(lines[ind + 1]) tree = ref_kw.copy() # NRS1 Sky to Detector scaling = np.array([[1 / nrs1_pitchx, 0], [0, 1 / nrs1_pitchy]]) rotmat = models.Rotation2D._compute_matrix(-nrs1_angle) matrix = np.dot(rotmat, scaling) aff = models.AffineTransformation2D(matrix, name='fpa_affine_sky2detector') nrs1_sky2det = models.Shift(-nrs1_posx, name='fpa_shift_x') & \ models.Shift(-nrs1_posy, name='fpa_shift_y') | aff # NRS1 Detector to Sky rotmat = models.Rotation2D._compute_matrix(-nrs1_angle) scaling = np.array([[nrs1_pitchx, 0], [0, nrs1_pitchy]]) matrix = np.dot(rotmat, scaling) aff = models.AffineTransformation2D(matrix, name='fpa_affine_detector2sky') nrs1_det2sky = aff | models.Shift(nrs1_posx, name='fpa_shift_x_det2sky') & \ models.Shift(nrs1_posy, name='fpa_shift_y_det2sky') nrs1_det2sky.inverse = nrs1_sky2det # NRS2 Sky to Detector scaling = np.array([[-1 / nrs2_pitchx, 0], [0, -1 / nrs2_pitchy]]) rotmat = models.Rotation2D._compute_matrix(-nrs2_angle) matrix = np.dot(rotmat, scaling) aff = models.AffineTransformation2D(matrix, name='fpa_affine_sky2detector') nrs2_sky2det = models.Shift(-nrs2_posx, name='fpa_shixft_x') & \ models.Shift(-nrs2_posy, name='fpa_shift_y') | aff # NRS2 Detector to Sky rotmat = models.Rotation2D._compute_matrix(nrs2_angle) scaling = np.array([[-nrs2_pitchx, 0], [0, -nrs2_pitchy]]) matrix = np.dot(rotmat, scaling) aff = models.AffineTransformation2D(matrix, name='fpa_affine_detector2sky') nrs2_det2sky = aff | models.Shift(nrs2_posx, name='fpa_shift_x_det2sky') & \ models.Shift(nrs2_posy, name='fpa_shift_y_det2sky') nrs2_det2sky.inverse = nrs2_sky2det tree['NRS1'] = nrs1_det2sky tree['NRS2'] = nrs2_det2sky fasdf = AsdfFile() fasdf.tree = tree fasdf.add_history_entry("Build 6") fasdf.write_to(outname) return fasdf