<jupyter_start><jupyter_text>Walkthrough the NIRCAM imaging WCS pipeline rountrip of values through the coordinate frame transforms<jupyter_code>import jwst
jwst.__version__
from astropy.io import fits
from jwst import assign_wcs
from jwst.datamodels import image
# add in the columns for ra and dec min/max points, translated from the wcs object for now
direct_data='test_disperse_f335m_rate_updated.fits'   # original image provided for testing

# We will open the direct image as an Image datamodel
direct_image = image.ImageModel(direct_data)<jupyter_output><empty_output><jupyter_text>Some basics about this image<jupyter_code>direct_image.meta.instrument.pupil, direct_image.meta.instrument.filter, direct_image.meta.instrument.module, direct_image.meta.instrument.detector, direct_image.meta.instrument.channel<jupyter_output><empty_output><jupyter_text>This is the FITS WCS information<jupyter_code>direct_image.get_fits_wcs()
# Load up the assign_wcs step that will assign all the transforms from world->detector
assign_wcs_step=assign_wcs.AssignWcsStep()
reference_file_types = ['distortion', 'filteroffset', 'specwcs', 'regions',
                            'wavelengthrange', 'camera', 'collimator',
                            'disperser', 'fore', 'fpa', 'msa', 'ote', 'ifupost',
                            'ifufore', 'ifuslicer']
reference_file_names = {}

# Ask CRDS for the reference files that apply to the image are working with
for name in reference_file_types:
    reffile = assign_wcs_step.get_reference_file(direct_image, name)
    reference_file_names[name] = reffile if reffile else ""
