示例#1
0
    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
示例#2
0
    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)
示例#3
0
    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
示例#4
0
    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
示例#5
0
    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)