def thermal_spectrum(self, thermtable=None):
        """Calculate thermal spectrum using
        :func:`ThermalObservationMode.to_spectrum`.

        Parameters
        ----------
        thermtable : str or `None`
            Thermal component table filename.
            If `None`, uses ``stsynphot.config.conf.thermtable``.

        Returns
        -------
        sp : `synphot.spectrum.SourceSpectrum`
            Thermal spectrum in PHOTLAM.

        Raises
        ------
        synphot.exceptions.SynphotError
            Calculation failed.

        """
        thom = ThermalObservationMode(
            self._obsmode, graphtable=self.gtname, comptable=self.ctname,
            thermtable=thermtable)

        try:
            sp = thom.to_spectrum()
        except IndexError as e:  # pragma: no cover
            raise synexceptions.SynphotError(f'Broken graph table: {repr(e)}')

        return sp
    def _get_wave_intersection(self):
        """Find wavelengths in Angstrom where
        ``stsynphot.config.conf.waveset_array`` intersects with
        ``stsynphot.spectrum.Vega``.

        .. note:: No one knows why Vega is the chosen one.

        """
        def_wave = conf.waveset_array  # Angstrom
        minw = min(def_wave)
        maxw = max(def_wave)

        # Refine min and max wavelengths using thermal components
        for component in self.components[1:]:
            emissivity = component.emissivity
            if emissivity is not None:
                w = emissivity.waveset.value  # Angstrom
                minw = max(minw, w.min())
                maxw = min(maxw, w.max())

        w = self._merge_em_wave()
        result = w[(w > minw) & (w < maxw)]

        # Intersect with Vega
        if Vega is None:
            raise synexceptions.SynphotError('Missing Vega spectrum.')
        w = Vega.waveset.value  # Angstrom

        return result[(result > w.min()) & (result < w.max())]
Beispiel #3
0
    def get_filenames(self, compnames):
        """Get filenames of given component names.

        For multiple matches, only the first match is kept.

        Parameters
        ----------
        compnames : list of str
            List of component names to search. Case-sensitive.

        Returns
        -------
        files : list of str
            List of matched filenames.

        Raises
        ------
        synphot.exceptions.SynphotError
            Unmatched component name.

        """
        files = []

        for compname in compnames:
            if compname not in (None, '', conf.clear_filter):
                index = np.where(self.compnames == compname)[0]
                if len(index) < 1:
                    raise synexceptions.SynphotError(
                        'Cannot find {0} in {1}.'.format(compname, self.name))
                files.append(self.filenames[index[0]].lstrip())
            else:
                files.append(conf.clear_filter)

        return files
Beispiel #4
0
    def thermback(self, area=None, thermtable=None):
        """Calculate thermal background count rate for
        ``self.obsmode``.

        Calculation uses
        :func:`~stsynphot.observationmode.ObservationMode.thermal_spectrum`
        to extract thermal component source spectrum in
        PHOTLAM per square arcsec. Then this spectrum is
        integrated and multiplied by detector pixel scale
        and telescope collecting area to produce a count rate
        in count/s/pix. This unit is non-standard but used widely
        by STScI Exposure Time Calculator.

        .. note::

            Similar to IRAF SYNPHOT THERMBACK.

        Parameters
        ----------
        area : float, `~astropy.units.quantity.Quantity`, or `None`
            Area that flux covers.
            If not a Quantity, assumed to be in :math:`cm^{2}`.
            If `None`, use `area`.

        thermtable : str or `None`
            Thermal component table filename.
            If `None`, uses ``stsynphot.config.conf.thermtable``.

        Returns
        -------
        bg : `~astropy.units.quantity.Quantity`
            Thermal background count rate.

        Raises
        ------
        synphot.exceptions.SynphotError
            Calculation failed.

        """
        if self.obsmode.pixscale is None:
            raise synexceptions.SynphotError(
                'Undefined pixel scale for {0}.'.format(self.obsmode))

        if area is None:
            area = self.area
        area = units.validate_quantity(area, units.AREA)

        sp = self.obsmode.thermal_spectrum(thermtable=thermtable)
        bg = sp.integrate() * self.obsmode.pixscale ** 2 * area

        return bg.value * (u.count / u.s / u.pix)
Beispiel #5
0
    def __init__(self, modelclass, obsmode=None, **kwargs):
        if obsmode is None:
            raise synexceptions.SynphotError('Missing OBSMODE.')

        super(ObservationSpectralElement, self).__init__(modelclass, **kwargs)
        self._obsmode = obsmode
        self.meta['expr'] = str(obsmode)

        # Check for zero bounds, if applicable
        try:
            self.bounded_by_zero()
        except synexceptions.SynphotError:  # pragma: no cover
            warnings.warn(
                'Zero-bound check not done due to undefined waveset.',
                AstropyUserWarning)