reference_file_names
direct_gwcs = assign_wcs_step(direct_image)<jupyter_output>2018-04-26 17:39:15,552 - stpipe.AssignWcsStep - INFO - Step AssignWcsStep running with args (<ImageModel(2048, 2048) from test_disperse_f335m_rate_updated.fits>,).
2018-04-26 17:39:15,958 - stpipe.AssignWcsStep - INFO - assign_wcs updated S_REGION to POLYGON ICRS  0.009022622237533287 -0.008853991096776785 0.008719091565190015 0.008959908112853443 359.9911115957577 0.008837456490423869 359.9911689262167 -0.00902295821400355
2018-04-26 17:39:15,959 - stpipe.AssignWcsStep - INFO - COMPLETED assign_wcs
2018-04-26 17:39:15,968 - stpipe.AssignWcsStep - INFO - Step AssignWcsStep done<jupyter_text>Some information about where the transforms are centered<jupyter_code>direct_gwcs.meta.wcsinfo.crpix1, direct_gwcs.meta.wcsinfo.crpix2, direct_gwcs.meta.wcsinfo.crval1, direct_gwcs.meta.wcsinfo.crval2<jupyter_output><empty_output><jupyter_text>The GWCS object that contains all the transforms is now attached to the image model<jupyter_code>direct_gwcs.meta.wcs<jupyter_output><empty_output><jupyter_text>Check the transform from detector pixels to sky coordinates in decimal degrees of RA and DEC The default transform goes from detector pixels to sky coordinate (ra,dec)<jupyter_code>direct_gwcs.meta.wcs(110,110)
detector_to_world = direct_gwcs.meta.wcs.get_transform('detector','world')
detector_to_world(110,110)<jupyter_output><empty_output><jupyter_text>Now get the inverse transform from RA,DEC  to detector pixels, using the RA,DEC we just calculatedThis should return the pixel (110,110)<jupyter_code>world_to_detector = direct_gwcs.meta.wcs.get_transform('world','detector')
world_to_detector(0.00804448203007923, -0.007899731808577077)<jupyter_output><empty_output><jupyter_text>Let's check the other transforms to make sure it's just the distortion reference file that is off<jupyter_code>direct_gwcs.meta.wcs.available_frames
world_to_v2v3 = direct_gwcs.meta.wcs.get_transform('world','v2v3')
world_to_v2v3(0.00804448203007923, -0.007899731808577077)  # degrees
v2v3_to_world = direct_gwcs.meta.wcs.get_transform('v2v3','world')
v2v3_to_world(149.63161618088085, -555.8266943126895)  # arcseconds<jupyter_output><empty_output><jupyter_text>The following transforms only goes through the distortion reference file, it can't seem to return the original detector coordinates<jupyter_code>detector_to_v2v3 = direct_gwcs.meta.wcs.get_transform('detector','v2v3')
detector_to_v2v3(110, 110)
v2v3_to_detector = direct_gwcs.meta.wcs.get_transform('v2v3','detector')
v2v3_to_detector(149.63161618088085, -555.8266943126896)<jupyter_output><empty_output><jupyter_text>The transform across the distortion image is not able to reproduce values roundtrippingLet's check if we can reproduce the anchor point of the distortion, the value at CRPIX1, CRPIX2<jupyter_code>crpix1, crpix2, crval1, crval2=direct_gwcs.meta.wcsinfo.crpix1, direct_gwcs.meta.wcsinfo.crpix2, direct_gwcs.meta.wcsinfo.crval1, direct_gwcs.meta.wcsinfo.crval2<jupyter_output><empty_output><jupyter_text>Check the roundtrip of crpix1,crpix2 from detector  v2v3This also uses the distortion reference file<jupyter_code>crpix1, crpix2
detector_to_v2v3(crpix1, crpix2)
v2v3_to_detector(120.63991845281103, -527.3565915096161)<jupyter_output><empty_output><jupyter_text>Check the roundtrip of crval1,crval2 from world  v2v3<jupyter_code>crval1, crval2
world_to_v2v3(crval1, crval2)
v2v3_to_world(120.67137599999998, -527.387665)<jupyter_output><empty_output><jupyter_text>The above examples convince me that the distortion reference image, specifically `jwst_nircam_distortion_0061.asdf` has 2D variations that make it impossible to compute the correct detector pixel coordinates given a position on the sky. It's possible that the *incorrect* distortion reference image is being returned from CRDS for the image, let's have a look at the RMAP that is being used  This is the current rmap in usehttps://jwst-crds.stsci.edu/browse/jwst_nircam_distortion_0018.rmapIt's checking these values:        'parkey' : (('META.EXPOSURE.TYPE', 'META.INSTRUMENT.DETECTOR', 'META.INSTRUMENT.CHANNEL', 'META.INSTRUMENT.PUPIL', 'META.INSTRUMENT.FILTER'),    ('META.OBSERVATION.DATE', 'META.OBSERVATION.TIME')),        'reference_to_dataset' : {            'CHANNEL' : 'META.INSTRUMENT.CHANNEL',            'DETECTOR' : 'META.INSTRUMENT.DETECTOR',            'EXP_TYPE' : 'META.EXPOSURE.TYPE',<jupyter_code>direct_image.meta.exposure.type, direct_image.meta.instrument.detector, direct_image.meta.instrument.channel, direct_image.meta.instrument.pupil, direct_image.meta.instrument.filter<jupyter_output><empty_output><jupyter_text>For the values specified above, the RMAP shows matching as:    ('NRC_IMAGE|NRC_TSIMAGE|NRC_FLAT|NRC_LED|NRC_WFSC|NRC_GRISM|NRC_TSGRISM|NRC_FOCUS',     'NRCA1',     'SHORT',     'CLEAR|F162M|F164N|GDHS0|GDHS60|WLM8|WLP8|PINHOLES|MASKIPR',     'N/A') : UseAfter({'2014-10-01 00:00:00' : 'jwst_nircam_distortion_0061.asdf',    }),    So the reference file matches regardness of FILTER (which has N/A) that is specified, it only cares about pupil and the detector specification  Let's try the same thing with a different image, this one is taken from the latest NIRCAM simulationsIt has a differently populated FITS WCS information, and specifies a different filter and detector<jupyter_code>direct_data='V54321001002P000000000110d_A5_F444W_rate.fits'   # most recent simulation for testing

# We will open the direct image as a DrizProduct datamodel
direct_image = image.ImageModel(direct_data)
direct_image.meta.instrument.pupil, direct_image.meta.instrument.filter,direct_image.meta.instrument.module, direct_image.meta.instrument.detector, direct_image.meta.instrument.channel
reference_file_names = {}

# Ask CRDS for the reference files that apply to the image are working with
for name in reference_file_types:
    reffile = assign_wcs_step.get_reference_file(direct_image, name)
    reference_file_names[name] = reffile if reffile else ""
