Coverage for python/astro_metadata_translator/translators/fits.py: 27%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of astro_metadata_translator.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the LICENSE file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12"""Metadata translation code for standard FITS headers"""
14__all__ = ("FitsTranslator", )
16from astropy.time import Time
17from astropy.coordinates import EarthLocation
18import astropy.units as u
20from ..translator import MetadataTranslator, cache_translation
23class FitsTranslator(MetadataTranslator):
24 """Metadata translator for FITS standard headers.
26 Understands:
28 - DATE-OBS
29 - INSTRUME
30 - TELESCOP
31 - OBSGEO-[X,Y,Z]
33 """
35 # Direct translation from header key to standard form
36 _trivial_map = dict(instrument="INSTRUME",
37 telescope="TELESCOP")
39 @classmethod
40 def can_translate(cls, header, filename=None):
41 """Indicate whether this translation class can translate the
42 supplied header.
44 Checks the instrument value and compares with the supported
45 instruments in the class
47 Parameters
48 ----------
49 header : `dict`-like
50 Header to convert to standardized form.
51 filename : `str`, optional
52 Name of file being translated.
54 Returns
55 -------
56 can : `bool`
57 `True` if the header is recognized by this class. `False`
58 otherwise.
59 """
60 if cls.supported_instrument is None:
61 return False
63 # Protect against being able to always find a standard
64 # header for instrument
65 try:
66 translator = cls(header, filename=filename)
67 instrument = translator.to_instrument()
68 except KeyError:
69 return False
71 return instrument == cls.supported_instrument
73 @classmethod
74 def _from_fits_date_string(cls, date_str, scale='utc', time_str=None):
75 """Parse standard FITS ISO-style date string and return time object
77 Parameters
78 ----------
79 date_str : `str`
80 FITS format date string to convert to standard form. Bypasses
81 lookup in the header.
82 scale : `str`, optional
83 Override the time scale from the TIMESYS header. Defaults to
84 UTC.
85 time_str : `str`, optional
86 If provided, overrides any time component in the ``dateStr``,
87 retaining the YYYY-MM-DD component and appending this time
88 string, assumed to be of format HH:MM::SS.ss.
90 Returns
91 -------
92 date : `astropy.time.Time`
93 `~astropy.time.Time` representation of the date.
94 """
95 if time_str is not None:
96 date_str = "{}T{}".format(date_str[:10], time_str)
98 return Time(date_str, format="isot", scale=scale)
100 def _from_fits_date(self, date_key, mjd_key=None, scale=None):
101 """Calculate a date object from the named FITS header
103 Uses the TIMESYS header if present to determine the time scale,
104 defaulting to UTC. Can be overridden since sometimes headers
105 use TIMESYS for DATE- style headers but also have headers using
106 different time scales.
108 Parameters
109 ----------
110 date_key : `str`
111 The key in the header representing a standard FITS
112 ISO-style date. Can be `None` to go straight to MJD key.
113 mjd_key : `str`, optional
114 The key in the header representing a standard FITS MJD
115 style date. This key will be tried if ``date_key`` is not
116 found, is `None`, or can not be parsed.
117 scale : `str`, optional
118 Override value to use for the time scale in preference to
119 TIMESYS or the default. Should be a form understood by
120 `~astropy.time.Time`.
122 Returns
123 -------
124 date : `astropy.time.Time`
125 `~astropy.time.Time` representation of the date.
126 """
127 used = []
128 if scale is not None:
129 pass
130 elif self.is_key_ok("TIMESYS"):
131 scale = self._header["TIMESYS"].lower()
132 used.append("TIMESYS")
133 else:
134 scale = "utc"
135 if date_key is not None and self.is_key_ok(date_key):
136 date_str = self._header[date_key]
137 value = self._from_fits_date_string(date_str, scale=scale)
138 used.append(date_key)
139 elif self.is_key_ok(mjd_key):
140 value = Time(self._header[mjd_key], scale=scale, format="mjd")
141 used.append(mjd_key)
142 else:
143 value = None
144 self._used_these_cards(*used)
145 return value
147 @cache_translation
148 def to_datetime_begin(self):
149 """Calculate start time of observation.
151 Uses FITS standard ``MJD-OBS`` or ``DATE-OBS``, in conjunction
152 with the ``TIMESYS`` header.
154 Returns
155 -------
156 start_time : `astropy.time.Time`
157 Time corresponding to the start of the observation.
158 """
159 return self._from_fits_date("DATE-OBS", mjd_key="MJD-OBS")
161 @cache_translation
162 def to_datetime_end(self):
163 """Calculate end time of observation.
165 Uses FITS standard ``MJD-END`` or ``DATE-END``, in conjunction
166 with the ``TIMESYS`` header.
168 Returns
169 -------
170 start_time : `astropy.time.Time`
171 Time corresponding to the end of the observation.
172 """
173 return self._from_fits_date("DATE-END", mjd_key="MJD-END")
175 @cache_translation
176 def to_location(self):
177 """Calculate the observatory location.
179 Uses FITS standard ``OBSGEO-`` headers.
181 Returns
182 -------
183 location : `astropy.coordinates.EarthLocation`
184 An object representing the location of the telescope.
185 """
186 cards = [f"OBSGEO-{c}" for c in ("X", "Y", "Z")]
187 coords = [self._header[c] for c in cards]
188 value = EarthLocation.from_geocentric(*coords, unit=u.m)
189 self._used_these_cards(*cards)
190 return value