Beispiel #6
0
def _read_table(filename, ext, dtypes):
    """Generic table reader.

    Parameters
    ----------
    filename : str
        Table filename.
        If suffix is not 'fits' or 'fit', assume ASCII format.

    ext : int
        Data extension.
        This is ignored for ASCII file.

    dtypes : dict
        Dictionary that maps column names to data types.

    Returns
    -------
    data : `~astropy.io.fits.FITS_rec` or `~astropy.table.Table`
        Data table.

    Raises
    ------
    synphot.exceptions.SynphotError
        Failure to parse table.

    """
    # FITS
    if filename.endswith('.fits') or filename.endswith('.fit'):
        with fits.open(filename) as f:
            data = f[ext].data.copy()

        err_str = ''
        for key, val in dtypes.items():
            if not np.issubdtype(data[key].dtype, val):
                err_str += 'Expect {0} to be {1} but get {2}.\n'.format(
                    key, val, data[key].dtype)
        if err_str:
            raise synexceptions.SynphotError(err_str)

    # ASCII
    else:  # pragma: no cover
        converters = dict([[k, ascii.convert_numpy(v)]
                           for k, v in dtypes.items()])
        data = ascii.read(filename, converters=converters)

    return data
Beispiel #7
0
    def from_obsmode(cls,
                     obsmode,
                     graphtable=None,
                     comptable=None,
                     component_dict={}):
        """Create a bandpass from observation mode string.

        Parameters
        ----------
        obsmode : str
            Observation mode.

        graphtable : str or `None`
            Graph table filename.
            If `None`, uses ``stsynphot.config.conf.graphtable``.

        comptable : str or `None`
            Optical component table filename.
            If `None`, uses ``stsynphot.config.conf.comptable``.

        component_dict : dict
            Maps component filename to corresponding
            `~stsynphot.observationmode.Component`.

        Returns
        -------
        bp : `ObservationSpectralElement`
            Empirical bandpass.

        Raises
        ------
        synphot.exceptions.SynphotError
            Observation mode yields no throughput.

        """
        from .observationmode import ObservationMode  # Avoid circular import

        ob = ObservationMode(obsmode,
                             graphtable=graphtable,
                             comptable=comptable,
                             component_dict=component_dict)

        if not isinstance(ob.throughput, SpectralElement):  # pragma: no cover
            raise synexceptions.SynphotError(
                '{0} has no throughput.'.format(obsmode))

        return cls(ob.throughput, obsmode=ob)
def get_catalog_index(gridname):
    """Extract catalog index (grid parameters).

    It is read once and then cached until the cache is cleared explicitly using
    :func:`reset_cache`.

    Parameters
    ----------
    gridname : str
        See :func:`grid_to_spec`.

    Returns
    -------
    cat_index : list
        List of ``[t_eff, metallicity, log_g, filename]``.

    catdir : str
        Directory containing the requested catalog.

    """
    if gridname == 'ck04models':
        catdir = 'crgridck04$'
    elif gridname == 'k93models':
        catdir = 'crgridk93$'
    elif gridname == 'phoenix':
        catdir = 'crgridphoenix$'
    else:
        raise synexceptions.SynphotError(
            f'{gridname} is not a supported catalog grid.')

    catdir = stio.irafconvert(catdir)
    filename = stio.resolve_filename(catdir, 'catalog.fits')

    # If not cached, read from grid catalog and cache it
    if filename not in _CACHE:
        data = stio.read_catalog(filename)  # EXT 1
        _CACHE[filename] = [list(map(float, index.split(','))) +
                            [data['FILENAME'][i]]
                            for i, index in enumerate(data['INDEX'])]

    return _CACHE[filename], catdir