reference_file_names
direct_image.get_fits_wcs()
direct_gwcs = assign_wcs_step(direct_image)
direct_gwcs.meta.wcs<jupyter_output><empty_output><jupyter_text>Check the transform from detector pixels to sky coordinates in decimal degrees of RA and DEC The default transform goes from detector pixels to sky coordinate (ra,dec)<jupyter_code>direct_gwcs.meta.wcs(110,110)
detector_to_world = direct_gwcs.meta.wcs.get_transform('detector','world')
detector_to_world(110,110)<jupyter_output><empty_output><jupyter_text>Now get the inverse transform from RA,DEC  to detector pixels, using the RA,DEC we just calculatedThis should return the pixel (110,110)<jupyter_code>world_to_detector = direct_gwcs.meta.wcs.get_transform('world','detector')
world_to_detector(53.149149027123194, -27.839618613331695)<jupyter_output><empty_output><jupyter_text>Let's check the other transforms to make sure it's just the distortion reference file that is off<jupyter_code>direct_gwcs.meta.wcs.available_frames
world_to_v2v3 = direct_gwcs.meta.wcs.get_transform('world','v2v3')
world_to_v2v3(53.149149027123194, -27.839618613331695)  # degrees
v2v3_to_world = direct_gwcs.meta.wcs.get_transform('v2v3','world')
v2v3_to_world(144.31111617155733, -550.8134158050235)  # arcseconds<jupyter_output><empty_output><jupyter_text>The following transforms only goes through the distortion reference file, it can't seem to return the original detector coordinates<jupyter_code>detector_to_v2v3 = direct_gwcs.meta.wcs.get_transform('detector','v2v3')
detector_to_v2v3(110, 110)
v2v3_to_detector = direct_gwcs.meta.wcs.get_transform('v2v3','detector')
v2v3_to_detector(144.31111617152274, -550.8134158049928)<jupyter_output><empty_output><jupyter_text>The transform across the distortion image is not able to reproduce values roundtrippingLet's check if we can reproduce the anchor point of the distortion, the value at CRPIX1, CRPIX2<jupyter_code>crpix1, crpix2, crval1, crval2=direct_gwcs.meta.wcsinfo.crpix1, direct_gwcs.meta.wcsinfo.crpix2, direct_gwcs.meta.wcsinfo.crval1, direct_gwcs.meta.wcsinfo.crval2
crval1,crval2, direct_gwcs.meta.wcsinfo.roll_ref<jupyter_output><empty_output><jupyter_text>Check the roundtrip of crpix1,crpix2 from detector  v2v3This also uses the distortion reference file<jupyter_code>crpix1, crpix2
detector_to_v2v3(crpix1, crpix2)
v2v3_to_detector(86.04055467237623, -493.16454761867965)<jupyter_output><empty_output><jupyter_text>Check the roundtrip of crval1,crval2 from world  v2v3<jupyter_code>crval1, crval2
world_to_v2v3(crval1, crval2)
v2v3_to_world(86.10345800001141, -493.2275120000079)<jupyter_output><empty_output><jupyter_text>Using a different distortion reference file we still are seeing the same offsets with the reverse transform.We can do a little more detective work and chart the roundtrip offsets that are present in all the distortion reference files.First we need to get a local copy of all the distortion reference files in CRDS for the NRC_IMAGE mode.I'm going to do this by asking CRDS. Make sure you have these environment variables set:    CRDS_SERVER_URL=https://jwst-crds.stsci.edu    CRDS_PATH=/Users/sosey/crds_cache  --> wherever you want the files stored locally         get all the nircam distortion files currently in use    crds sync --contexts jwst-nircam-distortion-operational --fetch-references<jupyter_code>import glob
dist_files=glob.glob('/Users/sosey/crds_cache/references/jwst/nircam/*distortion*')<jupyter_output><empty_output><jupyter_text>Let's make a WCS object for each of the distortion files that will take us through the transform.We use the most recent image as the starting point and direct the distortion file to use.<jupyter_code>from jwst.assign_wcs import nircam
from jwst.datamodels.wcs_ref_models import DistortionModel
from gwcs.wcs import WCS<jupyter_output><empty_output><jupyter_text>Next we'll cut the list down to just the distortion files used with imageing mode<jupyter_code>image_dist=[]
for dist in dist_files:
    print(dist)
    data=DistortionModel(dist)
    try:
        if (data['exp_type'] == 'NRC_IMAGE'):
            image_dist.append(dist)
    except KeyError:
        try:
            if (data['EXP_TYPE'] == 'NRC_IMAGE'):
                image_dist.append(dist)
        except KeyError:
            if "NRC_IMAGE" in data.meta.exposure.p_exptype:
                image_dist.append(dist)
    data.close()
