def test_functional_ifu_grating(): """Compare Nirspec instrument model with IDT model for IFU grating.""" # setup test model_file = 'ifu_grating_functional_ESA_v1_20180619.txt' hdul = create_nirspec_ifu_file(grating='G395H', filter='F290LP', gwa_xtil=0.35986012, gwa_ytil=0.13448857) im = datamodels.ImageModel(hdul) refs = create_reference_files(im) pipeline = nirspec.create_pipeline(im, refs, slit_y_range=[-0.55, 0.55]) w = wcs.WCS(pipeline) im.meta.wcs = w slit_wcs = nirspec.nrs_wcs_set_input(im, 0) # use slice 0 ins_file = get_file_path(model_file) ins_tab = table.Table.read(ins_file, format='ascii') slitx = [0] * 5 slity = [-.5, -.25, 0, .25, .5] lam = np.array([2.9, 3.39, 3.88, 4.37, 5]) * 10**-6 order, wrange = nirspec.get_spectral_order_wrange(im, refs['wavelengthrange']) im.meta.wcsinfo.sporder = order im.meta.wcsinfo.waverange_start = wrange[0] im.meta.wcsinfo.waverange_end = wrange[1] # Slit to MSA entrance # This includes the Slicer transform and the IFUFORE transform slit2msa = slit_wcs.get_transform('slit_frame', 'msa_frame') msax, msay, _ = slit2msa(slitx, slity, lam) assert_allclose(slitx, ins_tab['xslitpos']) assert_allclose(slity, ins_tab['yslitpos']) assert_allclose(msax + 0.0073, ins_tab['xmsapos'], rtol=1e-2) # expected offset assert_allclose(msay + 0.0085, ins_tab['ymaspos'], rtol=1e-2) # expected offset # Slicer slit2slicer = slit_wcs.get_transform('slit_frame', 'slicer') x_slicer, y_slicer, _ = slit2slicer(slitx, slity, lam) # MSA exit # Applies the IFUPOST transform to coordinates at the Slicer with datamodels.IFUPostModel(refs['ifupost']) as ifupost: ifupost_transform = nirspec._create_ifupost_transform(ifupost.slice_0) x_msa_exit, y_msa_exit = ifupost_transform(x_slicer, y_slicer, lam) assert_allclose(x_msa_exit, ins_tab['xmsapos']) assert_allclose(y_msa_exit, ins_tab['ymaspos']) # Computations are done using the eact form of the equations in the reports # Part I of the Forward IFU-POST transform - the linear transform xc_out = 0.0487158154447 yc_out = 0.00856211956976 xc_in = 0.000355277216 yc_in = -3.0089012e-05 theta = np.deg2rad(-0.129043957046) factor_x = 0.100989874454 factor_y = 0.100405184145 # Slicer coordinates xS = 0.000399999989895 yS = -0.00600000005215 x = xc_out + factor_x * (+cos(theta) * (xS - xc_in) + sin(theta) * (yS - yc_in)) y = yc_out + factor_y * (-sin(theta) * (xS - xc_in) + cos(theta) * (yS - yc_in)) # Forward IFU-POST II part - non-linear transform lam = 2.9e-6 coef_names = [ f'c{x}_{y}' for x in range(6) for y in range(6) if x + y <= 5 ] y_forw = [ -82.3492267824, 29234.6982762, -540260.780853, 771881.305018, -2563462.26848, 29914272.1164, 4513.04082605, -2212869.44311, 32875633.0303, -29923698.5288, 27293902.5636, -39820.4434726, 62431493.9962, -667197265.033, 297253538.182, -1838860.86305, -777169857.2, 4514693865.7, 42790637.764, 3596423850.94, -260274017.448 ] y_forw_dist = [ 188531839.97, -43453434864.0, 70807756765.8, -308272809909.0, 159768473071.0, 9712633344590.0, -11762923852.9, 3545938873190.0, -4198643655420.0, 12545642983100.0, -11707051591600.0, 173091230285.0, -108534069056000.0, 82893348097600.0, -124708740989000.0, 2774389757990.0, 1476779720300000.0, -545358301961000.0, -93101557994100.0, -7536890639430000.0, 646310545048000.0 ] y_coeff = {} for i, coef in enumerate(coef_names): y_coeff[coef] = y_forw[i] + lam * y_forw_dist[i] poly2d = astmodels.Polynomial2D(5, **y_coeff) ifupost_y = poly2d(x, y) assert_allclose(ifupost_y, ins_tab['ymaspos'][0]) assert_allclose(ifupost_y, y_msa_exit[0]) # reset 'lam' lam = np.array([2.9, 3.39, 3.88, 4.37, 5]) * 10**-6 # Coordinates at Collimator exit # Applies the Collimator forward transform to coordinates at the MSA exit with datamodels.open(refs['collimator']) as col: colx, coly = col.model.inverse(x_msa_exit, y_msa_exit) assert_allclose(colx, ins_tab['xcoll']) assert_allclose(coly, ins_tab['ycoll']) # After applying direcitonal cosines dircos = trmodels.Unitless2DirCos() xcolDircosi, ycolDircosi, z = dircos(colx, coly) assert_allclose(xcolDircosi, ins_tab['xcolDirCosi']) assert_allclose(ycolDircosi, ins_tab['ycolDirCosi']) # Slit to GWA entrance # applies the Collimator forward, Unitless to Directional and 3D Rotation to MSA exit coordinates with datamodels.DisperserModel(refs['disperser']) as disp: disperser = nirspec.correct_tilt(disp, im.meta.instrument.gwa_xtilt, im.meta.instrument.gwa_ytilt) collimator2gwa = nirspec.collimator_to_gwa(refs, disperser) x_gwa_in, y_gwa_in, z_gwa_in = collimator2gwa(x_msa_exit, y_msa_exit) assert_allclose(x_gwa_in, ins_tab['xdispIn']) # Slit to GWA out # Runs slit--> slicer --> msa_exit --> collimator --> dircos --> rotation --> angle_from_grating equation slit2gwa = slit_wcs.get_transform('slit_frame', 'gwa') x_gwa_out, y_gwa_out, z_gwa_out = slit2gwa(slitx, slity, lam) assert_allclose(x_gwa_out, ins_tab['xdispLaw']) assert_allclose(y_gwa_out, ins_tab['ydispLaw']) # CAMERA entrance (assuming direction is from sky to detector) angles = [ disperser['theta_x'], disperser['theta_y'], disperser['theta_z'], disperser['tilt_y'] ] rotation = trmodels.Rotation3DToGWA(angles, axes_order="xyzy", name='rotation') dircos2unitless = trmodels.DirCos2Unitless() gwa2cam = rotation.inverse | dircos2unitless x_camera_entrance, y_camera_entrance = gwa2cam(x_gwa_out, y_gwa_out, z_gwa_out) assert_allclose(x_camera_entrance, ins_tab['xcamCosi']) assert_allclose(y_camera_entrance, ins_tab['ycamCosi']) # at FPA with datamodels.CameraModel(refs['camera']) as camera: x_fpa, y_fpa = camera.model.inverse(x_camera_entrance, y_camera_entrance) assert_allclose(x_fpa, ins_tab['xfpapos']) assert_allclose(y_fpa, ins_tab['yfpapos']) # at SCA slit2sca = slit_wcs.get_transform('slit_frame', 'sca') x_sca_nrs1, y_sca_nrs1 = slit2sca(slitx, slity, lam) # At NRS2 with datamodels.FPAModel(refs['fpa']) as fpa: x_sca_nrs2, y_sca_nrs2 = fpa.nrs2_model.inverse(x_fpa, y_fpa) assert_allclose(x_sca_nrs1[:3] + 1, ins_tab['i'][:3]) assert_allclose(y_sca_nrs1[:3] + 1, ins_tab['j'][:3]) assert_allclose(x_sca_nrs2[3:] + 1, ins_tab['i'][3:]) assert_allclose(y_sca_nrs2[3:] + 1, ins_tab['j'][3:]) # at oteip # Goes through slicer, ifufore, and fore transforms slit2oteip = slit_wcs.get_transform('slit_frame', 'oteip') x_oteip, y_oteip, _ = slit2oteip(slitx, slity, lam) assert_allclose(x_oteip, ins_tab['xOTEIP']) assert_allclose(y_oteip, ins_tab['yOTEIP']) # at v2, v3 [in arcsec] slit2v23 = slit_wcs.get_transform('slit_frame', 'v2v3') v2, v3, _ = slit2v23(slitx, slity, lam) v2 /= 3600 v3 /= 3600 assert_allclose(v2, ins_tab['xV2V3']) assert_allclose(v3, ins_tab['yV2V3'])
def test_functional_ifu_prism(): """Compare Nirspec instrument model with IDT model for IFU prism.""" # setup test model_file = 'ifu_prism_functional_ESA_v1_20180619.txt' hdu1 = create_nirspec_ifu_file(grating='PRISM', filter='CLEAR', gwa_xtil=0.35986012, gwa_ytil=0.13448857, gwa_tilt=37.1) im = datamodels.ImageModel(hdu1) refs = create_reference_files(im) pipeline = nirspec.create_pipeline(im, refs, slit_y_range=[-0.55, 0.55]) w = wcs.WCS(pipeline) im.meta.wcs = w slit_wcs = nirspec.nrs_wcs_set_input(im, 0) # use slice 0 ins_file = get_file_path(model_file) ins_tab = table.Table.read(ins_file, format='ascii') slitx = [0] * 5 slity = [-.5, -.25, 0, .25, .5] lam = np.array([.7e-7, 1e-6, 2e-6, 3e-6, 5e-6]) order, wrange = nirspec.get_spectral_order_wrange(im, refs['wavelengthrange']) im.meta.wcsinfo.sporder = order im.meta.wcsinfo.waverange_start = wrange[0] im.meta.wcsinfo.waverange_end = wrange[1] # Slit to MSA entrance # This includes the Slicer transform and the IFUFORE transform slit2msa = slit_wcs.get_transform('slit_frame', 'msa_frame') msax, msay, _ = slit2msa(slitx, slity, lam) assert_allclose(slitx, ins_tab['xslitpos']) assert_allclose(slity, ins_tab['yslitpos']) assert_allclose(msax + 0.0073, ins_tab['xmsapos'], rtol=1e-2) # expected offset assert_allclose(msay + 0.0085, ins_tab['ymaspos'], rtol=1e-2) # expected offset # Slicer slit2slicer = slit_wcs.get_transform('slit_frame', 'slicer') x_slicer, y_slicer, _ = slit2slicer(slitx, slity, lam) # MSA exit # Applies the IFUPOST transform to coordinates at the Slicer with datamodels.IFUPostModel(refs['ifupost']) as ifupost: ifupost_transform = nirspec._create_ifupost_transform(ifupost.slice_0) x_msa_exit, y_msa_exit = ifupost_transform(x_slicer, y_slicer, lam) assert_allclose(x_msa_exit, ins_tab['xmsapos']) assert_allclose(y_msa_exit, ins_tab['ymaspos']) # Coordinates at Collimator exit # Applies the Collimator forward transform to coordinates at the MSA exit with datamodels.open(refs['collimator']) as col: colx, coly = col.model.inverse(x_msa_exit, y_msa_exit) assert_allclose(colx, ins_tab['xcoll']) assert_allclose(coly, ins_tab['ycoll']) # After applying direcitonal cosines dircos = trmodels.Unitless2DirCos() xcolDircosi, ycolDircosi, z = dircos(colx, coly) assert_allclose(xcolDircosi, ins_tab['xcolDirCosi']) assert_allclose(ycolDircosi, ins_tab['ycolDirCosi']) # Slit to GWA entrance # applies the Collimator forward, Unitless to Directional and 3D Rotation to MSA exit coordinates with datamodels.DisperserModel(refs['disperser']) as disp: disperser = nirspec.correct_tilt(disp, im.meta.instrument.gwa_xtilt, im.meta.instrument.gwa_ytilt) collimator2gwa = nirspec.collimator_to_gwa(refs, disperser) x_gwa_in, y_gwa_in, z_gwa_in = collimator2gwa(x_msa_exit, y_msa_exit) assert_allclose(x_gwa_in, ins_tab['xdispIn']) # Slit to GWA out # Runs slit--> slicer --> msa_exit --> collimator --> dircos --> rotation --> angle_from_grating equation slit2gwa = slit_wcs.get_transform('slit_frame', 'gwa') x_gwa_out, y_gwa_out, z_gwa_out = slit2gwa(slitx, slity, lam) assert_allclose(x_gwa_out, ins_tab['xdispLaw']) assert_allclose(y_gwa_out, ins_tab['ydispLaw']) # CAMERA entrance (assuming direction is from sky to detector) angles = [ disperser['theta_x'], disperser['theta_y'], disperser['theta_z'], disperser['tilt_y'] ] rotation = trmodels.Rotation3DToGWA(angles, axes_order="xyzy", name='rotation') dircos2unitless = trmodels.DirCos2Unitless() gwa2cam = rotation.inverse | dircos2unitless x_camera_entrance, y_camera_entrance = gwa2cam(x_gwa_out, y_gwa_out, z_gwa_out) assert_allclose(x_camera_entrance, ins_tab['xcamCosi']) assert_allclose(y_camera_entrance, ins_tab['ycamCosi']) # at FPA with datamodels.CameraModel(refs['camera']) as camera: x_fpa, y_fpa = camera.model.inverse(x_camera_entrance, y_camera_entrance) assert_allclose(x_fpa, ins_tab['xfpapos']) assert_allclose(y_fpa, ins_tab['yfpapos']) # at SCA slit2sca = slit_wcs.get_transform('slit_frame', 'sca') x_sca_nrs1, y_sca_nrs1 = slit2sca(slitx, slity, lam) # At NRS2 with datamodels.FPAModel(refs['fpa']) as fpa: x_sca_nrs2, y_sca_nrs2 = fpa.nrs2_model.inverse(x_fpa, y_fpa) assert_allclose(x_sca_nrs1 + 1, ins_tab['i']) assert_allclose(y_sca_nrs1 + 1, ins_tab['j']) # at oteip # Goes through slicer, ifufore, and fore transforms slit2oteip = slit_wcs.get_transform('slit_frame', 'oteip') x_oteip, y_oteip, _ = slit2oteip(slitx, slity, lam) assert_allclose(x_oteip, ins_tab['xOTEIP']) assert_allclose(y_oteip, ins_tab['yOTEIP']) # at v2, v3 [in arcsec] slit2v23 = slit_wcs.get_transform('slit_frame', 'v2v3') v2, v3, _ = slit2v23(slitx, slity, lam) v2 /= 3600 v3 /= 3600 assert_allclose(v2, ins_tab['xV2V3']) assert_allclose(v3, ins_tab['yV2V3'])
def test_functional_fs_msa(mode): # """ # Compare Nirspec instrument model with IDT model for FS and MSA. # """ if mode == 'fs': model_file = 'fixed_slits_functional_ESA_v4_20180618.txt' hdul = create_nirspec_fs_file(grating='G395H', filter='F290LP') im = datamodels.ImageModel(hdul) refs = create_reference_files(im) pipeline = nirspec.create_pipeline(im, refs, slit_y_range=[-0.55, 0.55]) w = wcs.WCS(pipeline) im.meta.wcs = w # Use slit S200A1 slit_wcs = nirspec.nrs_wcs_set_input(im, 'S200A1') if mode == 'msa': model_file = 'msa_functional_ESA_v2_20180620.txt' hdul = create_nirspec_mos_file(grating='G395H', filt='F290LP') im = datamodels.ImageModel(hdul) refs = create_reference_files(im) slit = trmodels.Slit(name=1, shutter_id=4699, xcen=319, ycen=13, ymin=-0.55000000000000004, ymax=0.55000000000000004, quadrant=3, source_id=1, shutter_state='x', source_name='lamp', source_alias='foo', stellarity=100.0, source_xpos=-0.5, source_ypos=0.5) open_slits = [slit] pipeline = nirspec.slitlets_wcs(im, refs, open_slits) w = wcs.WCS(pipeline) im.meta.wcs = w slit_wcs = nirspec.nrs_wcs_set_input(im, 1) ins_file = get_file_path(model_file) ins_tab = table.Table.read(ins_file, format='ascii') # Setup the test slitx = [0] * 5 slity = [-.5, -.25, 0, .25, .5] lam = np.array([2.9, 3.39, 3.88, 4.37, 5]) * 10**-6 # Slit to MSA absolute slit2msa = slit_wcs.get_transform('slit_frame', 'msa_frame') msax, msay, _ = slit2msa(slitx, slity, lam) assert_allclose(slitx, ins_tab['xslitpos']) assert_allclose(slity, ins_tab['yslitpos']) assert_allclose(msax, ins_tab['xmsapos']) assert_allclose(msay, ins_tab['ymaspos']) # Coordinates at Collimator exit # Applies the Collimator forward transform to MSa absolute coordinates with datamodels.open(refs['collimator']) as col: colx, coly = col.model.inverse(msax, msay) assert_allclose(colx, ins_tab['xcoll']) assert_allclose(coly, ins_tab['ycoll']) # After applying direcitonal cosines dircos = trmodels.Unitless2DirCos() xcolDircosi, ycolDircosi, z = dircos(colx, coly) assert_allclose(xcolDircosi, ins_tab['xcolDirCosi']) assert_allclose(ycolDircosi, ins_tab['ycolDirCosi']) # MSA to GWA entrance # This runs the Collimator forward, Unitless to Directional cosine, and # 3D Rotation. It uses the corrected GWA tilt value with datamodels.DisperserModel(refs['disperser']) as disp: disperser = nirspec.correct_tilt(disp, im.meta.instrument.gwa_xtilt, im.meta.instrument.gwa_ytilt) collimator2gwa = nirspec.collimator_to_gwa(refs, disperser) x_gwa_in, y_gwa_in, z_gwa_in = collimator2gwa(msax, msay) assert_allclose(x_gwa_in, ins_tab['xdispIn']) assert_allclose(y_gwa_in, ins_tab['ydispIn']) # Slit to GWA out slit2gwa = slit_wcs.get_transform('slit_frame', 'gwa') x_gwa_out, y_gwa_out, z_gwa_out = slit2gwa(slitx, slity, lam) assert_allclose(x_gwa_out, ins_tab['xdispLaw']) assert_allclose(y_gwa_out, ins_tab['ydispLaw']) # CAMERA entrance (assuming direction is from sky to detector) angles = [ disperser['theta_x'], disperser['theta_y'], disperser['theta_z'], disperser['tilt_y'] ] rotation = trmodels.Rotation3DToGWA(angles, axes_order="xyzy", name='rotation') dircos2unitless = trmodels.DirCos2Unitless() gwa2cam = rotation.inverse | dircos2unitless x_camera_entrance, y_camera_entrance = gwa2cam(x_gwa_out, y_gwa_out, z_gwa_out) assert_allclose(x_camera_entrance, ins_tab['xcamCosi']) assert_allclose(y_camera_entrance, ins_tab['ycamCosi']) # at FPA with datamodels.CameraModel(refs['camera']) as camera: x_fpa, y_fpa = camera.model.inverse(x_camera_entrance, y_camera_entrance) assert_allclose(x_fpa, ins_tab['xfpapos']) assert_allclose(y_fpa, ins_tab['yfpapos']) # at SCA These are 0-based , the IDT results are 1-based slit2sca = slit_wcs.get_transform('slit_frame', 'sca') x_sca_nrs1, y_sca_nrs1 = slit2sca(slitx, slity, lam) # At NRS2 with datamodels.FPAModel(refs['fpa']) as fpa: x_sca_nrs2, y_sca_nrs2 = fpa.nrs2_model.inverse(x_fpa, y_fpa) # expect 1 pix difference wvlns_on_nrs1 = slice(2) wvlns_on_nrs2 = slice(2, 4) assert_allclose(x_sca_nrs1[wvlns_on_nrs1] + 1, ins_tab['i'][wvlns_on_nrs1]) assert_allclose(y_sca_nrs1[wvlns_on_nrs1] + 1, ins_tab['j'][wvlns_on_nrs1]) assert_allclose(x_sca_nrs2[wvlns_on_nrs2] + 1, ins_tab['i'][wvlns_on_nrs2]) assert_allclose(y_sca_nrs2[wvlns_on_nrs2] + 1, ins_tab['j'][wvlns_on_nrs2]) # at oteip slit2oteip = slit_wcs.get_transform('slit_frame', 'oteip') x_oteip, y_oteip, _ = slit2oteip(slitx, slity, lam) assert_allclose(x_oteip, ins_tab['xOTEIP']) assert_allclose(y_oteip, ins_tab['yOTEIP']) # at v2, v3 [in arcsec] slit2v23 = slit_wcs.get_transform('slit_frame', 'v2v3') v2, v3, _ = slit2v23(slitx, slity, lam) v2 /= 3600 v3 /= 3600 assert_allclose(v2, ins_tab['xV2V3']) assert_allclose(v3, ins_tab['yV2V3'])