Beispiel #9
0
def read_graphtable(filename, tab_ext=1):
    """Read graph table file.

    Table must contain the following named columns:

    #. ``COMPNAME`` - Component name, usually filter name (str)
    #. ``KEYWORD`` - Usually instrument name (str)
    #. ``INNODE`` - Input node number (int)
    #. ``OUTNODE``- Output node number (int)
    #. ``THCOMPNAME`` - Thermal component name, usually filter name (str)
    #. ``COMMENT`` - Comment (str)

    Example:

    +--------+-------+------+-------+----------+--------+
    |COMPNAME|KEYWORD|INNODE|OUTNODE|THCOMPNAME|COMMENT |
    +========+=======+======+=======+==========+========+
    | clear  |nicmos |   1  |  30   |  clear   |idno=100|
    +--------+-------+------+-------+----------+--------+
    | clear  | wfc3  |   1  |  30   |  clear   |        |
    +--------+-------+------+-------+----------+--------+
    | clear  | wfpc  |   1  |  20   |  clear   |idno=100|
    +--------+-------+------+-------+----------+--------+

    Parameters
    ----------
    filename : str
        Graph table filename.
        If suffix is not 'fits' or 'fit', assume ASCII format.

    tab_ext : int, optional
        FITS extension index of the data table.
        This is ignored for ASCII file.

    Returns
    -------
    primary_area : `~astropy.units.quantity.Quantity` or `None`
        Value of PRIMAREA keyword in primary header.
        Always `None` for ASCII file.

    data : `~astropy.io.fits.FITS_rec` or `~astropy.table.Table`
        Data table.

    Raises
    ------
    synphot.exceptions.SynphotError
        Failure to parse graph table.

    """
    graph_dtypes = {
        'COMPNAME': np.str_,
        'KEYWORD': np.str_,
        'INNODE': np.int32,
        'OUTNODE': np.int32,
        'THCOMPNAME': np.str_,
        'COMMENT': np.str_
    }
    data = _read_table(filename, tab_ext, graph_dtypes)

    # Get primary area
    if filename.endswith('.fits') or filename.endswith('.fit'):
        with fits.open(filename) as f:
            primary_area = f[str('PRIMARY')].header.get('PRIMAREA', None)
    else:  # pragma: no cover
        primary_area = None

    if primary_area is not None and not isinstance(primary_area, u.Quantity):
        primary_area = primary_area * units.AREA

    # Check for segmented graph table
    if np.any([x.lower().endswith('graph')
               for x in data['COMPNAME']]):  # pragma: no cover
        raise synexceptions.SynphotError(
            'Segmented graph tables not supported.')

    return primary_area, data
Beispiel #10
0
def interpolate_spectral_element(parfilename, interpval, ext=1):
    """Interpolate (or extrapolate) throughput spectra in given
    parameterized FITS table to given parameter value.

    FITS table is parsed with :func:`stsynphot.stio.read_interp_spec`.
    Parameterized values must be in ascending order in the
    table columns.

    If extrapolation is needed but not allowed, default throughput
    from ``THROUGHPUT`` column will be used.

    Parameters
    ----------
    parfilename : str
        Parameterized filename contains a suffix followed by
        a column name specificationin between square brackets.
        For example, ``path/acs_fr656n_006_syn.fits[fr656n#]``.

    interpval : float
        Desired parameter value.

    ext : int, optional
        FITS extension index of the data table.

    Returns
    -------
    sp : `synphot.spectrum.SpectralElement`
        Empirical bandpass at ``interpval``.

    Raises
    ------
    synphot.exceptions.ExtrapolationNotAllowed
        Extrapolation is not allowed by data table.

    synphot.exceptions.SynphotError
        No columns available for interpolation or extrapolation.

    """
    def_colname = 'THROUGHPUT'
    warndict = {}

    # Separate real filename and column name specification
    xre = _interpfilepatt.search(parfilename)
    if xre is None:
        raise synexceptions.SynphotError(
            '{0} must be in the format of "path/filename.fits'
            '[col#]"'.format(parfilename))
    filename = parfilename[0:xre.start()]
    col_prefix = xre.group('col').upper()

    # Read data table
    data, wave_unit, doshift, extrapolate = stio.read_interp_spec(
        filename, tab_ext=ext)
    wave_unit = units.validate_unit(wave_unit)
    wave0 = data['WAVELENGTH']

    # Determine the columns that bracket the desired value.
    # Grab all columns that begin with the parameter name (e.g. 'MJD#')
    # and then split off the numbers after the '#'.
    col_names = []
    col_pars = []
    for n in data.names:
        cn = n.upper()
        if cn.startswith(col_prefix):
            col_names.append(cn)
            col_pars.append(float(cn.split('#')[1]))

    if len(col_names) < 1:
        raise synexceptions.SynphotError(
            '{0} contains no interpolated columns for {1}.'.format(
                filename, col_prefix))

    # Assumes ascending order of parameter values in table.
    min_par = col_pars[0]
    max_par = col_pars[-1]

    # Exact match. No interpolation needed.
    if interpval in col_pars:
        thru = data[col_names[col_pars.index(interpval)]]

    # Need interpolation.
    elif (interpval > min_par) and (interpval < max_par):
        upper_ind = np.searchsorted(col_pars, interpval)
        lower_ind = upper_ind - 1

        thru = _interp_spec(
            interpval, wave0, col_pars[lower_ind], col_pars[upper_ind],
            data[col_names[lower_ind]], data[col_names[upper_ind]], doshift)

    # Need extrapolation, if allowed.
    elif extrapolate:
        # Extrapolate below lowest columns.
        if interpval < min_par:
            thru = _extrap_spec(interpval, min_par, col_pars[1],
                                data[col_names[0]], data[col_names[1]])

        # Extrapolate above highest columns.
        else:  # interpval > max_par
            thru = _extrap_spec(interpval, col_pars[-2], max_par,
                                data[col_names[-2]], data[col_names[-1]])

    # Extrapolation not allowed.
    else:
        # Use default, if available.
        if def_colname in data.names:
            warnings.warn(
                'Extrapolation not allowed, using default throughput for '
                '{0}.'.format(parfilename), AstropyUserWarning)
            warndict['DefaultThroughput'] = True
            thru = data[def_colname]

        # Nothing can be done.
        else:
            raise synexceptions.ExtrapolationNotAllowed(
                'No default throughput for {0}.'.format(parfilename))

    meta = {'expr': '{0}#{1:g}'.format(filename, interpval),
            'warnings': warndict}
    return SpectralElement(
        Empirical1D, points=wave0*wave_unit, lookup_table=thru, meta=meta)