direct_data='V54321001002P000000000110d_A5_F444W_rate.fits'   # latest simulated image for testing

# We will open the direct image as a DrizProduct datamodel
direct_image = image.ImageModel(direct_data)
direct_image.meta.instrument.pupil, direct_image.meta.instrument.filter,direct_image.meta.instrument.module, direct_image.meta.instrument.detector, direct_image.meta.instrument.channel
# Load up the assign_wcs step to populate our structure, we should see only the distortion file being used
assign_wcs_step=assign_wcs.AssignWcsStep()
reference_file_types = ['distortion', 'filteroffset', 'specwcs', 'regions',
                            'wavelengthrange', 'camera', 'collimator',
                            'disperser', 'fore', 'fpa', 'msa', 'ote', 'ifupost',
                            'ifufore', 'ifuslicer']
reference_file_names = {}

# Ask CRDS for the reference files that apply to the image are working with
for name in reference_file_types:
    reffile = assign_wcs_step.get_reference_file(direct_image, name)
    reference_file_names[name] = reffile if reffile else ""
reference_file_names<jupyter_output>2018-04-26 17:39:40,195 - stpipe.AssignWcsStep - INFO - AssignWcsStep instance created.<jupyter_text>I'm going to call a part of the pipeline that already assumes the correct file has been matched by CRDS, so I should just need to give it the reference file to use and it will return the pipeline that includes that file<jupyter_code>results=[]
for dist in image_dist:
    reference_file_names['distortion'] = dist
    pipeline = nircam.imaging(direct_image, reference_file_names)
    test_wcs = WCS(pipeline)
    ra,dec = test_wcs(110,110)
    try:
        w2d = test_wcs.get_transform('world','detector')
        x,y = w2d(ra,dec)
        results.append({'dfile':dist, 'ra':ra, 'dec':dec, 'x':x, 'y':y, 'start_x': 110, 'start_y': 110})
    except NotImplementedError:
        pass
    
    
line = '{:>79}  {:>8}  {:>8}  {:>19}  {:>19}  {:>19}  {:>19}'.format("FILE", "START_X", "START_Y","RETURNED X", "RETURNED Y", "DELTA X", "DELTA Y")
print(line)
for res in results:
    print("{:>79}  {:>8}  {:>8}  {:>19}  {:>19}  {:>19}  {:>19}".format(res['dfile'], res['start_x'], res['start_y'],res['x'], res['y'], res['start_x'] - res['x'], res['start_y']-res['y']))<jupyter_output>FILE   START_X   START_Y           RETURNED X           RETURNED Y              DELTA X              DELTA Y
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0004.asdf       110       110  -32211.563169272278   -4414611.484215153   32321.563169272278    4414721.484215153
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0011.asdf       110       110   110.50304890545871   109.23927236754712  -0.5030489054587122    0.760727632452884
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0012.asdf       110       110   110.34137150293236   109.62428757378888  -0.3413715029323612  0.37571242621112333
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0013.asdf       110       110   109.94816366972968    110.0303481756208  0.05183633027031931  -0.0303481756208015
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0014.asdf [...]<jupyter_text>NOTE: according to the RMAP, distortion files numbered prior to `*0011.asf` are old and replaced by the useafter date with updated files, so the results for `jwst_nircam_distortion_0004.asdf` above can be ignored  We can do the same number crunching just using the the model inside the distortion reference file itself, and we should get the same answer as with the GWCS model<jupyter_code>results=[]
for dist in image_dist:
    model = DistortionModel(dist).model
    try:
        v2v3x, v2v3y = model(110, 110)
        x, y = model.inverse(v2v3x, v2v3y)
        results.append({'dfile':dist, 'v2v3x':v2v3x, 'v2v3y':v2v3y, 'x':x, 'y':y, 'start_x': 110, 'start_y': 110})
    except NotImplementedError:
        pass
