Пример #1
0
def test_distortion_schema_bad_valueerror(distortion_model):
    """Check that ValueError is raised for ReferenceFile missing items"""
    dist = DistortionModel(distortion_model, strict_validation=True)
    dist.meta.author = None

    with pytest.raises(ValueError):
        dist.validate()
Пример #2
0
def test_distortion_schema_bad_assertionerror(distortion_model):
    """Check that AssertionError is raised for distortion-specific missing items"""
    dist = DistortionModel(distortion_model, strict_validation=True)
    dist.meta.instrument.channel = None

    with pytest.raises(AssertionError):
        dist.validate()
Пример #3
0
def distortion_model():
    """Create a distortion model that should pass all validation"""
    m = models.Shift(1) & models.Shift(2)
    dist = DistortionModel(model=m, input_units=u.pixel, output_units=u.arcsec)

    dist.meta.reftype = "distortion"
    dist.meta.instrument.name = "NIRCAM"
    dist.meta.instrument.detector = "NRCA1"
    dist.meta.instrument.p_pupil = "F162M|F164N|CLEAR|"
    dist.meta.instrument.pupil = "F162M"
    dist.meta.exposure.p_exptype = "NRC_IMAGE|NRC_TSIMAGE|NRC_FLAT|NRC_LED|NRC_WFSC|"
    dist.meta.exposure.type = "NRC_IMAGE"
    dist.meta.psubarray = "FULL|SUB64P|SUB160)|SUB160P|SUB320|SUB400P|SUB640|"
    dist.meta.subarray.name = "FULL"

    # Populate the following so that no validation warnings or errors happen
    dist.meta.instrument.module = "A"
    dist.meta.instrument.channel = "SHORT"
    dist.meta.input_units = u.degree
    dist.meta.output_units = u.degree
    dist.meta.description = "NIRCam distortion reference file"
    dist.meta.author = "Hank the Septopus"
    dist.meta.pedigree = "Cleveland"
    dist.meta.useafter = "2000-01-01T00:00:00"

    return dist
Пример #4
0
def test_distortion_schema(tmpdir):
    """Make sure DistortionModel roundtrips"""
    m = models.Shift(1) & models.Shift(2)
    dist = DistortionModel(model=m, input_units=u.pixel, output_units=u.arcsec)
    dist.meta.instrument.name = "NIRCAM"
    dist.meta.instrument.detector = "NRCA1"
    dist.meta.instrument.p_pupil = "F162M|F164N|CLEAR|"
    dist.meta.instrument.pupil = "F162M"
    dist.meta.exposure.p_exptype = "NRC_IMAGE|NRC_TSIMAGE|NRC_FLAT|NRC_LED|NRC_WFSC|"
    dist.meta.exposure.type = "NRC_IMAGE"
    dist.meta.psubarray = "FULL|SUB64P|SUB160)|SUB160P|SUB320|SUB400P|SUB640|"
    dist.meta.subarray.name = "FULL"
    path = str(tmpdir.join("test_dist.asdf"))
    dist.save(path)

    with DistortionModel(path) as dist1:
        assert dist1.meta.instrument.p_pupil == dist.meta.instrument.p_pupil
        assert dist1.meta.instrument.pupil == dist.meta.instrument.pupil
        assert dist1.meta.exposure.p_exptype == dist.meta.exposure.p_exptype
        assert dist1.meta.exposure.type == dist.meta.exposure.type
        assert dist1.meta.psubarray == dist.meta.psubarray
        assert dist1.meta.subarray.name == dist.meta.subarray.name
Пример #5
0
def test_open_asdf_readonly(tmpdir):
    tmpfile = str(tmpdir.join('readonly.asdf'))

    with DistortionModel() as model:
        model.meta.telescope = 'JWST'
        model.meta.instrument.name = 'NIRCAM'
        model.meta.instrument.detector = 'NRCA4'
        model.meta.instrument.channel = 'SHORT'
        model.save(tmpfile)

    os.chmod(tmpfile, 0o440)
    assert os.access(tmpfile, os.W_OK) == False

    with datamodels.open(tmpfile) as model:
        assert model.meta.telescope == 'JWST'
