Source code for pypeit.core.meta
"""
Provides methods common to :class:`~pypeit.metadata.PypeItMetaData` and
:class:`~pypeit.spectrographs.spectrograph.Spectrograph` that define the
common metadata used for all spectrographs.
.. include common links, assuming primary doc root is up one directory
.. include:: ../include/links.rst
"""
import numpy as np
from astropy import units, coordinates
from IPython import embed
[docs]
def convert_radec(ra, dec):
"""
Handle multiple ra,dec inputs and return decimal degrees
If ra, dec are str but do *not* have J or ':' in the RA term,
then they will be converted to floats
Args:
ra (str or float or `numpy.ndarray`_):
RA as decimal deg (float) or hh:mm:ss.s (str)
dec (str or float or `numpy.ndarray`_):
DEC as decimal deg (float) or +dd:mm:ss.s (str)
Must be the same format as ra
Returns:
tuple:
float,float of ra,dec in decimal deg if input is str or float
np.ndarray, np.ndarray of ra,dec in decimal deg if input is np.ndarray
"""
if isinstance(ra, str):
if (('J' in ra) or (':' in ra)) or (' ' in ra.strip()):
coord = coordinates.SkyCoord(ra, dec, unit=(units.hourangle, units.deg))
return coord.ra.value, coord.dec.value
else:
return float(ra), float(dec)
elif isinstance(ra, np.ndarray):
if isinstance(ra[0], str):
if (('J' in ra[0]) or (':' in ra[0])) or (' ' in ra[0].strip()):
coords = coordinates.SkyCoord(ra,dec, unit=(units.hourangle, units.deg))
return coords.ra.value, coords.dec.value
else:
return ra.astype(float), dec.astype(float)
elif isinstance(ra[0], float):
return ra.astype(float), dec.astype(float)
else:
raise IOError("Bad ra, dec format!!")
else:
return ra, dec
[docs]
def define_core_meta():
"""
Define the core set of meta data that must be defined
to run PypeIt.
See the metadata.rst file for further discussion
.. warning::
The keys should all be <= 8 length as they are all written
to the Header.
Each meta entry is a dict with the following keys:
- dtype: str, float, int
- comment: str
- rtol: float, optional -- Sets the relative tolerance for
float meta when used to set a configuration
Each meta dtype must be scalar or str. No tuple, list, ndarray, etc.
Returns:
dict: core_meta
"""
# Mainly to format output to PypeIt file
core_meta = {}
# Target
core_meta['ra'] = dict(dtype=float, comment='(J2000) RA in decimal degrees')
core_meta['dec'] = dict(dtype=float, comment='(J2000) DEC in decimal degrees')
core_meta['target'] = dict(dtype=str, comment='Name of the target')
# Instrument related
core_meta['dispname'] = dict(dtype=str, comment='Disperser name')
core_meta['decker'] = dict(dtype=str, comment='Slit/mask/decker name')
core_meta['binning'] = dict(dtype=str, comment='"spectral,spatial" binning')
# Obs
core_meta['mjd'] = dict(dtype=float, comment='Observation MJD; Read by astropy.time.Time format=mjd')
core_meta['airmass'] = dict(dtype=float, comment='Airmass')
core_meta['exptime'] = dict(dtype=float, comment='Exposure time (s)')
# Test me
# TODO: Do we need this?
for key in core_meta.keys():
assert len(key) <= 8
# Return
return core_meta
[docs]
def define_additional_meta(nlamps=20):
"""
Defines meta that tends to be instrument-specific and not used as
widely in the code.
See :func:`define_core_meta` for additional details
For meta used to define configurations, the rtol key specifies
the relative tolerance for a match
Args:
nlamps (:obj:`int`, optional):
Number of calibrations lamps for this instrument.
Returns:
:obj:`dict`: Describes the additional meta data used in
pypeit.
"""
# TODO: Can we consolidate dither (only read by vlt_xshooter, and never
# used in the code) with the MOSFIRE dith* keywords?
additional_meta = {'amp': dict(dtype=str, comment='Amplifier used'),
'arm': dict(dtype=str, comment='Name of arm (e.g. NIR for X-Shooter)'),
'calpos': dict(dtype=str, comment='Position of calibration system (KCWI)'),
'datasec': dict(dtype=str, comment='Data section (windowing)'),
'dateobs': dict(dtype=str, comment='Observation date'),
'decker_secondary': dict(dtype=str, comment='Partial Slitmask/decker name. '
'It differs from decker. This is currently '
'only needed for the reduction of some '
'MOSFIRE masks, which use calibrations '
'taken with a partially different decker '
'name than the one used for the associated '
'science frames.'),
'detector': dict(dtype=str, comment='Name of detector'),
'dichroic': dict(dtype=str, comment='Beam splitter'),
'dispangle': dict(dtype=float, comment='Angle of the disperser', rtol=0.),
'cenwave': dict(dtype=float, comment='Central wavelength of the disperser', rtol=0.),
# TODO what is the difference between dither and dithoff? Also, we should rename these to be
# more clearly the offset along the slit to distinguish from the IFU case of RA_off and DEC_off
'dither': dict(dtype=float, comment='Dither amount in arcsec'),
'dithoff': dict(dtype=float, comment='Dither offset'),
'dithpat': dict(dtype=str, comment='Dither pattern'),
'dithpos': dict(dtype=str, comment='Dither position'),
'posang': dict(dtype=float, comment='Position angle of the observation (degrees, positive is East from North)'),
'ra_off': dict(dtype=float, comment='Dither offset in RA'),
'dec_off': dict(dtype=float, comment='Dither offset in DEC'),
'echangle':dict(dtype=float, comment='Echelle angle'),
'filter1': dict(dtype=str, comment='First filter in optical path'),
'filter2': dict(dtype=str, comment='Second filter in optical path'),
'frameno': dict(dtype=str, comment='Frame number provided by instrument software'),
'hatch': dict(dtype=str, comment='Position of instrument hatch'),
'humidity': dict(dtype=float, comment='Humidity at observation time (as a percentage, not a fraction)'),
'idname': dict(dtype=str, comment='Instrument supplied frametype (e.g. bias)'),
'instrument': dict(dtype=str, comment='Header supplied instrument name'),
'mode': dict(dtype=str, comment='Observing mode'),
'object': dict(dtype=str, comment='Alternative object name (cf. target)'),
'obstime': dict(dtype=str, comment='Observation time'),
'oscansec': dict(dtype=str, comment='Overscan section (windowing)'),
'parangle': dict(dtype=float, comment='Parallactic angle (units.radian)'),
'pressure': dict(dtype=float, comment='Pressure (units.pascal) at observation time'),
'seq_expno': dict(dtype=int, comment='Number of exposure in observing sequence'),
'slitwid': dict(dtype=float, comment='Slit width, sometimes distinct from decker'),
'slitlength': dict(dtype=float, comment='Slit length, used only for long slits'),
'temperature': dict(dtype=float, comment='Temperature (units.K) at observation time'),
'utc': dict(dtype=str, comment='UTC of observation'),
'mirror': dict(dtype=str, comment='Position of an instrument mirror (e.g. IN or OUT)'),
'xd': dict(dtype=float, comment='Cross disperser (e.g. red or blue for HIRES)'),
'xdangle':dict(dtype=float, comment='Cross disperser angle'),
}
for kk in range(nlamps):
additional_meta['lampstat{:02d}'.format(kk+1)] \
= dict(dtype=str, comment='Status of a given lamp (e.g off/on)')
additional_meta['lampshst{:02d}'.format(kk + 1)] \
= dict(dtype=str, comment='Status of a lamp shutter (e.g closed/open)')
return additional_meta
[docs]
def get_meta_data_model(nlamps=20):
"""
Construct full metadata model general to all spectrographs.
This is a wrapper for :func:`define_core_meta` and
:func:`define_additional_meta` that checks that the keys defined
by both are unique (a coding issue) and returns a single combined
dictionary.
Args:
nlamps (:obj:`int`, optional):
Number of calibrations lamps for this instrument, passed
directly to :func:`define_additional_meta`.
Returns:
:obj:`dict`: Dictionary with the full metadata model common to all
spectrographs.
Raises:
ValueError:
Raised if the coding of func:`define_core_meta` and
:func:`define_additional_meta` do not produce unique
keys. This should never be raised in the released
version.
"""
core = define_core_meta()
add = define_additional_meta(nlamps=nlamps)
if np.any(np.isin(list(core.keys()), list(add.keys()))):
raise ValueError('CODING ERROR: Keys in core and additional meta data are not unique!')
core.update(add)
return core