line = '{:>79}  {:>8}  {:>8}  {:>19}  {:>19}  {:>19}  {:>19}'.format("FILE", "START_X", "START_Y","RETURNED X", "RETURNED Y", "DELTA X", "DELTA Y")
print(line)
for res in results:
    print("{:>79}  {:>8}  {:>8}  {:>19}  {:>19}  {:>19}  {:>19}".format(res['dfile'], res['start_x'], res['start_y'],res['x'], res['y'], res['start_x'] - res['x'], res['start_y']-res['y']))<jupyter_output>FILE   START_X   START_Y           RETURNED X           RETURNED Y              DELTA X              DELTA Y
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0004.asdf       110       110   -32211.56316247882   -4414611.483280538    32321.56316247882    4414721.483280538
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0011.asdf       110       110   110.50304890570733   109.23927236783844  -0.5030489057073311   0.7607276321615615
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0012.asdf       110       110   110.34137150365541   109.62428757533306  -0.34137150365540947  0.37571242466694343
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0013.asdf       110       110    109.9481636701729   110.03034817585473  0.05183632982709696  -0.030348175854726378
/Users/sosey/crds_cache/references/jwst/nircam/jwst_nircam_distortion_0014.as[...]<jupyter_text>The NIRCAM team used scripts that translate through the SIAF file as part of their data simulatorBelow, I'll go through the same process and see what happens (communicae via Hilbert):    The SIAF file contains all of the relevant coordinate system definitions and parameters, as well as the polynomial coefficients which describe the translation between the V2,V3 system and pixel space for each NIRCam aperture on each detector. <NAME>'s report from 2009 which defines the terms in the SIAF file and shows how to use the information in the SIAF file to generate functions for translating between coordinate systems.     (Note that the translation functions and definitions are built around pixel coordinates that are indexed to 1. So if you are going to run translations in python, where things are indexed to 0, be sure to add 1 when creating inputs for the translation models, and subtract 1 from the outputs.)    If you are interested in quickly being able to translate between pixel space and RA/Dec or V2/V3, I have already used the data in the SIAF file to construct distortion reference files. These are the reference files that will be used in the DMS pipeline. There's a separate file for each aperture. I've attached the file for full-frame A1 observations as an example. Let me know if you would like others.    I've attached some python code in coord_translate.py that contains everything you need to go from x,y to RA,Dec and back. The script depends on the other attached python scripts.Inside the scripts, there are instructions for translating between x,y  ra,dec through the SIAF file<jupyter_code>from asdf import AsdfFile
from astropy.io import ascii
import numpy as np

from SIAFDistortions import rotations
from SIAFDistortions import read_siaf_table
from SIAFDistortions import polynomial

distortionTable = ascii.read('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv', header_start=1)<jupyter_output><empty_output><jupyter_text>Method 1, (Ra, Dec)--> (x, y)  just using the distortion reference file    There are two methods for translating from RA,Dec to x,y. The first makes use of only    the distortion reference file (the asdf file). This method is faster (and is    therefore used within the DMS pipeline), but loses accuracy the farther from the    reference location that you get. For full frame observations, errors can approach    ~20 pixels at the corners of the detector (ther reference location is the center    of the detector).<jupyter_code>def RADecToXY_approx(ra,dec,attitude_matrix,coord_transform,refpix_v2,refpix_v3):
    #If the full set of distortion coefficients are not provided,
    #(i.e. you don't have the SIAF file)
    #then we fall back to the coordinate transform provided by the
    #distortion reference file. These results are not exact, and
    #become less accurate the farther the source is from the center
    #of the detector. Results can be incorrect by ~20 pixels in the
    #corners of the detector.


    #RA,Dec to V2,V3
    pixelv2,pixelv3 = rotations.getv2v3(attitude_matrix,ra,dec)
    
    #V2,V3 to distorted pixels
    deltapixelx,deltapixely = coord_transform.inverse(pixelv2-refpix_v2,pixelv3-refpix_v3)

    return deltapixelx,deltapixely
#distortion reference file to use
dist_reffile = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'