Beispiel #11
0
def grid_to_spec(gridname, t_eff, metallicity, log_g):
    """Extract spectrum from given catalog grid parameters.
    Interpolate if necessary.

    Grid parameters are only read once and then cached.
    Until the cache is cleared explicitly using
    :func:`reset_cache`, cached values are used.

    Parameters
    ----------
    gridname : {'ck04models', 'k93models', 'phoenix'}
        Model to use:
            * ``ck04models`` - Castelli & Kurucz (2004)
            * ``k93models`` - Kurucz (1993)
            * ``phoenix`` - Allard et al. (2009)

    t_eff : str, float or `astropy.units.quantity.Quantity`
        Effective temperature of model.
        If not Quantity, assumed to be in Kelvin.
        If string (from parser), convert to Quantity.

    metallicity : str or float
        Metallicity of model.
        If string (from parser), convert to float.

    log_g : str or float
        Log surface gravity for model.
        If string (from parser), convert to float.

    Returns
    -------
    sp : `synphot.spectrum.SourceSpectrum`
        Empirical source spectrum.

    Raises
    ------
    stsynphot.exceptions.ParameterOutOfBounds
        Grid parameter out of bounds.

    synphot.exceptions.SynphotError
        Invalid inputs.

    """
    if gridname == 'ck04models':
        catdir = 'crgridck04$'
    elif gridname == 'k93models':
        catdir = 'crgridk93$'
    elif gridname == 'phoenix':
        catdir = 'crgridphoenix$'
    else:
        raise synexceptions.SynphotError(
            '{0} is not a supported catalog grid.'.format(gridname))

    metallicity = _par_from_parser(metallicity)
    if isinstance(metallicity, u.Quantity):
        raise synexceptions.SynphotError(
            'Quantity is not supported for metallicity.')

    log_g = _par_from_parser(log_g)
    if isinstance(log_g, u.Quantity):
        raise synexceptions.SynphotError(
            'Quantity is not supported for log surface gravity.')

    t_eff = units.validate_quantity(_par_from_parser(t_eff), u.K).value
    catdir = stio.irafconvert(catdir)
    filename = os.path.join(catdir, 'catalog.fits')

    # If not cached, read from grid catalog and cache it
    if filename not in _CACHE:
        data = stio.read_catalog(filename)  # Ext 1
        _CACHE[filename] = [[float(x) for x in index.split(',')] +
                            [data['FILENAME'][i]]
                            for i, index in enumerate(data['INDEX'])]

    indices = _CACHE[filename]

    list0, list1 = _break_list(indices, 0, t_eff)

    list2, list3 = _break_list(list0, 1, metallicity)
    list4, list5 = _break_list(list1, 1, metallicity)

    list6, list7 = _break_list(list2, 2, log_g)
    list8, list9 = _break_list(list3, 2, log_g)
    list10, list11 = _break_list(list4, 2, log_g)
    list12, list13 = _break_list(list5, 2, log_g)

    sp1 = _get_spectrum(list6[0], catdir)
    sp2 = _get_spectrum(list7[0], catdir)
    sp3 = _get_spectrum(list8[0], catdir)
    sp4 = _get_spectrum(list9[0], catdir)
    sp5 = _get_spectrum(list10[0], catdir)
    sp6 = _get_spectrum(list11[0], catdir)
    sp7 = _get_spectrum(list12[0], catdir)
    sp8 = _get_spectrum(list13[0], catdir)

    spa1 = _interpolate_spectrum(sp1, sp2, log_g)
    spa2 = _interpolate_spectrum(sp3, sp4, log_g)
    spa3 = _interpolate_spectrum(sp5, sp6, log_g)
    spa4 = _interpolate_spectrum(sp7, sp8, log_g)

    spa5 = _interpolate_spectrum(spa1, spa2, metallicity)
    spa6 = _interpolate_spectrum(spa3, spa4, metallicity)

    spa7 = _interpolate_spectrum(spa5, spa6, t_eff)

    sp = spa7[0]
    sp.meta['expr'] = '{0}(T_eff={1:g},metallicity={2:g},log_g={3:g})'.format(
        gridname, t_eff, metallicity, log_g)

    return sp
