def ifu_msa_to_oteip(reference_files): """ Transform from the MSA frame to the OTEIP frame. Parameters ---------- reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~astropy.modeling.core.Model` model. Transform from MSA to OTEIP. """ with AsdfFile.open(reference_files['fore']) as f: fore = f.tree['model'].copy() with AsdfFile.open(reference_files['ifufore']) as f: ifufore = f.tree['model'].copy() msa2fore_mapping = Mapping((0, 1, 2, 2)) msa2fore_mapping.inverse = Identity(3) ifu_fore_transform = ifufore & Identity(1) ifu_fore_transform.inverse = Mapping( (0, 1, 2, 2)) | ifufore.inverse & Identity(1) fore_transform = msa2fore_mapping | fore & Identity(1) return msa2fore_mapping | ifu_fore_transform | fore_transform
def oteip_to_v23(reference_files): """ Transform from the OTEIP frame to the V2V3 frame. Parameters ---------- reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~astropy.modeling.core.Model` model. Transform from OTEIP to V2V3. """ with AsdfFile.open(reference_files['ote']) as f: ote = f.tree['model'].copy() fore2ote_mapping = Identity(3, name='fore2ote_mapping') fore2ote_mapping.inverse = Mapping((0, 1, 2, 2)) # Create the transform to v2/v3/lambda. The wavelength units up to this point are # meters as required by the pipeline but the desired output wavelength units is microns. # So we are going to Scale the spectral units by 1e6 (meters -> microns) # The spatial units are currently in deg. Convertin to arcsec. oteip_to_xyan = fore2ote_mapping | (ote & Scale(1e6)) # Add a shift for the aperture. oteip2v23 = oteip_to_xyan | Identity(1) & (Shift(468 / 3600) | Scale(-1)) & Identity(1) return oteip2v23
def run_test(model): wcsobj = model.meta.wcs for ch in model.meta.instrument.channel: ref_data = mrs_ref_data[ch + band_mapping[model.meta.instrument.band]] detector_to_alpha_beta = wcsobj.get_transform('detector', 'alpha_beta') #ab_to_xan_yan = wcsobj.get_transform('alpha_beta', 'Xan_Yan').set_input(int(ch)) ab_to_v2v3 = wcsobj.get_transform('alpha_beta', 'v2v3').set_input(int(ch)) ab_to_xan_yan = ab_to_v2v3 | Scale(1 / 60) & Scale(1 / 60) & Identity( 1) | Identity(1) & (Scale(-1) | Shift(-7.8)) & Identity(1) ref_alpha = ref_data['alpha'] ref_beta = ref_data['beta'] ref_lam = ref_data['lam'] x, y = ref_data['x'], ref_data['y'] for i, s in enumerate(ref_data['s']): sl = int(ch) * 100 + s alpha, beta, lam = detector_to_alpha_beta.set_input(sl)(x[i], y[i]) utils.assert_allclose(alpha, ref_alpha[i], atol=10**-4) utils.assert_allclose(beta, ref_beta[i], atol=10**-4) utils.assert_allclose(lam, ref_lam[i], atol=10**-4) xan, yan, lam = ab_to_xan_yan(ref_alpha, ref_beta, ref_lam) utils.assert_allclose(xan, ref_data['v2'], atol=10**-4) utils.assert_allclose(yan, ref_data['v3'], atol=10**-4) utils.assert_allclose(lam, ref_data['lam'], atol=10**-4)
def ifupost2asdf(ifupost_files, outname): """ Create a reference file of type ``ifupost`` . Combines all IDT ``IFU-POST`` reference files in one ASDF file. forward direction : MSA to Collimator backward_direction: Collimator to MSA Parameters ---------- ifupost_files : list Names of all ``IFU-POST`` IDT reference files outname : str Name of output ``ASDF`` file """ ref_kw = common_reference_file_keywords("IFUPOST", "NIRSPEC IFU-POST transforms - CDP4") fa = AsdfFile() fa.tree = ref_kw for fifu in ifupost_files: n = int((fifu.split('IFU-POST_')[1]).split('.pcf')[0]) fa.tree[n] = {} with open(fifu) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2') + 1].split() rotation_angle = float(lines[lines.index('*Rotation') + 1]) input_rot_center = lines[lines.index('*InputRotationCentre 2') + 1].split() output_rot_center = lines[lines.index('*OutputRotationCentre 2') + 1].split() linear_sky2det = homothetic_sky2det(input_rot_center, rotation_angle, factors, output_rot_center) degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_backward) output2poly_mapping = Identity(2, name='output_mapping') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='input_mapping') input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = linear_sky2det | model_poly fa.tree[n]['model'] = model asdffile = fa.write_to(outname) return asdffile
def _convert_item_to_models(self, item, drop_all_non_separable): inputs = [] prepend = [] axes_to_drop = [] # Iterate over all the axes and keep a list of models prepend to the # transform, and a list of axes to remove from the wcs completely. # We always add a model to prepend list so that we maintain consistency # with the number of axes. If prepend is entirely identity models, it # is not used. input_units = self._input_units() for i, ax in enumerate(item): if isinstance(ax, int): if self.separable[i]: axes_to_drop.append(i) elif not self.separable[i] and drop_all_non_separable: axes_to_drop.append(i) else: inputs.append(ax * input_units[i]) prepend.append(Identity(1)) elif ax.start: inputs.append(None) prepend.append(Shift(ax.start * input_units[i])) else: inputs.append(None) prepend.append(Identity(1)) return inputs, prepend, axes_to_drop
def mask_slit(ymin=-.5, ymax=.5): """ Returns a model which masks out pixels in a NIRSpec cutout outside the slit. Uses ymin, ymax for the slit and the wavelength range to define the location of the slit. Parameters ---------- ymin, ymax : float ymin and ymax relative boundary of a slit. Returns ------- model : `~astropy.modeling.core.Model` A model which takes x_slit, y_slit, lam inputs and substitutes the values outside the slit with NaN. """ greater_than_ymax = Logical(condition='GT', compareto=ymax, value=np.nan) less_than_ymin = Logical(condition='LT', compareto=ymin, value=np.nan) model = Mapping((0, 1, 2, 1)) | Identity(3) & (greater_than_ymax | less_than_ymin | models.Scale(0)) | \ Mapping((0, 1, 3, 2, 3)) | Identity(1) & Mapping((0,), n_inputs=2) + Mapping((1,)) & \ Mapping((0,), n_inputs=2) + Mapping((1,)) model.inverse = Identity(3) return model
def setup_class(cls): cls.model1D = Identity(n_inputs=1) cls.model2D = Identity(n_inputs=2) | Mapping((0, ), n_inputs=2) cls.model3D = Identity(n_inputs=3) | Mapping((0, ), n_inputs=3) cls.data = cls.x = cls.y = cls.z = np.linspace(0, 10, num=100) cls.lsq_exp = 0
def pcf_forward(pcffile, outname): """ Create the **IDT** forward transform from collimator to gwa. """ with open(pcffile) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2') + 1].split() # factor==1/factor in backward msa2ote direction and factor==factor in sky2detector direction scale = models.Scale(float(factors[0]), name="x_scale") & \ models.Scale(float(factors[1]), name="y_scale") rotation_angle = lines[lines.index('*Rotation') + 1] # The minius sign here is because astropy.modeling has the opposite direction of rotation than the idl implementation rotation = models.Rotation2D(-float(rotation_angle), name='rotation') # Here the model is called "output_shift" but in the team version it is the "input_shift". input_rot_center = lines[lines.index('*InputRotationCentre 2') + 1].split() input_rot_shift = models.Shift(-float(input_rot_center[0]), name='input_x_shift') & \ models.Shift(-float(input_rot_center[1]), name='input_y_shift') # Here the model is called "input_shift" but in the team version it is the "output_shift". output_rot_center = lines[lines.index('*OutputRotationCentre 2') + 1].split() output_rot_shift = models.Shift(float(output_rot_center[0]), name='output_x_shift') & \ models.Shift(float(output_rot_center[1]), name='output_y_shift') degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_backward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward poly_mapping1 = Mapping((0, 1, 0, 1)) poly_mapping1.inverse = Identity(2) poly_mapping2 = Identity(2) poly_mapping2.inverse = Mapping((0, 1, 0, 1)) model = input_rot_shift | rotation | scale | output_rot_shift | \ poly_mapping1 | x_poly_forward & y_poly_forward | poly_mapping2 f = AsdfFile() f.tree = {'model': model} f.write_to(outname)
def ifu(input_model, reference_files): """ IFU pipeline """ slits = np.arange(30) # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange(input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder # DMS to SCA transform dms2detector = dms_to_sca(input_model) # DETECTOR to GWA transform det2gwa = Identity(2) & detector_to_gwa(reference_files, input_model.meta.instrument.detector, disperser) # GWA to SLIT gwa2slit = gwa_to_ifuslit(slits, disperser, wrange, sporder, reference_files) # SLIT to MSA transform slit2msa = ifuslit_to_msa(slits, reference_files) det, sca, gwa, slit_frame, msa_frame, oteip, v2v3, world = create_frames() if input_model.meta.instrument.filter != 'OPAQUE': # MSA to OTEIP transform msa2oteip = ifu_msa_to_oteip(reference_files) # OTEIP to V2,V3 transform # This includes a wavelength unit conversion from meters to microns. oteip2v23 = oteip_to_v23(reference_files) # Create coordinate frames in the NIRSPEC WCS pipeline" # # The oteip2v2v3 transform converts the wavelength from meters (which is assumed # in the whole pipeline) to microns (which is the expected output) # # "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world" pipeline = [(det, dms2detector), (sca, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping((0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa')), (msa_frame, msa2oteip.rename('msa2oteip')), (oteip, oteip2v23.rename('oteip2v23')), (v2v3, None)] else: # convert to microns if the pipeline ends earlier #slit2msa = (Mapping((0, 1, 2, 3, 4)) | slit2msa | Identity(2) & Scale(10**6)).rename('slit2msa') slit2msa = (Mapping((0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa') pipeline = [(det, dms2detector), (sca, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, slit2msa), (msa_frame, None)] return pipeline
def ifu(input_model, reference_files): """ IFU pipeline """ slits = np.arange(30) # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange( input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder # DMS to SCA transform dms2detector = dms_to_sca(input_model) # DETECTOR to GWA transform det2gwa = Identity(2) & detector_to_gwa( reference_files, input_model.meta.instrument.detector, disperser) # GWA to SLIT gwa2slit = gwa_to_ifuslit(slits, disperser, wrange, sporder, reference_files) # SLIT to MSA transform slit2msa = ifuslit_to_msa(slits, reference_files) det, sca, gwa, slit_frame, msa_frame, oteip, v2v3, world = create_frames() if input_model.meta.instrument.filter != 'OPAQUE': # MSA to OTEIP transform msa2oteip = ifu_msa_to_oteip(reference_files) # OTEIP to V2,V3 transform # This includes a wavelength unit conversion from meters to microns. oteip2v23 = oteip_to_v23(reference_files) # Create coordinate frames in the NIRSPEC WCS pipeline" # # The oteip2v2v3 transform converts the wavelength from meters (which is assumed # in the whole pipeline) to microns (which is the expected output) # # "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world" pipeline = [(det, dms2detector), (sca, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping( (0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa')), (msa_frame, msa2oteip.rename('msa2oteip')), (oteip, oteip2v23.rename('oteip2v23')), (v2v3, None)] else: pipeline = [(det, dms2detector), (sca, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping( (0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa')), (msa_frame, None)] return pipeline
def ote2asdf(otepcf, outname, ref_kw): """ ref_kw = common_reference_file_keywords('OTE', 'NIRSPEC OTE transform - CDP4') ote2asdf('Model/Ref_Files/CoordTransform/OTE.pcf', 'jwst_nirspec_ote_0001.asdf', ref_kw) """ with open(otepcf) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2 1') + 1].split() # this corresponds to modeling Rotation direction as is rotation_angle = float(lines[lines.index('*Rotation') + 1]) input_rot_center = lines[lines.index('*InputRotationCentre 2 1') + 1].split() output_rot_center = lines[lines.index('*OutputRotationCentre 2 1') + 1].split() mlinear = homothetic_det2sky(input_rot_center, rotation_angle, factors, output_rot_center) degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_backward = coeffs_from_pcf(degree, xlines) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_backward) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_forward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_backward = coeffs_from_pcf(degree, ylines) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_backward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_forward = coeffs_from_pcf(degree, ylines) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_forward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward output2poly_mapping = Identity(2, name='output_mapping') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='input_mapping') input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | mlinear f = AsdfFile() f.tree = ref_kw.copy() f.tree['model'] = model f.add_history_entry("Build 6") f.write_to(outname) return model_poly, mlinear
def imaging(input_model, reference_files): """ Imaging pipeline frames : detector, gwa, msa, sky """ # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # DETECTOR to GWA transform det2gwa = detector_to_gwa(reference_files, input_model.meta.instrument.detector, disperser) gwa_through = Const1D(-1) * Identity(1) & Const1D(-1) * Identity( 1) & Identity(1) angles = [ disperser['theta_x'], disperser['theta_y'], disperser['theta_z'], disperser['tilt_y'] ] rotation = Rotation3DToGWA(angles, axes_order="xyzy", name='rotaton').inverse dircos2unitless = DirCos2Unitless(name='directional_cosines2unitless') col = AsdfFile.open(reference_files['collimator']).tree['model'] # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange( input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder lam = wrange[0] + (wrange[1] - wrange[0]) * .5 lam_model = Mapping((0, 1, 1)) | Identity(2) & Const1D(lam) gwa2msa = gwa_through | rotation | dircos2unitless | col | lam_model gwa2msa.inverse = col.inverse | dircos2unitless.inverse | rotation.inverse | gwa_through # MSA to OTEIP transform msa2ote = msa_to_oteip(reference_files) msa2oteip = msa2ote | Mapping((0, 1), n_inputs=3) msa2oteip.inverse = Mapping((0, 1, 0, 1)) | msa2ote.inverse | Mapping( (0, 1), n_inputs=3) # OTEIP to V2,V3 transform with AsdfFile.open(reference_files['ote']) as f: oteip2v23 = f.tree['model'].copy() # Create coordinate frames in the NIRSPEC WCS pipeline # "detector", "gwa", "msa", "oteip", "v2v3", "world" det, gwa, msa_frame, oteip, v2v3 = create_imaging_frames() imaging_pipeline = [(det, det2gwa), (gwa, gwa2msa), (msa_frame, msa2oteip), (oteip, oteip2v23), (v2v3, None)] return imaging_pipeline
def gwa_to_ifuslit(slits, disperser, wrange, order, reference_files): """ GWA to SLIT transform. Parameters ---------- slits : list A list of slit IDs for all IFU slits 0-29. disperser : dict A corrected disperser ASDF object. filter : str The filter used. grating : str The grating used in the observation. reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~jwst_lib.pipeline_models.Gwa2Slit` model. Transform from GWA frame to SLIT frame. """ agreq = AngleFromGratingEquation(disperser['groove_density'], order, name='alpha_from_greq') lgreq = WavelengthFromGratingEquation(disperser['groove_density'], order, name='lambda_from_greq') collimator2gwa = collimator_to_gwa(reference_files, disperser) lam = (wrange[1] - wrange[0]) / 2 + wrange[0] ifuslicer = AsdfFile.open(reference_files['ifuslicer']) ifupost = AsdfFile.open(reference_files['ifupost']) slit_models = {} ifuslicer_model = ifuslicer.tree['model'] for slit in slits: slitdata = ifuslicer.tree['data'][slit] slitdata_model = get_slit_location_model(slitdata) ifuslicer_transform = (slitdata_model | ifuslicer_model) ifupost_transform = ifupost.tree[slit]['model'] msa2gwa = ifuslicer_transform | ifupost_transform | collimator2gwa gwa2msa = gwa_to_ymsa(msa2gwa) # TODO: Use model sets here bgwa2msa = Mapping((0, 1, 0, 1), n_inputs=3) | \ Const1D(0) * Identity(1) & Const1D(-1) * Identity(1) & Identity(2) | \ Identity(1) & gwa2msa & Identity(2) | \ Mapping((0, 1, 0, 1, 2, 3)) | Identity(2) & msa2gwa & Identity(2) | \ Mapping((0, 1, 2, 5), n_inputs=7)| Identity(2) & lgreq # msa to before_gwa #msa2bgwa = Mapping((0, 1, 2, 2)) | msa2gwa & Identity(1) | Mapping((3, 0, 1, 2)) | agreq msa2bgwa = msa2gwa & Identity(1) | Mapping((3, 0, 1, 2)) | agreq bgwa2msa.inverse = msa2bgwa slit_models[slit] = bgwa2msa ifuslicer.close() ifupost.close() return Gwa2Slit(slit_models)
def gwa_to_slit(slits_id, disperser, wrange, order, reference_files): """ GWA to SLIT transform. Parameters ---------- slits_id : list A list of slit IDs for all open shutters/slitlets. disperser : dict A corrected disperser ASDF object. filter : str The filter used. grating : str The grating used in the observation. reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~jwst_lib.pipeline_models.Gwa2Slit` model. Transform from GWA frame to SLIT frame. """ agreq = AngleFromGratingEquation(disperser['groove_density'], order, name='alpha_from_greq') lgreq = WavelengthFromGratingEquation(disperser['groove_density'], order, name='lambda_from_greq') collimator2gwa = collimator_to_gwa(reference_files, disperser) msa = AsdfFile.open(reference_files['msa']) slit_models = {} for i in range(1, 6): slit_names = slits_id[slits_id[:, 0] == i] if slit_names.any(): msa_model = msa.tree[i]['model'] for slit in slit_names: index = slit[1] - 1 slitdata = msa.tree[slit[0]]['data'][index] slitdata_model = get_slit_location_model(slitdata) msa_transform = slitdata_model | msa_model msa2gwa = (msa_transform | collimator2gwa) gwa2msa = gwa_to_ymsa(msa2gwa) # TODO: Use model sets here bgwa2msa = Mapping((0, 1, 0, 1), n_inputs=3) | \ Const1D(0) * Identity(1) & Const1D(-1) * Identity(1) & Identity(2) | \ Identity(1) & gwa2msa & Identity(2) | \ Mapping((0, 1, 0, 1, 2, 3)) | Identity(2) & msa2gwa & Identity(2) | \ Mapping((0, 1, 2, 5), n_inputs=7)| Identity(2) & lgreq # msa to before_gwa msa2bgwa = msa2gwa & Identity(1) | Mapping( (3, 0, 1, 2)) | agreq bgwa2msa.inverse = msa2bgwa s = slitid_to_slit(np.array([slit]))[0] slit_models[s] = bgwa2msa msa.close() return Gwa2Slit(slit_models)
def nrs_wcs_set_input(wcsobj, quadrant, slitid, wavelength_range): """ Returns a WCS object for this slit. Parameters ---------- wcsobj : `~gwcs.WCS` A WCS object for the all open slitlets in an observation. quadrant : int MSA Quadrant number. slit : int Slit id within this quadrant. wavelength_range: list Wavelength range for the combination of fliter and grating. Returns ------- wcsobj : `~gwcs.wcs.WCS` WCS object for this slit. """ import copy # TODO: Add a copy method to gwcs.WCS slit = slitid_to_slit(np.array([(quadrant, slitid)]))[0] slit_wcs = copy.deepcopy(wcsobj) #slit_wcs.set_transform('detector', 'gwa', wcsobj.pipeline[0][1][1:]) slit_wcs.set_transform('detector', 'gwa', wcsobj.pipeline[0][1]) slit_wcs.set_transform('gwa', 'slit_frame', wcsobj.pipeline[1][1].models[slit]) slit_wcs.set_transform('slit_frame', 'msa_frame', wcsobj.pipeline[2][1][1].models[slit] & Identity(1)) slit2detector = slit_wcs.get_transform('slit_frame', 'detector') domain = compute_domain(slit2detector, wavelength_range) slit_wcs.domain = domain return slit_wcs
def make_mock_jwst_pipeline(v2ref=0, v3ref=0, roll=0, crpix=[512, 512], cd=[[1e-5, 0], [0, 1e-5]], crval=[0, 0], enable_vacorr=True): detector = gwcs.coordinate_frames.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix)) v2v3 = gwcs.coordinate_frames.Frame2D(name='v2v3', axes_order=(0, 1), unit=(u.arcsec, u.arcsec)) v2v3vacorr = gwcs.coordinate_frames.Frame2D(name='v2v3vacorr', axes_order=(0, 1), unit=(u.arcsec, u.arcsec)) world = gwcs.coordinate_frames.CelestialFrame(reference_frame=coord.ICRS(), name='world') det2v2v3 = create_DetToV2V3(v2ref=v2ref / 3600.0, v3ref=v3ref / 3600.0, roll=roll, cd=cd, crpix=crpix) v23sky = V2V3ToSky( [-v2ref / 3600.0, v3ref / 3600.0, -roll, -crval[1], crval[0]], [2, 1, 0, 1, 2]) if enable_vacorr: pipeline = [(detector, det2v2v3), (v2v3, Identity(2)), (v2v3vacorr, v23sky), (world, None)] else: pipeline = [(detector, det2v2v3), (v2v3, v23sky), (world, None)] return pipeline
def spatial_like_model(): crpix1, crpix2 = (100, 100) * u.pix cdelt1, cdelt2 = (10, 10) * (u.arcsec / u.pix) shiftu = Shift(-crpix1) & Shift(-crpix2) scale = Multiply(cdelt1) & Multiply(cdelt2) return (shiftu | scale | Pix2Sky_AZP()) & Identity(1)
def test_snell_sellmeier_combined(sellmeier_glass): fromdircos = geometry.FromDirectionCosines() todircos = geometry.ToDirectionCosines() model = sellmeier_glass & todircos | sp.Snell3D() & Identity( 1) | fromdircos expected = (0.07013833805527926, 0.07013833805527926, 1.0050677723764139) assert_allclose(model(2, .1, .1, .9), expected)
def _make_reference_gwcs_wcs(fits_hdr): hdr = fits.Header.fromfile(get_pkg_data_filename(fits_hdr)) fw = fitswcs.WCS(hdr) unit_conv = Scale(1.0 / 3600.0, name='arcsec_to_deg_1D') unit_conv = unit_conv & unit_conv unit_conv.name = 'arcsec_to_deg_2D' unit_conv_inv = Scale(3600.0, name='deg_to_arcsec_1D') unit_conv_inv = unit_conv_inv & unit_conv_inv unit_conv_inv.name = 'deg_to_arcsec_2D' c2s = CartesianToSpherical(name='c2s', wrap_lon_at=180) s2c = SphericalToCartesian(name='s2c', wrap_lon_at=180) c2tan = ((Mapping((0, 1, 2), name='xyz') / Mapping( (0, 0, 0), n_inputs=3, name='xxx')) | Mapping((1, 2), name='xtyt')) c2tan.name = 'Cartesian 3D to TAN' tan2c = (Mapping((0, 0, 1), n_inputs=2, name='xtyt2xyz') | (Const1D(1, name='one') & Identity(2, name='I(2D)'))) tan2c.name = 'TAN to cartesian 3D' tan2c.inverse = c2tan c2tan.inverse = tan2c aff = AffineTransformation2D(matrix=fw.wcs.cd) offx = Shift(-fw.wcs.crpix[0]) offy = Shift(-fw.wcs.crpix[1]) s = 5e-6 scale = Scale(s) & Scale(s) det2tan = (offx & offy) | scale | tan2c | c2s | unit_conv_inv taninv = s2c | c2tan tan = Pix2Sky_TAN() n2c = RotateNative2Celestial(fw.wcs.crval[0], fw.wcs.crval[1], 180) wcslin = unit_conv | taninv | scale.inverse | aff | tan | n2c sky_frm = cf.CelestialFrame(reference_frame=coord.ICRS()) det_frm = cf.Frame2D(name='detector') v2v3_frm = cf.Frame2D(name="v2v3", unit=(u.arcsec, u.arcsec), axes_names=('x', 'y'), axes_order=(0, 1)) pipeline = [(det_frm, det2tan), (v2v3_frm, wcslin), (sky_frm, None)] gw = gwcs.WCS(input_frame=det_frm, output_frame=sky_frm, forward_transform=pipeline) gw.crpix = fw.wcs.crpix gw.crval = fw.wcs.crval gw.bounding_box = ((-0.5, fw.pixel_shape[0] - 0.5), (-0.5, fw.pixel_shape[1] - 0.5)) return gw
def dva_corr_model(va_scale, v2_ref, v3_ref): """ Create transformation that accounts for differential velocity aberration (scale). Parameters ---------- va_scale : float, None Ratio of the apparent plate scale to the true plate scale. When ``va_scale`` is `None`, it is assumed to be identical to ``1`` and an ``astropy.modeling.models.Identity`` model will be returned. v2_ref : float, None Telescope ``v2`` coordinate of the reference point in ``arcsec``. When ``v2_ref`` is `None`, it is assumed to be identical to ``0``. v3_ref : float, None Telescope ``v3`` coordinate of the reference point in ``arcsec``. When ``v3_ref`` is `None`, it is assumed to be identical to ``0``. Returns ------- va_corr : astropy.modeling.CompoundModel, astropy.modeling.models.Identity A 2D compound model that corrects DVA. If ``va_scale`` is `None` or 1 then `astropy.modeling.models.Identity` will be returned. """ if va_scale is None or va_scale == 1: return Identity(2) if va_scale <= 0: raise ValueError( "'Velocity aberration scale must be a positive number.") va_corr = Scale(va_scale, name='dva_scale_v2') & Scale(va_scale, name='dva_scale_v3') if v2_ref is None: v2_ref = 0 if v3_ref is None: v3_ref = 0 if v2_ref == 0 and v3_ref == 0: return va_corr # NOTE: it is assumed that v2, v3 angles and va scale are small enough # so that for expected scale factors the issue of angle wrapping # (180 degrees) can be neglected. v2_shift = (1 - va_scale) * v2_ref v3_shift = (1 - va_scale) * v3_ref va_corr |= Shift(v2_shift, name='dva_v2_shift') & Shift( v3_shift, name='dva_v3_shift') va_corr.name = 'DVA_Correction' return va_corr
def ifu(input_model, reference_files): """ IFU pipeline """ slits = np.arange(30) # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange( input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder # DETECTOR to GWA transform det2gwa = Identity(2) & detector_to_gwa( reference_files, input_model.meta.instrument.detector, disperser) # GWA to SLIT gwa2slit = gwa_to_ifuslit(slits, disperser, wrange, sporder, reference_files) # SLIT to MSA transform slit2msa = ifuslit_to_msa(slits, reference_files) # MSA to OTEIP transform msa2oteip = ifu_msa_to_oteip(reference_files) # OTEIP to V2,V3 transform oteip2v23 = oteip_to_v23(reference_files) # Create coordinate frames in the NIRSPEC WCS pipeline" # "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world" det, gwa, slit_frame, msa_frame, oteip, v2v3 = create_frames() pipeline = [(det, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping( (0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa')), (msa_frame, msa2oteip.rename('msa2oteip')), (oteip, oteip2v23.rename('oteip2v23')), (v2v3, None)] return pipeline
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 test_fittable_compound(): m = Identity(1) | Mapping((0, )) | Gaussian1D(1, 5, 4) x = np.arange(10) y_real = m(x) dy = 0.005 with NumpyRNGContext(1234567): n = np.random.normal(0., dy, x.shape) y_noisy = y_real + n pfit = LevMarLSQFitter() new_model = pfit(m, x, y_noisy) y_fit = new_model(x) assert_allclose(y_fit, y_real, atol=dy)
def oteip_to_v23(reference_files): """ Transform from the OTEIP frame to the V2V3 frame. Parameters ---------- reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~astropy.modeling.core.Model` model. Transform from OTEIP to V2V3. """ with AsdfFile.open(reference_files['ote']) as f: ote = f.tree['model'].copy() fore2ote_mapping = Identity(3, name='fore2ote_mapping') fore2ote_mapping.inverse = Mapping((0, 1, 2, 2)) # Convert the wavelength to microns return fore2ote_mapping | (ote & Identity(1) / Const1D(1e-6))
def test_dont_drop_one_half(spatial_like): """ Test the situation where we are not just dropping one half of the tree. """ spatial_like = spatial_like & Identity(1) tree = spatial_like._tree ginp_map = make_tree_input_map(tree) r_ginp_map = {tuple(v): k for k, v in ginp_map.items()} trees = remove_input_frame(tree, "x01") assert r_ginp_map[("x0", )] in trees assert tree.left not in trees
def ifu(input_model, reference_files): """ IFU pipeline """ slits = np.arange(30) # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange(input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder # DETECTOR to GWA transform det2gwa = Identity(2) & detector_to_gwa(reference_files, input_model.meta.instrument.detector, disperser) # GWA to SLIT gwa2slit = gwa_to_ifuslit(slits, disperser, wrange, sporder, reference_files) # SLIT to MSA transform slit2msa = ifuslit_to_msa(slits, reference_files) # MSA to OTEIP transform msa2oteip = ifu_msa_to_oteip(reference_files) # OTEIP to V2,V3 transform oteip2v23 = oteip_to_v23(reference_files) # Create coordinate frames in the NIRSPEC WCS pipeline" # "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world" det, gwa, slit_frame, msa_frame, oteip, v2v3 = create_frames() pipeline = [(det, det2gwa.rename('detector2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping((0, 1, 2, 3, 4)) | slit2msa).rename('slit2msa')), (msa_frame, msa2oteip.rename('msa2oteip')), (oteip, oteip2v23.rename('oteip2v23')), (v2v3, None)] return pipeline
def assign_moving_target_wcs(input_model): if not isinstance(input_model, datamodels.ModelContainer): raise ValueError("Expected a ModelContainer object") # Get the MT RA/Dec values from all the input exposures mt_ra = np.array( [model.meta.wcsinfo.mt_ra for model in input_model._models]) mt_dec = np.array( [model.meta.wcsinfo.mt_dec for model in input_model._models]) # Compute the mean MT RA/Dec over all exposures if (None in mt_ra) or (None in mt_dec): log.warning("One or more MT RA/Dec values missing in input images") log.warning("Step will be skipped, resulting in target misalignment") for model in input_model: model.meta.cal_step.assign_mtwcs = 'SKIPPED' return input_model else: mt_avra = mt_ra.mean() mt_avdec = mt_dec.mean() for model in input_model: pipeline = model.meta.wcs._pipeline[:-1] mt = deepcopy(model.meta.wcs.output_frame) mt.name = 'moving_target' mt_ra = model.meta.wcsinfo.mt_ra mt_dec = model.meta.wcsinfo.mt_dec model.meta.wcsinfo.mt_avra = mt_avra model.meta.wcsinfo.mt_avdec = mt_avdec rdel = mt_avra - mt_ra ddel = mt_avdec - mt_dec if isinstance(mt, cf.CelestialFrame): transform_to_mt = Shift(rdel) & Shift(ddel) elif isinstance(mt, cf.CompositeFrame): transform_to_mt = Shift(rdel) & Shift(ddel) & Identity(1) else: raise ValueError("Unrecognized coordinate frame.") pipeline.append((model.meta.wcs.output_frame, transform_to_mt)) pipeline.append((mt, None)) new_wcs = WCS(pipeline) del model.meta.wcs model.meta.wcs = new_wcs model.meta.cal_step.assign_mtwcs = 'COMPLETE' return input_model
def oteip_to_v23(reference_files): """ Transform from the OTEIP frame to the V2V3 frame. Parameters ---------- reference_files: dict Dictionary with reference files returned by CRDS. Returns ------- model : `~astropy.modeling.core.Model` model. Transform from OTEIP to V2V3. """ with AsdfFile.open(reference_files['ote']) as f: ote = f.tree['model'].copy() fore2ote_mapping = Identity(3, name='fore2ote_mapping') fore2ote_mapping.inverse = Mapping((0, 1, 2, 2)) # Create the transform to v2/v3/lambda. The wavelength units up to this point are # meters as required by the pipeline but the desired output wavelength units is microns. # So we are going to Scale the spectral units by 1e6 (meters -> microns) return fore2ote_mapping | (ote & Scale(1e6))
def _v2v3_to_tpcorr_from_full(tpcorr): s2c = tpcorr['s2c'] c2s = tpcorr['c2s'] # unit_conv = _get_submodel(tpcorr, 'arcsec_to_deg_2D') # unit_conv_inv = _get_submodel(tpcorr, 'deg_to_arcsec_2D') # # The code below is a work-around to the code above not working. # TODO: understand why _get_submodel is unable to retrieve # some submodels in a compound model. # unit_conv = _get_submodel(tpcorr, 'arcsec_to_deg_1D') unit_conv = unit_conv & unit_conv unit_conv.name = 'arcsec_to_deg_2D' unit_conv_inv = _get_submodel(tpcorr, 'deg_to_arcsec_1D') unit_conv_inv = unit_conv_inv & unit_conv_inv unit_conv_inv.name = 'deg_to_arcsec_2D' affine = tpcorr['tp_affine'] affine_inv = affine.inverse affine_inv.name = 'tp_affine_inv' rot = tpcorr['det_to_optic_axis'] rot_inv = rot.inverse rot_inv.name = 'optic_axis_to_det' # c2tan = _get_submodel(tpcorr, 'Cartesian 3D to TAN') # tan2c = _get_submodel(tpcorr, 'TAN to cartesian 3D') # # The code below is a work-around to the code above not working. # TODO: understand why _get_submodel is unable to retrieve # some submodels in a compound model. # c2tan = ((Mapping((0, 1, 2), name='xyz') / Mapping( (0, 0, 0), n_inputs=3, name='xxx')) | Mapping((1, 2), name='xtyt')) c2tan.name = 'Cartesian 3D to TAN' tan2c = (Mapping((0, 0, 1), n_inputs=2, name='xtyt2xyz') | (Const1D(1, name='one') & Identity(2, name='I(2D)'))) tan2c.name = 'TAN to cartesian 3D' v2v3_to_tpcorr = unit_conv | s2c | rot | c2tan | affine v2v3_to_tpcorr.name = 'jwst_v2v3_to_tpcorr' tpcorr_to_v2v3 = affine_inv | tan2c | rot_inv | c2s | unit_conv_inv tpcorr_to_v2v3.name = 'jwst_tpcorr_to_v2v3' v2v3_to_tpcorr.inverse = tpcorr_to_v2v3 tpcorr_to_v2v3.inverse = v2v3_to_tpcorr return v2v3_to_tpcorr
def test_identity_input(): """ Test a case where an Identity (or Mapping) model is the first in a chain of composite models and thus is responsible for handling input broadcasting properly. Regression test for https://github.com/astropy/astropy/pull/3362 """ ident1 = Identity(1) shift = Shift(1) rotation = Rotation2D(angle=90) model = ident1 & shift | rotation assert_allclose(model(1, 2), [-3.0, 1.0])
def test_identity(): x = np.zeros((2, 3)) y = np.ones((2, 3)) ident1 = Identity(1) shift = Shift(1) rotation = Rotation2D(angle=60) model = ident1 & shift | rotation assert_allclose(model(1, 2), (-2.098076211353316, 2.3660254037844393)) res_x, res_y = model(x, y) assert_allclose((res_x, res_y), (np.array([[-1.73205081, -1.73205081, -1.73205081], [-1.73205081, -1.73205081, -1.73205081] ]), np.array([[1., 1., 1.], [1., 1., 1.]]))) assert_allclose(model.inverse(res_x, res_y), (x, y), atol=1.e-10)
def nrs_wcs_set_input(input_model, slit_name, wavelength_range=None): """ Returns a WCS object for this slit. Parameters ---------- input_model : `~jwst.datamodels.DataModel` A WCS object for the all open slitlets in an observation. slit_name : int or str Slit.name of an open slit. wavelength_range: list Wavelength range for the combination of fliter and grating. Returns ------- wcsobj : `~gwcs.wcs.WCS` WCS object for this slit. """ import copy # TODO: Add a copy method to gwcs.WCS wcsobj = input_model.meta.wcs if wavelength_range is None: _, wrange = spectral_order_wrange_from_model(input_model) else: wrange = wavelength_range slit_wcs = copy.deepcopy(wcsobj) slit_wcs.set_transform('sca', 'gwa', wcsobj.pipeline[1][1][1:]) # get the open slits from the model # Need them to get the slit ymin,ymax g2s = wcsobj.pipeline[2][1] open_slits = g2s.slits slit_wcs.set_transform('gwa', 'slit_frame', g2s.get_model(slit_name)) slit_wcs.set_transform( 'slit_frame', 'msa_frame', wcsobj.pipeline[3][1][1].get_model(slit_name) & Identity(1)) slit2detector = slit_wcs.get_transform('slit_frame', 'detector') if input_model.meta.exposure.type.lower() != 'nrs_ifu': slit = [s for s in open_slits if s.name == slit_name][0] domain = compute_domain(slit2detector, wrange, slit_ymin=slit.ymin, slit_ymax=slit.ymax) else: domain = compute_domain(slit2detector, wrange) slit_wcs.domain = domain return slit_wcs
def _tpcorr_init(v2_ref, v3_ref, roll_ref): s2c = SphericalToCartesian(name='s2c', wrap_lon_at=180) c2s = CartesianToSpherical(name='c2s', wrap_lon_at=180) unit_conv = Scale(1.0 / 3600.0, name='arcsec_to_deg_1D') unit_conv = unit_conv & unit_conv unit_conv.name = 'arcsec_to_deg_2D' unit_conv_inv = Scale(3600.0, name='deg_to_arcsec_1D') unit_conv_inv = unit_conv_inv & unit_conv_inv unit_conv_inv.name = 'deg_to_arcsec_2D' affine = AffineTransformation2D(name='tp_affine') affine_inv = AffineTransformation2D(name='tp_affine_inv') rot = RotationSequence3D([v2_ref, -v3_ref, roll_ref], 'zyx', name='det_to_optic_axis') rot_inv = rot.inverse rot_inv.name = 'optic_axis_to_det' # projection submodels: c2tan = ((Mapping((0, 1, 2), name='xyz') / Mapping( (0, 0, 0), n_inputs=3, name='xxx')) | Mapping((1, 2), name='xtyt')) c2tan.name = 'Cartesian 3D to TAN' tan2c = (Mapping((0, 0, 1), n_inputs=2, name='xtyt2xyz') | (Const1D(1, name='one') & Identity(2, name='I(2D)'))) tan2c.name = 'TAN to cartesian 3D' total_corr = (unit_conv | s2c | rot | c2tan | affine | tan2c | rot_inv | c2s | unit_conv_inv) total_corr.name = 'JWST tangent-plane linear correction. v1' inv_total_corr = (unit_conv | s2c | rot | c2tan | affine_inv | tan2c | rot_inv | c2s | unit_conv_inv) inv_total_corr.name = 'Inverse JWST tangent-plane linear correction. v1' # TODO # re-enable circular inverse definitions once # https://github.com/spacetelescope/asdf/issues/744 or # https://github.com/spacetelescope/asdf/issues/745 are resolved. # # inv_total_corr.inverse = total_corr total_corr.inverse = inv_total_corr return total_corr
def from_tree_transform(cls, node, ctx): mapping = node['mapping'] n_inputs = node.get('n_inputs') if all([isinstance(x, int) for x in mapping]): return Mapping(tuple(mapping), n_inputs) if n_inputs is None: n_inputs = max([x for x in mapping if isinstance(x, int)]) + 1 transform = Identity(n_inputs) new_mapping = [] i = n_inputs for entry in mapping: if isinstance(entry, int): new_mapping.append(entry) else: new_mapping.append(i) transform = transform & Const1D(entry.value) i += 1 return transform | Mapping(new_mapping)
def pcf2model(pcffile, name=""): """ Create a model from a NIRSPEC Camera.pcf or Collimator*.pcf file. - forward (team): sky to detector - Shift inputs to input_rotation_center - Rotate inputs - Scale inputs - Shift inputs to output_rot_center - Apply polynomial distortion - backward_team (team definition) detector to sky - Apply polynomial distortion - Shift inputs to output_rot_center - Scale inputs - Rotate inputs - Shift inputs to input_rotation_center WCS implementation - forward: detector to sky - equivalent to backward_team - backward: sky to detector - equivalent to forward_team Parameters ---------- pcffile : str one of the NIRSPEC ".pcf" reference files provided by the IDT team. "pcf" stands for "polynomial coefficients fit" outname : str Name of reference file to be wriiten to disk. Returns ------- fasdf : `~astropy.modeling.core.Model` Model object. Examples -------- >>> pcf2asdf("Camera.pcf", "camera.asdf") """ linear_det2sky = linear_from_pcf_det2sky(pcffile, name=name) with open(pcffile) as f: lines = [l.strip() for l in f.readlines()] degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='{0}_x_backward'.format(name), **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name='{0}_y_backward'.format(name), **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_forward = models.Polynomial2D(degree, name='{0}_x_forward'.format(name), **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name='{0}_y_forward'.format(name), **ycoeff_backward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward output2poly_mapping = Identity(2, name='{0}_outmap'.format(name)) output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='{0}_inmap'.format(name)) input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | linear_det2sky return model
def pcf2asdf(pcffile, outname, ref_file_kw): """ Create an asdf reference file with the transformation coded in a NIRSPEC Camera.pcf or Collimator*.pcf file. - forward (team): sky to detector - Shift inputs to input_rotation_center - Rotate inputs - Scale inputs - Shift inputs to output_rot_center - Apply polynomial distortion - backward_team (team definition) detector to sky - Apply polynomial distortion - Shift inputs to output_rot_center - Scale inputs - Rotate inputs - Shift inputs to input_rotation_center WCS implementation - forward: detector to sky - equivalent to backward_team - backward: sky to detector - equivalent to forward_team Parameters ---------- pcffile : str one of the NIRSPEC ".pcf" reference files provided by the IDT team. "pcf" stands for "polynomial coefficients fit" outname : str Name of reference file to be wriiten to disk. Returns ------- fasdf : AsdfFile AsdfFile object Examples -------- >>> pcf2asdf("Camera.pcf", "camera.asdf") """ with open(pcffile) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2') + 1].split() scale = models.Scale(1 / float(factors[0]), name="x_scale") & \ models.Scale(1 / float(factors[1]), name="y_scale") rotation_angle = lines[lines.index('*Rotation') + 1] # Backward rotation is in the counter-clockwise direction as in modeling # Forward is clockwise backward_rotation = models.Rotation2D(float(rotation_angle), name='rotation') rotation = backward_rotation.copy() # Here the model is called "output_shift" but in the team version it is the "input_shift". input_rot_center = lines[lines.index('*InputRotationCentre 2') + 1].split() output_offset = models.Shift(float(input_rot_center[0]), name='output_x_shift') & \ models.Shift(float(input_rot_center[1]), name='output_y_shift') # Here the model is called "input_shift" but in the team version it is the "output_shift". output_rot_center = lines[lines.index('*OutputRotationCentre 2') + 1].split() input_offset = models.Shift(-float(output_rot_center[0]), name='input_x_shift') & \ models.Shift(-float(output_rot_center[1]), name='input_y_shift') degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_backward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward output2poly_mapping = Identity(2, name='output_mapping') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='input_mapping') input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | input_offset |scale |rotation |output_offset f = AsdfFile() f.tree = ref_file_kw.copy() f.tree['model'] = model f.write_to(outname)
def fore2asdf(pcffore, outname, ref_kw): """ forward direction : msa 2 ote backward_direction: msa 2 fpa """ with open(pcffore) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2') + 1].split() # factor==1/factor in backward msa2ote direction and == factor in ote2msa direction scale = models.Scale(1. / float(factors[0]), name='scale_x') & \ models.Scale(1 / float(factors[1]), name='scale_y') rotation_angle = lines[lines.index('*Rotation') + 1] rotation = models.Rotation2D(float(rotation_angle), name='rotation') input_rot_center = lines[lines.index('*InputRotationCentre 2') + 1].split() output_offset = models.Shift(float(input_rot_center[0]), name="output_x_shift") & \ models.Shift(float(input_rot_center[1]), name="output_y_shift") output_rot_center = lines[lines.index('*OutputRotationCentre 2') + 1].split() input_offset = models.Shift(-float(output_rot_center[0]), name="input_x_shift") & \ models.Shift(-float(output_rot_center[1]), name="input_y_shift") degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) # Polynomial Correction in x x_poly_backward = models.Polynomial2D(degree, name="x_poly_backward", **xcoeff_forward) xlines_distortion = lines[xcoeff_index + 22: xcoeff_index + 43] xcoeff_forward_distortion = coeffs_from_pcf(degree, xlines_distortion) x_poly_backward_distortion = models.Polynomial2D(degree, name="x_backward_distortion", **xcoeff_forward_distortion) # do chromatic correction # the input is Xote, Yote, lam model_x_backward = (Mapping((0, 1), n_inputs=3) | x_poly_backward) + \ ((Mapping((0,1), n_inputs=3) | x_poly_backward_distortion) * Mapping((2,))) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name="y_poly_backward", **ycoeff_forward) ylines_distortion = lines[ycoeff_index + 22: ycoeff_index + 43] ycoeff_forward_distortion = coeffs_from_pcf(degree, ylines_distortion) y_poly_backward_distortion = models.Polynomial2D(degree, name="y_backward_distortion", **ycoeff_forward_distortion) # do chromatic correction # the input is Xote, Yote, lam model_y_backward = (Mapping((0,1), n_inputs=3) | y_poly_backward) + \ ((Mapping((0, 1), n_inputs=3) | y_poly_backward_distortion) * Mapping((2,))) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_forward = models.Polynomial2D(degree,name="x_poly_forward", **xcoeff_backward) xcoeff_backward_distortion = coeffs_from_pcf(degree, lines[xcoeff_index + 22: xcoeff_index + 43]) x_poly_forward_distortion = models.Polynomial2D(degree, name="x_forward_distortion", **xcoeff_backward_distortion) # the chromatic correction is done here # the input is Xmsa, Ymsa, lam model_x_forward = (Mapping((0,1), n_inputs=3) | x_poly_forward) + \ ((Mapping((0,1), n_inputs=3) | x_poly_forward_distortion) * Mapping((2,))) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name="y_poly_forward",**ycoeff_backward) ycoeff_backward_distortion = coeffs_from_pcf(degree, lines[ycoeff_index + 22: ycoeff_index + 43]) y_poly_forward_distortion = models.Polynomial2D(degree, name="y_forward_distortion", **ycoeff_backward_distortion) # do chromatic correction # the input is Xmsa, Ymsa, lam model_y_forward = (Mapping((0,1), n_inputs=3) | y_poly_forward) + \ ((Mapping((0,1), n_inputs=3) | y_poly_forward_distortion) * Mapping((2,))) #assign inverse transforms model_x = model_x_forward.copy() model_y = model_y_forward.copy() model_x.inverse = model_x_backward model_y.inverse = model_y_backward output2poly_mapping = Identity(2, name="output_mapping") output2poly_mapping.inverse = Mapping([0, 1, 2, 0, 1, 2]) input2poly_mapping = Mapping([0, 1, 2, 0, 1, 2], name="input_mapping") input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (model_x & model_y) | output2poly_mapping fore_linear = (input_offset | rotation | scale | output_offset) fore_linear_inverse = fore_linear.inverse fore_linear.inverse = fore_linear_inverse & Identity(1) model = model_poly | fore_linear f = AsdfFile() f.tree = ref_kw.copy() f.tree['model'] = model asdffile = f.write_to(outname) return asdffile
def ote2asdf(otepcf, outname, ref_kw): """ ref_kw = common_reference_file_keywords('OTE', 'NIRSPEC OTE transform - CDP4') ote2asdf('Model/Ref_Files/CoordTransform/OTE.pcf', 'jwst_nirspec_ote_0001.asdf', ref_kw) """ with open(otepcf) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2 1') + 1].split() scale = models.Scale(1 / float(factors[0]), name="x_scale") & \ models.Scale(1 / float(factors[1]), name="y_scale") # this corresponds to modeling Rotation direction as is rotation_angle = lines[lines.index('*Rotation') + 1] rotation = models.Rotation2D(float(rotation_angle), name='rotation') input_rot_center = lines[lines.index('*InputRotationCentre 2 1') + 1].split() output_offset = models.Shift(float(input_rot_center[0]), name='output_x_shift') & \ models.Shift(float(input_rot_center[1]), name='output_y_shift') # Here the model is called "input_shift" but in the team version it is the "output_shift". output_rot_center = lines[lines.index('*OutputRotationCentre 2 1') + 1].split() input_offset = models.Shift(-float(output_rot_center[0]), name='input_x_shift') & \ models.Shift(-float(output_rot_center[1]), name='input_y_shift') degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_forward = coeffs_from_pcf(degree, ylines) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_backward = coeffs_from_pcf(degree, xlines) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_backward = coeffs_from_pcf(degree, ylines) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_backward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward mlinear = input_offset | rotation | scale | output_offset output2poly_mapping = Identity(2, name='output_mapping') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='input_mapping') input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | mlinear f = AsdfFile() f.tree = ref_kw.copy() f.tree['model'] = model f.write_to(outname)
def pcf2asdf(pcffile, outname, ref_file_kw): """ Create an asdf reference file with the transformation coded in a NIRSPEC Camera.pcf or Collimator*.pcf file. - forward (team): sky to detector - Shift inputs to input_rotation_center - Rotate inputs - Scale inputs - Shift inputs to output_rot_center - Apply polynomial distortion - backward_team (team definition) detector to sky - Apply polynomial distortion - Shift inputs to output_rot_center - Scale inputs - Rotate inputs - Shift inputs to input_rotation_center WCS implementation - forward: detector to sky - equivalent to backward_team - backward: sky to detector - equivalent to forward_team Parameters ---------- pcffile : str one of the NIRSPEC ".pcf" reference files provided by the IDT team. "pcf" stands for "polynomial coefficients fit" outname : str Name of reference file to be wriiten to disk. Returns ------- fasdf : AsdfFile AsdfFile object Examples -------- >>> pcf2asdf("Camera.pcf", "camera.asdf") """ linear_det2sky = linear_from_pcf_det2sky(pcffile) with open(pcffile) as f: lines = [l.strip() for l in f.readlines()] degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='x_poly_backward', **xcoeff_forward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name='y_poly_backward', **ycoeff_forward) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_forward = models.Polynomial2D(degree, name='x_poly_forward', **xcoeff_backward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name='y_poly_forward', **ycoeff_backward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward output2poly_mapping = Identity(2, name='output_mapping') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='input_mapping') input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | linear_det2sky f = AsdfFile() f.tree = ref_file_kw.copy() f.tree['model'] = model f.write_to(outname)
def fore2asdf(pcffore, outname, ref_kw): """ forward direction : msa 2 ote backward_direction: msa 2 fpa """ with open(pcffore) as f: lines = [l.strip() for l in f.readlines()] fore_det2sky = linear_from_pcf_det2sky(pcffore) fore_linear = fore_det2sky fore_linear.inverse = fore_det2sky.inverse & Identity(1) # compute the polynomial degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1: xcoeff_index + 22] xcoeff_forward = coeffs_from_pcf(degree, xlines) # Polynomial Correction in x x_poly_backward = models.Polynomial2D(degree, name="x_poly_backward", **xcoeff_forward) xlines_distortion = lines[xcoeff_index + 22: xcoeff_index + 43] xcoeff_forward_distortion = coeffs_from_pcf(degree, xlines_distortion) x_poly_backward_distortion = models.Polynomial2D(degree, name="x_backward_distortion", **xcoeff_forward_distortion) # do chromatic correction # the input is Xote, Yote, lam model_x_backward = (Mapping((0, 1), n_inputs=3) | x_poly_backward) + \ ((Mapping((0,1), n_inputs=3) | x_poly_backward_distortion) * \ (Mapping((2,)) | Identity(1))) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ycoeff_forward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_backward = models.Polynomial2D(degree, name="y_poly_backward", **ycoeff_forward) ylines_distortion = lines[ycoeff_index + 22: ycoeff_index + 43] ycoeff_forward_distortion = coeffs_from_pcf(degree, ylines_distortion) y_poly_backward_distortion = models.Polynomial2D(degree, name="y_backward_distortion", **ycoeff_forward_distortion) # do chromatic correction # the input is Xote, Yote, lam model_y_backward = (Mapping((0,1), n_inputs=3) | y_poly_backward) + \ ((Mapping((0, 1), n_inputs=3) | y_poly_backward_distortion) * \ (Mapping((2,)) | Identity(1) )) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xcoeff_backward = coeffs_from_pcf(degree, lines[xcoeff_index + 1: xcoeff_index + 22]) x_poly_forward = models.Polynomial2D(degree,name="x_poly_forward", **xcoeff_backward) xcoeff_backward_distortion = coeffs_from_pcf(degree, lines[xcoeff_index + 22: xcoeff_index + 43]) x_poly_forward_distortion = models.Polynomial2D(degree, name="x_forward_distortion", **xcoeff_backward_distortion) # the chromatic correction is done here # the input is Xmsa, Ymsa, lam model_x_forward = (Mapping((0,1), n_inputs=3) | x_poly_forward) + \ ((Mapping((0,1), n_inputs=3) | x_poly_forward_distortion) * \ (Mapping((2,)) | Identity(1))) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ycoeff_backward = coeffs_from_pcf(degree, lines[ycoeff_index + 1: ycoeff_index + 22]) y_poly_forward = models.Polynomial2D(degree, name="y_poly_forward",**ycoeff_backward) ycoeff_backward_distortion = coeffs_from_pcf(degree, lines[ycoeff_index + 22: ycoeff_index + 43]) y_poly_forward_distortion = models.Polynomial2D(degree, name="y_forward_distortion", **ycoeff_backward_distortion) # do chromatic correction # the input is Xmsa, Ymsa, lam model_y_forward = (Mapping((0,1), n_inputs=3) | y_poly_forward) + \ ((Mapping((0,1), n_inputs=3) | y_poly_forward_distortion) * \ (Mapping((2,)) | Identity(1))) #assign inverse transforms model_x = model_x_forward.copy() model_y = model_y_forward.copy() model_x.inverse = model_x_backward model_y.inverse = model_y_backward output2poly_mapping = Identity(2, name="output_mapping") output2poly_mapping.inverse = Mapping([0, 1, 2, 0, 1, 2]) input2poly_mapping = Mapping([0, 1, 2, 0, 1, 2], name="input_mapping") input2poly_mapping.inverse = Identity(2) model_poly = input2poly_mapping | (model_x & model_y) | output2poly_mapping model = model_poly | fore_linear f = AsdfFile() f.tree = ref_kw.copy() f.tree['model'] = model asdffile = f.write_to(outname) return asdffile
def slits_wcs(input_model, reference_files): """ Create the WCS pipeline for observations using the MSA shutter array or fixed slits. Parameters ---------- input_model : `~jwst.datamodels.ImageModel` The input data model. reference_files : dict Dictionary with reference files supplied by CRDS. """ open_slits_id = get_open_slits(input_model, reference_files) if not open_slits_id: return None n_slits = len(open_slits_id) log.info("Computing WCS for {0} open slitlets".format(n_slits)) # Get the corrected disperser model disperser = get_disperser(input_model, reference_files['disperser']) # Get the default spectral order and wavelength range and record them in the model. sporder, wrange = get_spectral_order_wrange(input_model, reference_files['wavelengthrange']) input_model.meta.wcsinfo.waverange_start = wrange[0] input_model.meta.wcsinfo.waverange_end = wrange[1] input_model.meta.wcsinfo.spectral_order = sporder # DMS to SCA transform dms2detector = dms_to_sca(input_model).rename('dms2sca') # DETECTOR to GWA transform det2gwa = Identity(2) & detector_to_gwa(reference_files, input_model.meta.instrument.detector, disperser) # GWA to SLIT gwa2slit = gwa_to_slit(open_slits_id, input_model, disperser, reference_files) # SLIT to MSA transform slit2msa = slit_to_msa(open_slits_id, reference_files['msa']) # Create coordinate frames in the NIRSPEC WCS pipeline" # "detector", "gwa", "slit_frame", "msa_frame", "oteip", "v2v3", "world" det, sca, gwa, slit_frame, msa_frame, oteip, v2v3, world = create_frames() if input_model.meta.instrument.filter != 'OPAQUE': # MSA to OTEIP transform msa2oteip = msa_to_oteip(reference_files) # OTEIP to V2,V3 transform # This includes a wavelength unit conversion from meters to microns. oteip2v23 = oteip_to_v23(reference_files) # V2, V3 to sky tel2sky = pointing.v23tosky(input_model) & Identity(1) msa_pipeline = [(det, dms2detector), (sca, det2gwa.rename('det2gwa')), (gwa, gwa2slit.rename('gwa2slit')), (slit_frame, (Mapping((0, 1, 2, 3)) | slit2msa).rename('slit2msa')), (msa_frame, msa2oteip.rename('msa2oteip')), (oteip, oteip2v23.rename('oteip2v23')), (v2v3, tel2sky), (world, None)] else: # convert to microns if the pipeline ends earlier gwa2slit = (gwa2slit).rename('gwa2slit') msa_pipeline = [(det, dms2detector), (sca, det2gwa), (gwa, gwa2slit), (slit_frame, Mapping((0, 1, 2, 3)) | slit2msa), (msa_frame, None)] return msa_pipeline
def ote2asdf(otepcf, author, description, useafter): """ """ with open(otepcf) as f: lines = [l.strip() for l in f.readlines()] factors = lines[lines.index('*Factor 2 1') + 1].split() # this corresponds to modeling Rotation direction as is rotation_angle = float(lines[lines.index('*Rotation') + 1]) input_rot_center = lines[lines.index('*InputRotationCentre 2 1') + 1].split() output_rot_center = lines[lines.index('*OutputRotationCentre 2 1') + 1].split() mlinear = homothetic_det2sky(input_rot_center, rotation_angle, factors, output_rot_center, name="ote") degree = int(lines[lines.index('*FitOrder') + 1]) xcoeff_index = lines.index('*xBackwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_backward = coeffs_from_pcf(degree, xlines) x_poly_forward = models.Polynomial2D(degree, name='ote_x_forw', **xcoeff_backward) xcoeff_index = lines.index('*xForwardCoefficients 21 2') xlines = lines[xcoeff_index + 1].split('\t') xcoeff_forward = coeffs_from_pcf(degree, xlines) x_poly_backward = models.Polynomial2D(degree, name='ote_x_back', **xcoeff_forward) ycoeff_index = lines.index('*yBackwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_backward = coeffs_from_pcf(degree, ylines) y_poly_forward = models.Polynomial2D(degree, name='ote_y_forw', **ycoeff_backward) ycoeff_index = lines.index('*yForwardCoefficients 21 2') ylines = lines[ycoeff_index + 1].split('\t') ycoeff_forward = coeffs_from_pcf(degree, ylines) y_poly_backward = models.Polynomial2D(degree, name='ote_y_backw', **ycoeff_forward) x_poly_forward.inverse = x_poly_backward y_poly_forward.inverse = y_poly_backward output2poly_mapping = Identity(2, name='ote_outmap') output2poly_mapping.inverse = Mapping([0, 1, 0, 1]) input2poly_mapping = Mapping([0, 1, 0, 1], name='ote_inmap') input2poly_mapping.inverse = Identity(2) # The Nirspec model returns values in degrees. # We are converting them to arcsec in order to use the same V2V3 to sky # transform as other instruments. unit_transform = models.Scale(3600) & models.Scale(3600) model_poly = input2poly_mapping | (x_poly_forward & y_poly_forward) | output2poly_mapping model = model_poly | mlinear | unit_transform ote_model = OTEModel() ote_model.model = model ote_model.meta.author = author ote_model.meta.description = description ote_model.meta.useafter = useafter ote_model.meta.pedigree = "GROUND" ote_model.meta.output_units = 'arcsec' return ote_model