#RA and Dec you wish to convert to x,y
ra = 53.1490299775  # decimal degrees. RA you wish to convert to x,y
dec = -27.8168745624 # decimal degrees. Dec you wish to convert to x,y

#telescope pointing information 
tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
tel_rot = 45.04234416817661 #telescope rotation, degrees.

#in this case, if you don't have the SIAF file, you'll need to get the reference
#location v2,v3 coordinates for the desired aperture from some other source.
refpix_v2 = 120.6714   # arcsec. reference location is usually center of aperture 
refpix_v3 = -527.3877  # arcsec. 
refpix_x = 1024.5 # pixels. reference location x for desired aperture
refpix_y = 1024.5 # pixels. reference location y for desired aperture

#Read in the CRDS-format distortion reference file
with AsdfFile.open(dist_reffile) as dist_file:
    coord_transform = dist_file.tree['model']

#Create attitude_matrix
attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

#Translate
dx,dy = RADecToXY_approx(ra,dec,attitude_matrix,coord_transform,refpix_v2,refpix_v3)

#Final x,y value
pixelx = dx + refpix_x
pixely = dy + refpix_y

print("Approx {},{}".format(pixelx,pixely))<jupyter_output>Approx 1024.4999999998824,1024.4999999999436<jupyter_text>I'm going to turn the above into a function that accepts, ra, dec, distortion file so I can compare the differences with the above results<jupyter_code>def method_1_sky_to_pix(ra=0., dec=0., distortion_file=None):
    if distortion_file is None:
        distortion_file = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'
    with AsdfFile.open(distortion_file) as dist_file:
        coord_transform = dist_file.tree['model']
                
    #telescope pointing information 
    tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
    tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
    tel_rot = 45.04234416817661 #telescope rotation, degrees.

    #in this case, if you don't have the SIAF file, you'll need to get the reference
    #location v2,v3 coordinates for the desired aperture from some other source.
    refpix_v2 = 120.6714   # arcsec. reference location is usually center of aperture 
    refpix_v3 = -527.3877  # arcsec. 
    refpix_x = 1024.5 # pixels. reference location x for desired aperture
    refpix_y = 1024.5 # pixels. reference location y for desired aperture

    #Create attitude_matrix
    attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

    #Translate
    dx,dy = RADecToXY_approx(ra,dec,attitude_matrix,coord_transform,refpix_v2,refpix_v3)

    #Final x,y value
    pixelx = dx + refpix_x
    pixely = dy + refpix_y

    return (pixelx,pixely)<jupyter_output><empty_output><jupyter_text>Method 2, (Ra, Dec) --> (x, y) using the distortion reference file and the SIAF file    The second method of translating from RA,Dec to pixel x,y uses extra information in    the SIAF file that is not present in the distortion coefficient file. It is    computationally slower than the other method, but has minimal errors.<jupyter_code>def getDistortionCoefficients(table,from_sys,to_sys,aperture):
    '''from the table of distortion coefficients, get the coeffs that 
    correspond to the requested transformation and return as a list 
    for x and another for y
    '''
    match = table['AperName'] == aperture
    if np.any(match) == False:
        print("Aperture name {} not found in input CSV file.".format(aperture))
        sys.exit()

    row = table[match]

    if ((from_sys == 'science') & (to_sys == 'ideal')):
        label = 'Sci2Idl'
    elif ((from_sys == 'ideal') & (to_sys == 'science')):
        label = 'Idl2Sci'
    else:
        print("WARNING: from_sys of {} and to_sys of {} not a valid transformation.".format(from_sys,to_sys))
        sys.exit()
        
    #get the coefficients, return as list
    X_cols = [c for c in row.colnames if label+'X' in c]
    Y_cols = [c for c in row.colnames if label+'Y' in c]
    x_coeffs = [row[c].data[0] for c in X_cols]
    y_coeffs = [row[c].data[0] for c in Y_cols]

    #Also get the V2,V3 and x,y values of the reference pixel
    v2ref = row['V2Ref'].data[0]
    v3ref = row['V3Ref'].data[0]
    xref = row['XSciRef'].data[0]
    yref = row['YSciRef'].data[0]
    
    #Get parity and V3 Y angle info as well
    parity = row['VIdlParity'].data[0]
    yang = row['V3IdlYAngle'].data[0]
        
    return x_coeffs,y_coeffs,v2ref,v3ref,xref,yref,parity,yang