def grid_to_spec(gridname, t_eff, metallicity, log_g):
    """Extract spectrum from given catalog grid parameters.
    Interpolate if necessary.

    Grid parameters are read with :func:`get_catalog_index`.

    Parameters
    ----------
    gridname : {'ck04models', 'k93models', 'phoenix'}
        Model to use:
            * ``ck04models`` - Castelli & Kurucz (2004)
            * ``k93models`` - Kurucz (1993)
            * ``phoenix`` - Allard et al. (2009)

    t_eff : str, float or `astropy.units.quantity.Quantity`
        Effective temperature of model.
        If not Quantity, assumed to be in Kelvin.
        If string (from parser), convert to Quantity.

    metallicity : str or float
        Metallicity of model.
        If string (from parser), convert to float.

    log_g : str or float
        Log surface gravity for model.
        If string (from parser), convert to float.

    Returns
    -------
    sp : `synphot.spectrum.SourceSpectrum`
        Empirical source spectrum.

    Raises
    ------
    stsynphot.exceptions.ParameterOutOfBounds
        Grid parameter out of bounds.

    synphot.exceptions.SynphotError
        Invalid inputs.

    """
    indices, catdir = get_catalog_index(gridname)

    metallicity = _par_from_parser(metallicity)
    if isinstance(metallicity, u.Quantity):
        raise synexceptions.SynphotError(
            'Quantity is not supported for metallicity.')

    log_g = _par_from_parser(log_g)
    if isinstance(log_g, u.Quantity):
        raise synexceptions.SynphotError(
            'Quantity is not supported for log surface gravity.')

    t_eff = units.validate_quantity(_par_from_parser(t_eff), u.K).value

    list0, list1 = _break_list(indices, 0, t_eff)

    list2, list3 = _break_list(list0, 1, metallicity)
    list4, list5 = _break_list(list1, 1, metallicity)

    list6, list7 = _break_list(list2, 2, log_g)
    list8, list9 = _break_list(list3, 2, log_g)
    list10, list11 = _break_list(list4, 2, log_g)
    list12, list13 = _break_list(list5, 2, log_g)

    sp1 = _get_spectrum(list6[0], catdir)
    sp2 = _get_spectrum(list7[0], catdir)
    sp3 = _get_spectrum(list8[0], catdir)
    sp4 = _get_spectrum(list9[0], catdir)
    sp5 = _get_spectrum(list10[0], catdir)
    sp6 = _get_spectrum(list11[0], catdir)
    sp7 = _get_spectrum(list12[0], catdir)
    sp8 = _get_spectrum(list13[0], catdir)

    spa1 = _interpolate_spectrum(sp1, sp2, log_g)
    spa2 = _interpolate_spectrum(sp3, sp4, log_g)
    spa3 = _interpolate_spectrum(sp5, sp6, log_g)
    spa4 = _interpolate_spectrum(sp7, sp8, log_g)

    spa5 = _interpolate_spectrum(spa1, spa2, metallicity)
    spa6 = _interpolate_spectrum(spa3, spa4, metallicity)

    spa7 = _interpolate_spectrum(spa5, spa6, t_eff)

    sp = spa7[0]
    sp.meta['expr'] = (f'{gridname}(T_eff={t_eff:g},'
                       f'metallicity={metallicity:g},log_g={log_g:g})')

    return sp