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()
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()
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
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
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'
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))
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)
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))