def RADecToXY_exact(ra,dec,attitude_matrix,v2v32idlx,v2v32idly,v2_ref,v3_ref,x_sci2idl,y_sci2idl): 
    #RA,Dec to V2,V3
    pixelv2,pixelv3 = rotations.getv2v3(attitude_matrix,ra,dec)

    #Now V2,V3 to undistorted angular distance from the reference pixel
    xidl = v2v32idlx(pixelv2-v2_ref,pixelv3-v3_ref)
    yidl = v2v32idly(pixelv2-v2_ref,pixelv3-v3_ref)
                                
    #Finally, undistorted distances to distorted pixel values
    deltapixelx, deltapixely, err, iter = polynomial.invert(x_sci2idl,y_sci2idl,xidl,yidl,5)

    return deltapixelx,deltapixely

#distortion reference file to use
dist_reffile = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'

#aperture_name
ap_name = 'NRCA1_FULL'

#RA and Dec you wish to convert to x,y
ra = 53.1490299775  # decimal degrees. RA you wish to convert to x,y
dec = -27.8168745624 # decimal degrees. Dec you wish to convert to x,y

#telescope pointing information 
tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
tel_rot = 45.04234416817661 #telescope rotation, degrees.


#read in the SIAF file
distortionTable = ascii.read('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',header_start=1)

#get the extra parameters needed from the SIAF file
x_sci2idl,y_sci2idl,refpix_v2,refpix_v3,refpix_x,refpix_y,parity,v3yang = getDistortionCoefficients(distortionTable,'science','ideal',ap_name)

#generate the fucntion which will translate from V2,V3 to undistorted coordinates
v2v32idlx, v2v32idly = read_siaf_table.get_siaf_v2v3_transform('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',ap_name,to_system='ideal')

#Create attitude_matrix
attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

#Translate
dx,dy = RADecToXY_exact(ra,dec,attitude_matrix,v2v32idlx,v2v32idly,refpix_v2,refpix_v3,x_sci2idl,y_sci2idl)

#Final x,y value
pixelx = dx + refpix_x
pixely = dy + refpix_y

print("Exact {},{}".format(pixelx,pixely))<jupyter_output>Exact 1024.4999999998815,1024.4999999999445<jupyter_text>The function below does the example shown above, accepting x, y, distortion_file<jupyter_code>def method_2_sky_to_pix(ra=0., dec=0., distortion_file=None, ap_name=None):
    
    if distortion_file is None:
        distortion_file = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'
        ap_name = 'NRCA1_FULL'
    with AsdfFile.open(distortion_file) as dist_file:
        coord_transform = dist_file.tree['model']
                
    #aperture_name
    if ap_name is None:
        raise ValueError("Expected ap_name for distortion file")

    #telescope pointing information 
    tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
    tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
    tel_rot = 45.04234416817661 #telescope rotation, degrees.


    #read in the SIAF file
    distortionTable = ascii.read('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',header_start=1)

    #get the extra parameters needed from the SIAF file
    x_sci2idl,y_sci2idl,refpix_v2,refpix_v3,refpix_x,refpix_y,parity,v3yang = getDistortionCoefficients(distortionTable,'science','ideal',ap_name)

    #generate the fucntion which will translate from V2,V3 to undistorted coordinates
    v2v32idlx, v2v32idly = read_siaf_table.get_siaf_v2v3_transform('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',ap_name,to_system='ideal')

    #Create attitude_matrix
    attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

    #Translate
    dx,dy = RADecToXY_exact(ra,dec,attitude_matrix,v2v32idlx,v2v32idly,refpix_v2,refpix_v3,x_sci2idl,y_sci2idl)

    #Final x,y value
    pixelx = dx + refpix_x
    pixely = dy + refpix_y

    return (pixelx,pixely)<jupyter_output><empty_output><jupyter_text>Method, (x, y) --> (Ra, Dec)    Translating from x,y to RA,Dec is simpler, with only one method, which    gives exact answers<jupyter_code>def XYToRADec(pixelx,pixely,attitude_matrix,coord_transform,refpix_x,refpix_y,refpix_v2,refpix_v3):
    #Translate a given x,y location on the detector
    #to RA,Dec

    #Transform distorted pixels to V2,V3
    deltav2,deltav3 = coord_transform(pixelx-refpix_x,pixely-refpix_y)
    pixelv2 = deltav2 + refpix_v2
    pixelv3 = deltav3 + refpix_v3

    #Now translate V2,V3 to RA,Dec
    ra,dec = rotations.pointing(attitude_matrix,pixelv2,pixelv3)

    return ra,dec
