def parse_dither_pattern(self, file_list, ext=None): """ Parse headers from a file list to determine the dither pattern. Parameters ---------- file_list (list of strings): List of files for which dither pattern is desired ext (int, optional): Extension containing the relevant header for these files. Default=None. If None, code uses self.primary_hdrext Returns ------- dither_pattern, dither_id, offset_arcsec dither_pattern (str `numpy.ndarray`_): Array of dither pattern names dither_id (str `numpy.ndarray`_): Array of dither pattern IDs offset_arc (float `numpy.ndarray`_): Array of dither pattern offsets """ nfiles = len(file_list) offset_arcsec = np.zeros(nfiles) dither_pattern = None dither_id = None for ifile, file in enumerate(file_list): hdr = fits.getheader(file, self.primary_hdrext if ext is None else ext) try: ra, dec = meta.convert_radec(self.get_meta_value(hdr, 'ra', no_fussing=True), self.get_meta_value(hdr, 'dec', no_fussing=True)) except: msgs.warn('Encounter invalid value of your coordinates. Give zeros for both RA and DEC. Check that this does not cause problems with the offsets') ra, dec = 0.0, 0.0 if ifile == 0: coord_ref = SkyCoord(ra*units.deg, dec*units.deg) offset_arcsec[ifile] = 0.0 # ESOs position angle appears to be the negative of the canonical astronomical convention posang_ref = -(hdr['HIERARCH ESO INS SLIT POSANG']*units.deg) posang_ref_rad = posang_ref.to('radian').value # Unit vector pointing in direction of slit PA u_hat_slit = np.array([np.sin(posang_ref), np.cos(posang_ref)]) # [u_hat_ra, u_hat_dec] else: coord_this = SkyCoord(ra*units.deg, dec*units.deg) posang_this = coord_ref.position_angle(coord_this).to('deg') separation = coord_ref.separation(coord_this).to('arcsec').value ra_off, dec_off = coord_ref.spherical_offsets_to(coord_this) u_hat_this = np.array([ra_off.to('arcsec').value/separation, dec_off.to('arcsec').value/separation]) dot_product = np.dot(u_hat_slit, u_hat_this) if not np.isclose(np.abs(dot_product),1.0, atol=1e-2): msgs.error('The slit appears misaligned with the angle between the coordinates: dot_product={:7.5f}'.format(dot_product) + msgs.newline() + 'The position angle in the headers {:5.3f} differs from that computed from the coordinates {:5.3f}'.format(posang_this, posang_ref)) offset_arcsec[ifile] = separation*np.sign(dot_product) # dither_id.append(hdr['FRAMEID']) # offset_arcsec[ifile] = hdr['YOFFSET'] return dither_pattern, dither_id, offset_arcsec
def __init__(self, spec1dfile, sensfile, par=None, debug=False): """ See docs above """ # Arguments self.spec1dfile = spec1dfile self.sensfile = sensfile # Set spectrograph header = fits.getheader(self.spec1dfile) self.spectrograph = load_spectrograph(header['PYP_SPEC']) self.par = self.spectrograph.default_pypeit_par( )['sensfunc'] if par is None else par self.debug = debug # Core attributes that will be output to file self.meta_table = None self.out_table = None self.wave = None self.sensfunc = None self.steps = [] # Are we splicing together multiple detectors? self.splice_multi_det = True if self.par[ 'multi_spec_det'] is not None else False # Read in the Standard star data sobjs_std = (specobjs.SpecObjs.from_fitsfile(self.spec1dfile)).get_std( multi_spec_det=self.par['multi_spec_det']) # Unpack standard self.wave, self.counts, self.counts_ivar, self.counts_mask, self.meta_spec, header = sobjs_std.unpack_object( ret_flam=False) self.norderdet = 1 if self.wave.ndim == 1 else self.wave.shape[1] # If the user provided RA and DEC use those instead of what is in meta star_ra = self.meta_spec[ 'RA'] if self.par['star_ra'] is None else self.par['star_ra'] star_dec = self.meta_spec[ 'DEC'] if self.par['star_dec'] is None else self.par['star_dec'] star_ra, star_dec = meta.convert_radec( star_ra, star_dec) # Convert to decimal deg, as need be # Read in standard star dictionary self.std_dict = flux_calib.get_standard_spectrum( star_type=self.par['star_type'], star_mag=self.par['star_mag'], ra=star_ra, dec=star_dec)
def get_meta_value(self, inp, meta_key, required=False, ignore_bad_header=False, usr_row=None, no_fussing=False): """ Return meta data from a given file (or its array of headers) Args: inp (str or list): Input filename or headarr list meta_key (str or list of str): headarr (list, optional) List of headers required (bool, optional): Require the meta key to be returnable ignore_bad_header: bool, optional Over-ride required; not recommended usr_row: Row Provides user supplied frametype (and other things not used) no_fussing (bool, optional): No type checking or anything. Just pass back the first value retrieved Mainly for bound pairs of meta, e.g. ra/dec Returns: value: value or list of values """ if isinstance(inp, str): headarr = self.get_headarr(inp) else: headarr = inp # Loop? if isinstance(meta_key, list): values = [] for mdict in meta_key: values.append( self.get_meta_value(headarr, mdict, required=required)) # return values # Are we prepared to provide this meta data? if meta_key not in self.meta.keys(): if required: msgs.error( "Need to allow for meta_key={} in your meta data".format( meta_key)) else: msgs.warn("Requested meta data does not exist...") return None # Is this not derivable? If so, use the default # or search for it as a compound method value = None if self.meta[meta_key]['card'] is None: if 'default' in self.meta[meta_key].keys(): value = self.meta[meta_key]['default'] elif 'compound' in self.meta[meta_key].keys(): value = self.compound_meta(headarr, meta_key) else: msgs.error( "Failed to load spectrograph value for meta: {}".format( meta_key)) else: # Grab from the header, if we can try: value = headarr[self.meta[meta_key]['ext']][self.meta[meta_key] ['card']] except (KeyError, TypeError): value = None # Return now? if no_fussing: return value # Deal with 'special' cases if meta_key in ['ra', 'dec'] and value is not None: ra, dec = meta.convert_radec( self.get_meta_value(headarr, 'ra', no_fussing=True), self.get_meta_value(headarr, 'dec', no_fussing=True)) value = ra if meta_key == 'ra' else dec # JFH Added this bit of code to deal with situations where the header card is there but the wrong type, e.g. # MJD-OBS = 'null' try: if self.meta_data_model[meta_key]['dtype'] == str: retvalue = str(value).strip() elif self.meta_data_model[meta_key]['dtype'] == int: retvalue = int(value) elif self.meta_data_model[meta_key]['dtype'] == float: retvalue = float(value) elif self.meta_data_model[meta_key]['dtype'] == tuple: assert isinstance(value, tuple) retvalue = value castable = True except: retvalue = None castable = False # JFH Added the typing to prevent a crash below when the header value exists, but is the wrong type. This # causes a crash below when the value is cast. if value is None or not castable: # Was this required? if required: kerror = True if not ignore_bad_header: # Is this meta required for this frame type (Spectrograph specific) if ('required_ftypes' in self.meta[meta_key]) and (usr_row is not None): kerror = False # Is it required? for ftype in usr_row['frametype'].split(','): if ftype in self.meta[meta_key]['required_ftypes']: kerror = True # Bomb out? if kerror: embed(header='723 of spectrograph') msgs.error( 'Required meta "{:s}" did not load! You may have a corrupt header' .format(meta_key)) else: msgs.warn( "Required card {:s} missing from your header. Proceeding with risk.." .format(self.meta[meta_key]['card'])) return None # Return return retvalue
def get_meta_value(self, inp, meta_key, required=False, ignore_bad_header=False, usr_row=None, no_fussing=False): """ Return meta data from a given file (or its array of headers). Args: inp (:obj:`str`, :obj:`list`): Input filename or list of `astropy.io.fits.Header`_ objects. meta_key (:obj:`str`, :obj:`list`): A (list of) strings with the keywords to read from the file header(s). required (:obj:`bool`, optional): The metadata is required and must be available. If it is not, the method will raise an exception. ignore_bad_header (:obj:`bool`, optional): ``PypeIt`` expects certain metadata values to have specific datatypes. If the keyword finds the appropriate data but it cannot be cast to the correct datatype, this parameter determines whether or not the method raises an exception. If True, the incorrect type is ignored. It is recommended that this be False unless you know for sure that ``PypeIt`` can proceed appropriately. usr_row (`astropy.table.Table`_, optional): A single row table with the user-supplied frametype. This is used to determine if the metadata value is required for each frametype. Must contain a columns called `frametype`; everything else is ignored. no_fussing (:obj:`bool`, optional): No type checking or anything. Just pass back the first value retrieved. This is mainly for bound pairs of meta, e.g. ra/dec. Returns: object: Value recovered for (each) keyword. """ headarr = self.get_headarr(inp) if isinstance(inp, str) else inp # Loop? if isinstance(meta_key, list): return [ self.get_meta_value(headarr, key, required=required) for key in meta_key ] # Are we prepared to provide this meta data? if meta_key not in self.meta.keys(): if required: msgs.error( "Need to allow for meta_key={} in your meta data".format( meta_key)) else: msgs.warn( "Requested meta data for meta_key={} does not exist...". format(meta_key)) return None # Check if this meta key is required if 'required' in self.meta[meta_key].keys(): required = self.meta[meta_key]['required'] # Is this not derivable? If so, use the default # or search for it as a compound method value = None if self.meta[meta_key]['card'] is None: if 'default' in self.meta[meta_key].keys(): value = self.meta[meta_key]['default'] elif 'compound' in self.meta[meta_key].keys(): value = self.compound_meta(headarr, meta_key) else: msgs.error( "Failed to load spectrograph value for meta: {}".format( meta_key)) else: # Grab from the header, if we can try: value = headarr[self.meta[meta_key]['ext']][self.meta[meta_key] ['card']] except (KeyError, TypeError): value = None # Return now? if no_fussing: return value # Deal with 'special' cases if meta_key in ['ra', 'dec'] and value is not None: # TODO: Can we get rid of the try/except here and instead get to the heart of the issue? try: ra, dec = meta.convert_radec( self.get_meta_value(headarr, 'ra', no_fussing=True), self.get_meta_value(headarr, 'dec', no_fussing=True)) except: msgs.warn( 'Encounter invalid value of your coordinates. Give zeros for both RA and DEC' ) ra, dec = 0.0, 0.0 value = ra if meta_key == 'ra' else dec # JFH Added this bit of code to deal with situations where the # header card is there but the wrong type, e.g. MJD-OBS = # 'null' try: if self.meta_data_model[meta_key]['dtype'] == str: retvalue = str(value).strip() elif self.meta_data_model[meta_key]['dtype'] == int: retvalue = int(value) elif self.meta_data_model[meta_key]['dtype'] == float: retvalue = float(value) elif self.meta_data_model[meta_key]['dtype'] == tuple: if not isinstance(value, tuple): msgs.error( 'dtype for {0} is tuple, but value '.format(meta_key) + 'provided is {0}. Casting is not possible.'.format( type(value))) retvalue = value castable = True except: retvalue = None castable = False # JFH Added the typing to prevent a crash below when the header # value exists, but is the wrong type. This causes a crash # below when the value is cast. if value is None or not castable: # Was this required? if required: kerror = True if not ignore_bad_header: # Is this meta required for this frame type (Spectrograph specific) if ('required_ftypes' in self.meta[meta_key]) and (usr_row is not None): kerror = False # Is it required? # TODO: Use numpy.isin ? for ftype in usr_row['frametype'].split(','): if ftype in self.meta[meta_key]['required_ftypes']: kerror = True # Bomb out? if kerror: # TODO: Do we want this embed here? embed(header=utils.embed_header()) msgs.error('Required meta "{0}" did not load!'.format( meta_key) + 'You may have a corrupt header.') else: msgs.warn('Required card {0} missing '.format( self.meta[meta_key]['card']) + 'from your header. Proceeding with risk...') return None # Return return retvalue
def __init__(self, spec1dfile, sensfile, par=None, debug=False): # Instantiate as an empty DataContainer super().__init__() # Input and Output files self.spec1df = spec1dfile self.sensfile = sensfile # Spectrograph header = fits.getheader(self.spec1df) self.PYP_SPEC = header['PYP_SPEC'] self.spectrograph = load_spectrograph(self.PYP_SPEC) self.pypeline = self.spectrograph.pypeline # TODO: This line is necessary until we figure out a way to instantiate # spectrograph objects with configuration specific information from # spec1d files. self.spectrograph.dispname = header['DISPNAME'] # Get the algorithm parameters self.par = self.spectrograph.default_pypeit_par( )['sensfunc'] if par is None else par # TODO: Check the type of the parameter object? # Set the algorithm in the datamodel self.algorithm = self.__class__._algorithm # QA and throughput plot filenames self.qafile = sensfile.replace('.fits', '') + '_QA.pdf' self.thrufile = sensfile.replace('.fits', '') + '_throughput.pdf' # Other self.debug = debug self.steps = [] # Are we splicing together multiple detectors? self.splice_multi_det = True if self.par[ 'multi_spec_det'] is not None else False # Read in the Standard star data sobjs_std = specobjs.SpecObjs.from_fitsfile( self.spec1df).get_std(multi_spec_det=self.par['multi_spec_det']) if sobjs_std is None: msgs.error( 'There is a problem with your standard star spec1d file: {:s}'. format(self.spec1df)) # Unpack standard wave, counts, counts_ivar, counts_mask, self.meta_spec, header = sobjs_std.unpack_object( ret_flam=False) # Perform any instrument tweaks wave_twk, counts_twk, counts_ivar_twk, counts_mask_twk \ = self.spectrograph.tweak_standard(wave, counts, counts_ivar, counts_mask, self.meta_spec) # Reshape to 2d arrays self.wave_cnts, self.counts, self.counts_ivar, self.counts_mask, self.nspec_in, \ self.norderdet \ = utils.spec_atleast_2d(wave_twk, counts_twk, counts_ivar_twk, counts_mask_twk) # If the user provided RA and DEC use those instead of what is in meta star_ra = self.meta_spec[ 'RA'] if self.par['star_ra'] is None else self.par['star_ra'] star_dec = self.meta_spec[ 'DEC'] if self.par['star_dec'] is None else self.par['star_dec'] # Convert to decimal deg, as needed star_ra, star_dec = meta.convert_radec(star_ra, star_dec) # Read in standard star dictionary self.std_dict = flux_calib.get_standard_spectrum( star_type=self.par['star_type'], star_mag=self.par['star_mag'], ra=star_ra, dec=star_dec)