Пример #6
0
def test_distortion_schema(distortion_model, tmpdir):
    """Make sure DistortionModel roundtrips"""
    path = str(tmpdir.join("test_dist.asdf"))
    dist = distortion_model
    dist.save(path)

    with pytest.warns(None) as report:
        with DistortionModel(path) as dist1:
            assert dist1.meta.instrument.p_pupil == dist.meta.instrument.p_pupil
            assert dist1.meta.instrument.pupil == dist.meta.instrument.pupil
            assert dist1.meta.exposure.p_exptype == dist.meta.exposure.p_exptype
            assert dist1.meta.exposure.type == dist.meta.exposure.type
            assert dist1.meta.psubarray == dist.meta.psubarray
            assert dist1.meta.subarray.name == dist.meta.subarray.name
        assert len(report) == 0
def create_wfc3_distortion(detector, outname, sci_pupil,
                             sci_subarr, sci_exptype, history_entry, filter):
    """
    Create an asdf reference file with all distortion components for the NIRCam imager.
    NOTE: The IDT has not provided any distortion information. The files are constructed
    using ISIM transformations provided/(computed?) by the TEL team which they use to
    create the SIAF file.
    These reference files should be replaced when/if the IDT provides us with distortion.
    Parameters
    ----------
    detector : str
        NRCB1, NRCB2, NRCB3, NRCB4, NRCB5, NRCA1, NRCA2, NRCA3, NRCA4, NRCA5
    aperture : str
        Name of the aperture/subarray. (e.g. FULL, SUB160, SUB320, SUB640, GRISM_F322W2)
    outname : str
        Name of output file.
    Examples
    --------
    """
    # Download WFC3 Image Distortion File
    from astropy.utils.data import download_file
    fn = download_file('https://hst-crds.stsci.edu/unchecked_get/references/hst/w3m18525i_idc.fits', cache=True)
    wfc3_distortion_file = fits.open(fn)
    wfc3_filter_info = wfc3_distortion_file[1].data[list(wfc3_distortion_file[1].data['FILTER']).index(filter)]
    
    
    degree = 4  # WFC3 Distortion is fourth degree
    
    # From Bryan Hilbert:
    #   The parity term is just an indicator of the relationship between the detector y axis and the “science” y axis.
    #   A parity of -1 means that the y axes of the two systems run in opposite directions... A value of 1 indicates no flip.
    # From Colin Cox:
    #   ... for WFC3 it is always -1 so maybe people gave up mentioning it.
    parity = -1
    
    #full_aperture = detector + '_' + aperture

    # Get Siaf instance for detector/aperture
    #inst_siaf = pysiaf.Siaf('nircam')
    #siaf = inst_siaf[full_aperture]

    # *****************************************************
    # "Forward' transformations. science --> ideal --> V2V3
    xcoeffs, ycoeffs = get_distortion_coeffs(degree, wfc3_filter_info)

    sci2idlx = Polynomial2D(degree, **xcoeffs)
    sci2idly = Polynomial2D(degree, **ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    idl2v2v3x, idl2v2v3y = v2v3_model('ideal', 'v2v3', parity, np.radians(wfc3_distortion_file[1].data[wfc3_distortion_file[1].data['FILTER'] == filter]['THETA'][0]))

    '''
    # *****************************************************
    # 'Reverse' transformations. V2V3 --> ideal --> science
    xcoeffs, ycoeffs = get_distortion_coeffs('Idl2Sci', siaf)

    idl2scix = Polynomial2D(degree, **xcoeffs)
    idl2sciy = Polynomial2D(degree, **ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    v2v32idlx, v2v32idly = v2v3_model('v2v3', 'ideal', parity, np.radians(wfc3_distortion_file['THETA']))
    '''

    # Now create a compound model for each with the appropriate inverse
    # Inverse polynomials were removed in favor of using GWCS' numerical inverse capabilities
    sci2idl = Mapping([0, 1, 0, 1]) | sci2idlx & sci2idly
    #sci2idl.inverse = Mapping([0, 1, 0, 1]) | idl2scix & idl2sciy

    idl2v2v3 = Mapping([0, 1, 0, 1]) | idl2v2v3x & idl2v2v3y
    #idl2v2v3.inverse = Mapping([0, 1, 0, 1]) | v2v32idlx & v2v32idly

    # Now string the models together to make a single transformation

    # We also need
    # to account for the difference of 1 between the SIAF
    # coordinate values (indexed to 1) and python (indexed to 0).
    # Nadia said that this shift should be present in the
    # distortion reference file.

    core_model = sci2idl# | idl2v2v3

    # Now add in the shifts to create the full model
    # including the shift to go from 0-indexed python coords to
    # 1-indexed

    # Find the distance between (0,0) and the reference location
    xshift = Shift(wfc3_filter_info['XREF'])
    yshift = Shift(wfc3_filter_info['YREF'])
    
    # Finally, we need to shift by the v2,v3 value of the reference
    # location in order to get to absolute v2,v3 coordinates
    v2shift = Shift(wfc3_filter_info['V2REF'])
    v3shift = Shift(wfc3_filter_info['V3REF'])
    
    # SIAF coords
    index_shift = Shift(1)
    model = index_shift & index_shift | xshift & yshift | core_model | v2shift & v3shift

    # Since the inverse of all model components are now defined,
    # the total model inverse is also defined automatically

    # Save using the DistortionModel datamodel
    d = DistortionModel(model=model, input_units=u.pix,
                        output_units=u.arcsec)

    #Populate metadata

    # Keyword values in science data to which this file should
    # be applied
    p_pupil = ''
    for p in sci_pupil:
        p_pupil = p_pupil + p + '|'

    p_subarr = ''
    for p in sci_subarr:
        p_subarr = p_subarr + p + '|'

    p_exptype = ''
    for p in sci_exptype:
        p_exptype = p_exptype + p + '|'

    d.meta.instrument.p_pupil = p_pupil
    d.meta.subarray.p_subarray = p_subarr
    d.meta.exposure.p_exptype = p_exptype

    # metadata describing the reference file itself
    d.meta.title = "WFC3 Distortion"
    d.meta.instrument.name = "WFC3"
    d.meta.instrument.module = detector[-2]
    
    numdet = detector[-1]
    d.meta.instrument.channel = "LONG" if numdet == '5' else "SHORT"
    # In the reference file headers, we need to switch NRCA5 to
    # NRCALONG, and same for module B.
    d.meta.instrument.detector = (detector[0:4] + 'LONG') if numdet == 5 else detector
    
    d.meta.telescope = 'HST'
    d.meta.subarray.name = 'FULL'
    d.meta.pedigree = 'GROUND'
    d.meta.reftype = 'DISTORTION'
    d.meta.author = 'D. Nguyen'
    d.meta.litref = "https://github.com/spacetelescope/jwreftools"
    d.meta.description = "Distortion model from SIAF coefficients in pysiaf version 0.6.1"
    #d.meta.exp_type = exp_type
    d.meta.useafter = "2014-10-01T00:00:00"

    # To be ready for the future where we will have filter-dependent solutions
    d.meta.instrument.filter = 'N/A'

    # Create initial HISTORY ENTRY
    sdict = {'name': 'nircam_distortion_reffiles_from_pysiaf.py',
             'author': 'B.Hilbert',
             'homepage': 'https://github.com/spacetelescope/jwreftools',
             'version': '0.8'}

    entry = util.create_history_entry(history_entry, software=sdict)
    d.history = [entry]

    #Create additional HISTORY entries
    #entry2 = util.create_history_entry(history_2)
    #d.history.append(entry2)

    d.save(outname)
    print("Output saved to {}".format(outname))
Пример #8
0
def make_distortion(distfile, outname):
    """
    Create an asdf reference file with all distortion components for the MIRI imager.
    The filter offsets are stored in a separate file.

    Note: The IDT supplied distortion file lists sky to pixel as the
    forward transform. Since "forward" in the JWST pipeline is from
    pixel to sky, the meaning of forward and inverse matrices and the order
    in which they are applied is switched.

    The order of operation from pixel to sky is:
    - Apply MI matrix
    - Apply Ai and BI matrices
    - Apply the TI matrix (this gives V2/V3 coordinates)

    Parameters
    ----------
    distfile : str
        MIRI imager DISTORTION file provided by the IDT team.
    outname : str
        Name of reference file to be wriiten to disk.

    Returns
    -------
    fasdf : AsdfFile
        AsdfFile object

    Examples
    --------
    >>> make_distortion("MIRI_FM_MIRIMAGE_DISTORTION_07.04.01.fits", 'test.asdf')
    """
    # Transform from 0-indexed Detector frame (used by pipeline) to 0-indexed Science frame (used by CDP)
    det_to_sci = models.Shift(-4) & models.Identity(1)

    fdist = fits.open(distfile)
    mi_matrix = fdist['MI matrix'].data
    mi_col = models.Polynomial1D(1,
                                 c0=mi_matrix[0, 2],
                                 c1=mi_matrix[0, 0],
                                 name="M_column_correction")
    mi_row = models.Polynomial1D(1,
                                 c0=mi_matrix[1, 2],
                                 c1=mi_matrix[1, 1],
                                 name="M_row_correction")
    m_matrix = fdist['M matrix'].data
    m_col = models.Polynomial1D(1, c0=m_matrix[0, 2], c1=m_matrix[0, 0])
    m_row = models.Polynomial1D(1, c0=m_matrix[1, 2], c1=m_matrix[1, 1])
    mi_col.inverse = m_col.copy()
    mi_row.inverse = m_row.copy()
    m_transform = mi_col & mi_row
    m_transform.inverse = m_col & m_row

    # This turns the output of the MI transform into the shape needed for the AI/BI transforms
    mapping = models.Mapping([0, 1, 0, 1])
    mapping.inverse = models.Identity(2)

    ai_matrix = fdist['AI matrix'].data
    a_matrix = fdist['A matrix'].data
    col_poly = polynomial_from_coeffs_matrix_swap(ai_matrix,
                                                  name="A_correction")
    col_poly.inverse = polynomial_from_coeffs_matrix(a_matrix)
    bi_matrix = fdist['BI matrix'].data
    b_matrix = fdist['B matrix'].data
    row_poly = polynomial_from_coeffs_matrix_swap(bi_matrix,
                                                  name="B_correction")
    row_poly.inverse = polynomial_from_coeffs_matrix(b_matrix)
    poly = row_poly & col_poly  # DRL: I had to switch the order here
    poly.inverse = col_poly.inverse & row_poly.inverse  # but not switch here

    ti_matrix = fdist['TI matrix'].data
    t_matrix = fdist['T matrix'].data
    ti_col = models.Polynomial2D(1, name='TI_column_correction')
    ti_col.parameters = ti_matrix[0][::-1]
    ti_row = models.Polynomial2D(1, name='TI_row_correction')
    ti_row.parameters = ti_matrix[1][::-1]

    t_col = models.Polynomial2D(1, name='T_column_correction')
    t_col.parameters = t_matrix[0][::-1]
    t_row = models.Polynomial2D(1, name='T_row_correction')
    t_row.parameters = t_matrix[1][::-1]
    t_transform = ti_row & ti_col
    t_transform.inverse = t_row & t_col

    # ident is created here so that mapping can be assigned as inverse
    ident = models.Identity(2)
    ident.inverse = models.Mapping([0, 1, 0, 1])

    # This turns the output of the AI/BI transforms into the shape needed for the TI transform
    poly2t_mapping = models.Mapping([0, 1, 0, 1])
    poly2t_mapping.inverse = models.Mapping([0, 1, 0, 1])

    map_t2_xanyan = models.Mapping((1, 0))
    map_t2_xanyan.inverse = models.Mapping((0, 1, 0, 1))

    distortion_transform = det_to_sci | m_transform | mapping | poly | poly2t_mapping | t_transform | ident | models.Mapping(
        [1, 0])

    # Inverse transform created automatically, but if we needed to do it by hand
    # it would look like this
    #distortion_transform.inverse=models.Mapping([1,0]).inverse | ident.inverse | t_transform.inverse | poly2t_mapping.inverse | poly.inverse | mapping.inverse | m_transform.inverse | det_to_sci.inverse

    fdist.close()

    dist = DistortionModel()
    # Add general metadata
    dist = create_reffile_header(dist)
    # Add file-specific metadata
    dist.model = distortion_transform
    dist.meta.input_units = u.pix
    dist.meta.output_units = u.arcsec
    dist.meta.title = "MIRI imager distortion - CDP7"
    dist.meta.description = "CDP7 delivery"

    dist.save(outname)
Пример #9
0
def create_nircam_distortion(detector,
                             aperture,
                             outname,
                             sci_pupil,
                             sci_subarr,
                             sci_exptype,
                             history_entry,
                             author=None,
                             descrip=None,
                             pedigree=None,
                             useafter=None,
                             dist_coeffs_file=None,
                             siaf_xml_file=None):
    """
    Create an asdf reference file with all distortion components for the NIRCam imager.

    NOTE: The IDT has not provided any distortion information. The files are constructed
    using ISIM transformations provided/(computed?) by the TEL team which they use to
    create the SIAF file.
    These reference files should be replaced when/if the IDT provides us with distortion.

    Parameters
    ----------
    detector : str
        NRCB1, NRCB2, NRCB3, NRCB4, NRCB5, NRCA1, NRCA2, NRCA3, NRCA4, NRCA5

    aperture : str
        Name of the aperture/subarray. (e.g. FULL, SUB160, SUB320, SUB640, GRISM_F322W2)

    outname : str
        Name of output file.
    siaf_xml_file : str
        Name of SIAF xml file to use in place of the default SIAF version from pysiaf.
        If None, the default version in pysiaf will be used.

    sci_pupil : list
        Pupil wheel values for which this distortion solution applies

    sci_subarr : list
        List of subarray/aperture names to which this distortion solution applies

    sci_exptype : list
        List of exposure types to which this distortion solution applies

    history_entry : str
        Text to be added as a HISTORY entry in the output reference file

    author : str
        Value to place in the output file's Author metadata entry

    descrip : str
        Text to place in the output file's DECRIP header keyword

    pedgree : str
        Value to place in the output file's PEDIGREE header keyword

    useafter : str
        Value to place in the output file's USEAFTER header keyword (e.g. "2014-10-01T00:00:01")
    dist_coeffs_file : str
        Name of ascii file (nominally output by jwst_fpa package) containing distortion
        coefficients. If this is provided, the coefficients in this file are used, rather
        than those in pysiaf.

    Examples
    --------

    """
    degree = 5  # distotion in pysiaf is a 5th order polynomial
    numdet = detector[-1]
    module = detector[-2]
    channel = 'SHORT'
    if numdet == '5':
        channel = 'LONG'

    full_aperture = detector + '_' + aperture

    # Get Siaf instance for detector/aperture
    if siaf_xml_file is None:
        print('Using default SIAF version in pysiaf.')
        inst_siaf = pysiaf.Siaf('nircam')
    else:
        print(f'SIAF to be loaded from {siaf_xml_file}...')
        inst_siaf = pysiaf.Siaf(filename=siaf_xml_file, instrument='nircam')

    siaf = inst_siaf[full_aperture]

    # Find the distance between (0,0) and the reference location
    xshift, yshift = get_refpix(inst_siaf, full_aperture)

    # *****************************************************
    # If the user provides files containing distortion coefficients
    # (as output by the jwst_fpa package), use those rather than
    # retrieving coefficients from siaf.
    if dist_coeffs_file is not None:
        coeff_tab = read_distortion_coeffs_file(dist_coeffs_file)
        xcoeffs = convert_distortion_coeffs_table(coeff_tab, 'Sci2IdlX')
        ycoeffs = convert_distortion_coeffs_table(coeff_tab, 'Sci2IdlY')
        inv_xcoeffs = convert_distortion_coeffs_table(coeff_tab, 'Idl2SciX')
        inv_ycoeffs = convert_distortion_coeffs_table(coeff_tab, 'Idl2SciY')
    elif dist_coeffs_file is None:
        xcoeffs, ycoeffs = get_distortion_coeffs('Sci2Idl', siaf)
        inv_xcoeffs, inv_ycoeffs = get_distortion_coeffs('Idl2Sci', siaf)

    # V3IdlYAngle and V2Ref, V3Ref should always be taken from the latest version
    # of SIAF, rather than the output of jwst_fpa. Separate FGS/NIRISS analyses must
    # be done in order to modify these values.
    v3_ideal_y_angle = siaf.V3IdlYAngle * np.pi / 180.

    # *****************************************************
    # "Forward' transformations. science --> ideal --> V2V3
    #label = 'Sci2Idl'
    ##from_units = 'distorted pixels'
    ##to_units = 'arcsec'

    #xcoeffs, ycoeffs = get_distortion_coeffs(label, siaf)

    sci2idlx = Polynomial2D(degree, **xcoeffs)
    sci2idly = Polynomial2D(degree, **ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    parity = siaf.VIdlParity
    #v3_ideal_y_angle = siaf.V3IdlYAngle * np.pi / 180.
    idl2v2v3x, idl2v2v3y = v2v3_model('ideal', 'v2v3', parity,
                                      v3_ideal_y_angle)

    # Finally, we need to shift by the v2,v3 value of the reference
    # location in order to get to absolute v2,v3 coordinates
    v2shift, v3shift = get_v2v3ref(siaf)

    # *****************************************************
    # 'Reverse' transformations. V2V3 --> ideal --> science
    #label = 'Idl2Sci'
    ##from_units = 'arcsec'
    ##to_units = 'distorted pixels'

    #xcoeffs, ycoeffs = get_distortion_coeffs(label, siaf)

    idl2scix = Polynomial2D(degree, **inv_xcoeffs)
    idl2sciy = Polynomial2D(degree, **inv_ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    #parity = siaf.VIdlParity
    #v3_ideal_y_angle = siaf.V3IdlYAngle * np.pi / 180.
    v2v32idlx, v2v32idly = v2v3_model('v2v3', 'ideal', parity,
                                      v3_ideal_y_angle)

    ##"Forward' transformations. science --> ideal --> V2V3
    #sci2idlx, sci2idly, sciunit, idlunit = read_siaf_table.get_siaf_transform(coefffile,full_aperture,'science','ideal', 5)
    #idl2v2v3x, idl2v2v3y = read_siaf_table.get_siaf_v2v3_transform(coefffile,full_aperture,from_system='ideal')

    ##'Reverse' transformations. V2V3 --> ideal --> science
    #v2v32idlx, v2v32idly = read_siaf_table.get_siaf_v2v3_transform(coefffile,full_aperture,to_system='ideal')
    #idl2scix, idl2sciy, idlunit, sciunit = read_siaf_table.get_siaf_transform(coefffile,full_aperture,'ideal','science', 5)

    # Now create a compound model for each with the appropriate inverse
    sci2idl = Mapping([0, 1, 0, 1]) | sci2idlx & sci2idly
    sci2idl.inverse = Mapping([0, 1, 0, 1]) | idl2scix & idl2sciy

    idl2v2v3 = Mapping([0, 1, 0, 1]) | idl2v2v3x & idl2v2v3y
    idl2v2v3.inverse = Mapping([0, 1, 0, 1]) | v2v32idlx & v2v32idly

    # Now string the models together to make a single transformation

    # We also need
    # to account for the difference of 1 between the SIAF
    # coordinate values (indexed to 1) and python (indexed to 0).
    # Nadia said that this shift should be present in the
    # distortion reference file.

    core_model = sci2idl | idl2v2v3

    # Now add in the shifts to create the full model
    # including the shift to go from 0-indexed python coords to
    # 1-indexed

    # SIAF coords
    index_shift = Shift(1)
    model = index_shift & index_shift | xshift & yshift | core_model | v2shift & v3shift

    # Since the inverse of all model components are now defined,
    # the total model inverse is also defined automatically

    # In the reference file headers, we need to switch NRCA5 to
    # NRCALONG, and same for module B.
    if detector[-1] == '5':
        detector = detector[0:4] + 'LONG'

    # Save using the DistortionModel datamodel
    d = DistortionModel(model=model, input_units=u.pix, output_units=u.arcsec)

    #Populate metadata

    # Keyword values in science data to which this file should
    # be applied
    p_pupil = ''
    for p in sci_pupil:
        p_pupil = p_pupil + p + '|'

    p_subarr = ''
    for p in sci_subarr:
        p_subarr = p_subarr + p + '|'

    p_exptype = ''
    for p in sci_exptype:
        p_exptype = p_exptype + p + '|'

    d.meta.instrument.p_pupil = p_pupil
    d.meta.subarray.p_subarray = p_subarr
    d.meta.exposure.p_exptype = p_exptype

    #d.meta.instrument.p_pupil = "CLEAR|F162M|F164N|F323N|F405N|F470N|"
    #d.meta.p_subarray = "FULL|SUB64P|SUB160|SUB160P|SUB320|SUB400P|SUB640|SUB32TATS|SUB32TATSGRISM|SUB8FP1A|SUB8FP1B|SUB96DHSPILA|SUB96DHSPILB|SUB64FP1A|SUB64FP1B|"
    #d.meta.exposure.p_exptype = "NRC_IMAGE|NRC_TSIMAGE|NRC_FLAT|NRC_LED|NRC_WFSC|"

    # metadata describing the reference file itself
    d.meta.title = "NIRCam Distortion"
    d.meta.instrument.name = "NIRCAM"
    d.meta.instrument.module = module
    d.meta.instrument.channel = channel
    d.meta.instrument.detector = detector
    d.meta.telescope = 'JWST'
    d.meta.subarray.name = 'FULL'

    if pedigree is None:
        d.meta.pedigree = 'GROUND'
    else:
        if pedigree.upper() not in ['DUMMY', 'GROUND', 'FLIGHT']:
            raise ValueError("Bad PEDIGREE value.")
        d.meta.pedigree = pedigree.upper()

    d.meta.reftype = 'DISTORTION'

    if author is None:
        author = "B. Hilbert"
    d.meta.author = author

    d.meta.litref = "https://github.com/spacetelescope/nircam_calib/nircam_calib/reffile_creation/pipeline/distortion/nircam_distortion_reffiles_from_pysiaf.py"

    if descrip is None:
        d.meta.description = "TEST OF UPDATED CODE"
    else:
        d.meta.description = descrip

    #d.meta.exp_type = exp_type
    if useafter is None:
        d.meta.useafter = "2014-10-01T00:00:01"
    else:
        d.meta.useafter = useafter

    # To be ready for the future where we will have filter-dependent solutions
    d.meta.instrument.filter = 'N/A'

    # Create initial HISTORY ENTRY
    sdict = {
        'name': 'nircam_distortion_reffiles_from_pysiaf.py',
        'author': author,
        'homepage': 'https://github.com/spacetelescope/nircam_calib',
        'version': '0.0'
    }

    entry = util.create_history_entry(history_entry, software=sdict)
    d.history = [entry]

    #Create additional HISTORY entries
    #entry2 = util.create_history_entry(history_2)
    #d.history.append(entry2)

    d.save(outname)
    print("Output saved to {}".format(outname))
def create_nircam_distortion(detector, aperture, outname, sci_pupil,
                             sci_subarr, sci_exptype, history_entry):
    """
    Create an asdf reference file with all distortion components for the NIRCam imager.

    NOTE: The IDT has not provided any distortion information. The files are constructed
    using ISIM transformations provided/(computed?) by the TEL team which they use to
    create the SIAF file.
    These reference files should be replaced when/if the IDT provides us with distortion.

    Parameters
    ----------
    detector : str
        NRCB1, NRCB2, NRCB3, NRCB4, NRCB5, NRCA1, NRCA2, NRCA3, NRCA4, NRCA5
    aperture : str
        Name of the aperture/subarray. (e.g. FULL, SUB160, SUB320, SUB640, GRISM_F322W2)
    outname : str
        Name of output file.

    Examples
    --------

    """
    degree = 5  # distotion in pysiaf is a 5th order polynomial
    numdet = detector[-1]
    module = detector[-2]
    channel = 'SHORT'
    if numdet == '5':
        channel = 'LONG'

    full_aperture = detector + '_' + aperture

    # Get Siaf instance for detector/aperture
    inst_siaf = pysiaf.Siaf('nircam')
    siaf = inst_siaf[full_aperture]

    # Find the distance between (0,0) and the reference location
    xshift, yshift = get_refpix(inst_siaf, full_aperture)

    # *****************************************************
    # "Forward' transformations. science --> ideal --> V2V3
    label = 'Sci2Idl'
    #from_units = 'distorted pixels'
    #to_units = 'arcsec'

    xcoeffs, ycoeffs = get_distortion_coeffs(label, siaf)

    sci2idlx = Polynomial2D(degree, **xcoeffs)
    sci2idly = Polynomial2D(degree, **ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    parity = siaf.VIdlParity
    v3_ideal_y_angle = siaf.V3IdlYAngle * np.pi / 180.
    idl2v2v3x, idl2v2v3y = v2v3_model('ideal', 'v2v3', parity, v3_ideal_y_angle)

    # Finally, we need to shift by the v2,v3 value of the reference
    # location in order to get to absolute v2,v3 coordinates
    v2shift, v3shift = get_v2v3ref(siaf)

    # *****************************************************
    # 'Reverse' transformations. V2V3 --> ideal --> science
    label = 'Idl2Sci'
    #from_units = 'arcsec'
    #to_units = 'distorted pixels'

    xcoeffs, ycoeffs = get_distortion_coeffs(label, siaf)

    idl2scix = Polynomial2D(degree, **xcoeffs)
    idl2sciy = Polynomial2D(degree, **ycoeffs)

    # Get info for ideal -> v2v3 or v2v3 -> ideal model
    parity = siaf.VIdlParity
    v3_ideal_y_angle = siaf.V3IdlYAngle * np.pi / 180.
    v2v32idlx, v2v32idly = v2v3_model('v2v3', 'ideal', parity, v3_ideal_y_angle)

    ##"Forward' transformations. science --> ideal --> V2V3
    #sci2idlx, sci2idly, sciunit, idlunit = read_siaf_table.get_siaf_transform(coefffile,full_aperture,'science','ideal', 5)
    #idl2v2v3x, idl2v2v3y = read_siaf_table.get_siaf_v2v3_transform(coefffile,full_aperture,from_system='ideal')

    ##'Reverse' transformations. V2V3 --> ideal --> science
    #v2v32idlx, v2v32idly = read_siaf_table.get_siaf_v2v3_transform(coefffile,full_aperture,to_system='ideal')
    #idl2scix, idl2sciy, idlunit, sciunit = read_siaf_table.get_siaf_transform(coefffile,full_aperture,'ideal','science', 5)

    # Now create a compound model for each with the appropriate inverse
    sci2idl = Mapping([0, 1, 0, 1]) | sci2idlx & sci2idly
    sci2idl.inverse = Mapping([0, 1, 0, 1]) | idl2scix & idl2sciy

    idl2v2v3 = Mapping([0, 1, 0, 1]) | idl2v2v3x & idl2v2v3y
    idl2v2v3.inverse = Mapping([0, 1, 0, 1]) | v2v32idlx & v2v32idly

    # Now string the models together to make a single transformation

    # We also need
    # to account for the difference of 1 between the SIAF
    # coordinate values (indexed to 1) and python (indexed to 0).
    # Nadia said that this shift should be present in the
    # distortion reference file.

    core_model = sci2idl | idl2v2v3

    # Now add in the shifts to create the full model
    # including the shift to go from 0-indexed python coords to
    # 1-indexed

    # SIAF coords
    index_shift = Shift(1)
    model = index_shift & index_shift | xshift & yshift | core_model | v2shift & v3shift

    # Since the inverse of all model components are now defined,
    # the total model inverse is also defined automatically

    # In the reference file headers, we need to switch NRCA5 to
    # NRCALONG, and same for module B.
    if detector[-1] == '5':
        detector = detector[0:4] + 'LONG'

    # Save using the DistortionModel datamodel
    d = DistortionModel(model=model, input_units=u.pix,
                        output_units=u.arcsec)

    #Populate metadata

    # Keyword values in science data to which this file should
    # be applied
    p_pupil = ''
    for p in sci_pupil:
        p_pupil = p_pupil + p + '|'

    p_subarr = ''
    for p in sci_subarr:
        p_subarr = p_subarr + p + '|'

    p_exptype = ''
    for p in sci_exptype:
        p_exptype = p_exptype + p + '|'

    d.meta.instrument.p_pupil = p_pupil
    d.meta.subarray.p_subarray = p_subarr
    d.meta.exposure.p_exptype = p_exptype

    #d.meta.instrument.p_pupil = "CLEAR|F162M|F164N|F323N|F405N|F470N|"
    #d.meta.p_subarray = "FULL|SUB64P|SUB160|SUB160P|SUB320|SUB400P|SUB640|SUB32TATS|SUB32TATSGRISM|SUB8FP1A|SUB8FP1B|SUB96DHSPILA|SUB96DHSPILB|SUB64FP1A|SUB64FP1B|"
    #d.meta.exposure.p_exptype = "NRC_IMAGE|NRC_TSIMAGE|NRC_FLAT|NRC_LED|NRC_WFSC|"

    # metadata describing the reference file itself
    d.meta.title = "NIRCam Distortion"
    d.meta.instrument.name = "NIRCAM"
    d.meta.instrument.module = module
    d.meta.instrument.channel = channel
    d.meta.instrument.detector = detector
    d.meta.telescope = 'JWST'
    d.meta.subarray.name = 'FULL'
    d.meta.pedigree = 'GROUND'
    d.meta.reftype = 'DISTORTION'
    d.meta.author = 'B. Hilbert'
    d.meta.litref = "https://github.com/spacetelescope/jwreftools"
    d.meta.description = "Distortion model from SIAF coefficients in pysiaf version 0.6.1"
    #d.meta.exp_type = exp_type
    d.meta.useafter = "2014-10-01T00:00:00"

    # To be ready for the future where we will have filter-dependent solutions
    d.meta.instrument.filter = 'N/A'

    # Create initial HISTORY ENTRY
    sdict = {'name': 'nircam_distortion_reffiles_from_pysiaf.py',
             'author': 'B.Hilbert',
             'homepage': 'https://github.com/spacetelescope/jwreftools',
             'version': '0.8'}

    entry = util.create_history_entry(history_entry, software=sdict)
    d.history = [entry]

    #Create additional HISTORY entries
    #entry2 = util.create_history_entry(history_2)
    #d.history.append(entry2)

    d.save(outname)
    print("Output saved to {}".format(outname))