#pixel coords to translate
pixelx = 1024.5 
pixely = 1024.5

#telescope pointing information 
tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
tel_rot = 45.04234416817661 #telescope rotation, degrees.

#distortion reference file to use
dist_reffile = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'
ap_name = 'NRCA1_FULL'

#Read in the CRDS-format distortion reference file
with AsdfFile.open(dist_reffile) as dist_file:
    coord_transform = dist_file.tree['model']

#read in the SIAF file
distortionTable = ascii.read('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',header_start=1)

#get parameters needed from the SIAF file
x_sci2idl,y_sci2idl,refpix_v2,refpix_v3,refpix_x,refpix_y,parity,v3yang = getDistortionCoefficients(distortionTable,'science','ideal',ap_name)

#Create attitude_matrix
attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

#Translate
ra,dec = XYToRADec(pixelx,pixely,attitude_matrix,coord_transform,refpix_x,refpix_y,refpix_v2,refpix_v3)

print('RA,Dec is {},{}'.format(ra,dec))
def pix_to_sky(x=0, y=0, distortion_file=None, ap_name=None):
    #telescope pointing information 
    tel_ra = 53.1490299775  # decimal degrees. RA at the reference location on the detector
    tel_dec = -27.8168745624  # decimal degrees. Dec at the reference location on the detector
    tel_rot = 45.04234416817661 #telescope rotation, degrees.

    #distortion reference file to use
    if distortion_file is None:
        distortion_file = 'SIAFDistortions/NRCA1_FULL_distortion.asdf'
        ap_name = 'NRCA1_FULL'
    if ap_name is None:
        raise ValueError("Need to specify ap_name appropriate for the distortion file")

    #Read in the CRDS-format distortion reference file
    with AsdfFile.open(distortion_file) as dist_file:
        coord_transform = dist_file.tree['model']

    #read in the SIAF file
    distortionTable = ascii.read('SIAFDistortions/NIRCam_SIAF_2016-09-29.csv',header_start=1)

    #get parameters needed from the SIAF file
    x_sci2idl,y_sci2idl,refpix_v2,refpix_v3,refpix_x,refpix_y,parity,v3yang = getDistortionCoefficients(distortionTable,'science','ideal',ap_name)

    #Create attitude_matrix
    attitude_matrix = rotations.attitude(refpix_v2,refpix_v3,tel_ra,tel_dec,tel_rot)

    #Translate
    ra,dec = XYToRADec(x, y, attitude_matrix,coord_transform,refpix_x,refpix_y,refpix_v2,refpix_v3)

    return (ra,dec)<jupyter_output><empty_output><jupyter_text>The examples above were pulled from `coord_translate.py` and the telescope pointing information was taken from `V54321001002P000000000110d_A5_F444W_rate.fits` (used in the examples above)Now let's look at the (110,110) pixel that is far away from where the distortion is defined at crpix1, crpix2<jupyter_code>ra, dec = pix_to_sky(110, 110)
ra, dec<jupyter_output><empty_output><jupyter_text>We'll take that calculated ra,dec and feed it back to find the original pixel using the two methods outlined above<jupyter_code>method_1_sky_to_pix(ra, dec)
method_2_sky_to_pix(ra, dec)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08798857212327518, 'fraction_numerical': 0.07175855571089904, 'mean_word_length': 5.039464023494861, 'pattern_counts': {'":': 0, '<': 133, '<?xml version=': 0, '>': 166, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 8, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '34', 'dir': 'jupyter-structured-clean-dedup', 'id': '6317', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 13038, 'n_tokens_neox': 11443, 'n_words': 2870, 'path': 'nircam/NIRCAM-Imaging-Mode-WCS-Roundtrip.ipynb', 'repo_name': 'sosey/jwst-investigate', 'size': '32905'}