Coverage for python/astro_metadata_translator/properties.py: 62%
62 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 02:23 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 02:23 -0700
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"""Properties calculated by this package.
14Defines all properties in one place so that both `ObservationInfo` and
15`MetadataTranslator` can use them. In particular, the translator
16base class can use knowledge of these properties to predefine translation
17stubs with documentation attached, and `ObservationInfo` can automatically
18define the getter methods.
20"""
21from __future__ import annotations
23__all__ = (
24 "PropertyDefinition",
25 "PROPERTIES",
26)
28from dataclasses import dataclass
29from typing import Any, Callable, Optional, Tuple
31import astropy.coordinates
32import astropy.time
33import astropy.units
35# Helper functions to convert complex types to simple form suitable
36# for JSON serialization
37# All take the complex type and return simple python form using str, float,
38# int, dict, or list.
39# All assume the supplied parameter is not None.
42def earthlocation_to_simple(location: astropy.coordinates.EarthLocation) -> Tuple[float, ...]:
43 """Convert EarthLocation to tuple.
45 Parameters
46 ----------
47 location : `astropy.coordinates.EarthLocation`
48 The location to simplify.
50 Returns
51 -------
52 geocentric : `tuple` of (`float`, `float`, `float`)
53 The geocentric location as three floats in meters.
54 """
55 geocentric = location.to_geocentric()
56 return tuple(c.to_value(astropy.units.m) for c in geocentric)
59def simple_to_earthlocation(simple: Tuple[float, ...], **kwargs: Any) -> astropy.coordinates.EarthLocation:
60 """Convert simple form back to EarthLocation."""
61 return astropy.coordinates.EarthLocation.from_geocentric(*simple, unit=astropy.units.m)
64def datetime_to_simple(datetime: astropy.time.Time) -> Tuple[float, float]:
65 """Convert Time to tuple.
67 Parameters
68 ----------
69 datetime : `astropy.time.Time`
70 The time to simplify.
72 Returns
73 -------
74 mjds : `tuple` of (`float`, `float`)
75 The two MJDs in TAI.
76 """
77 tai = datetime.tai
78 return (tai.jd1, tai.jd2)
81def simple_to_datetime(simple: Tuple[float, float], **kwargs: Any) -> astropy.time.Time:
82 """Convert simple form back to astropy.time.Time"""
83 return astropy.time.Time(*simple, format="jd", scale="tai")
86def exptime_to_simple(exptime: astropy.units.Quantity) -> float:
87 """Convert exposure time Quantity to seconds."""
88 return exptime.to_value(astropy.units.s)
91def simple_to_exptime(simple: float, **kwargs: Any) -> astropy.units.Quantity:
92 """Convert simple form back to Quantity."""
93 return simple * astropy.units.s
96def angle_to_simple(angle: astropy.coordinates.Angle) -> float:
97 """Convert Angle to degrees."""
98 return angle.to_value(astropy.units.deg)
101def simple_to_angle(simple: float, **kwargs: Any) -> astropy.coordinates.Angle:
102 """Convert degrees to Angle."""
103 return astropy.coordinates.Angle(simple * astropy.units.deg)
106def focusz_to_simple(focusz: astropy.units.Quantity) -> float:
107 """Convert focusz to meters."""
108 return focusz.to_value(astropy.units.m)
111def simple_to_focusz(simple: float, **kwargs: Any) -> astropy.units.Quantity:
112 """Convert simple form back to Quantity."""
113 return simple * astropy.units.m
116def temperature_to_simple(temp: astropy.units.Quantity) -> float:
117 """Convert temperature to kelvin."""
118 return temp.to(astropy.units.K, equivalencies=astropy.units.temperature()).to_value()
121def simple_to_temperature(simple: float, **kwargs: Any) -> astropy.units.Quantity:
122 """Convert scalar kelvin value back to quantity."""
123 return simple * astropy.units.K
126def pressure_to_simple(press: astropy.units.Quantity) -> float:
127 """Convert pressure Quantity to hPa."""
128 return press.to_value(astropy.units.hPa)
131def simple_to_pressure(simple: float, **kwargs: Any) -> astropy.units.Quantity:
132 """Convert the pressure scalar back to Quantity."""
133 return simple * astropy.units.hPa
136def skycoord_to_simple(skycoord: astropy.coordinates.SkyCoord) -> Tuple[float, float]:
137 """Convert SkyCoord to ICRS RA/Dec tuple"""
138 icrs = skycoord.icrs
139 return (icrs.ra.to_value(astropy.units.deg), icrs.dec.to_value(astropy.units.deg))
142def simple_to_skycoord(simple: Tuple[float, float], **kwargs: Any) -> astropy.coordinates.SkyCoord:
143 """Convert ICRS tuple to SkyCoord."""
144 return astropy.coordinates.SkyCoord(*simple, unit=astropy.units.deg)
147def altaz_to_simple(altaz: astropy.coordinates.AltAz) -> Tuple[float, float]:
148 """Convert AltAz to Alt/Az tuple.
150 Do not include obstime or location in simplification. It is assumed
151 that those will be present from other properties.
152 """
153 return (altaz.az.to_value(astropy.units.deg), altaz.alt.to_value(astropy.units.deg))
156def simple_to_altaz(simple: Tuple[float, float], **kwargs: Any) -> astropy.coordinates.AltAz:
157 """Convert simple altaz tuple to AltAz.
159 Will look for location and datetime_begin in kwargs.
160 """
161 location = kwargs.get("location")
162 obstime = kwargs.get("datetime_begin")
164 return astropy.coordinates.AltAz(
165 simple[0] * astropy.units.deg, simple[1] * astropy.units.deg, obstime=obstime, location=location
166 )
169@dataclass
170class PropertyDefinition:
171 """Definition of an instrumental property."""
173 doc: str
174 """Docstring for the property."""
176 str_type: str
177 """Python type of property as a string (suitable for docstrings)."""
179 py_type: type
180 """Actual python type."""
182 to_simple: Optional[Callable[[Any], Any]] = None
183 """Function to convert value to simple form (can be ``None``)."""
185 from_simple: Optional[Callable[[Any], Any]] = None
186 """Function to convert from simple form back to required type (can be
187 ``None``)."""
190# Dict of properties to tuple where tuple is:
191# - description of property
192# - Python type of property as a string (suitable for docstrings)
193# - Actual python type as a type
194# - Simplification function (can be None)
195# - Function to convert simple form back to required type (can be None)
196PROPERTIES = {
197 "telescope": PropertyDefinition("Full name of the telescope.", "str", str),
198 "instrument": PropertyDefinition("The instrument used to observe the exposure.", "str", str),
199 "location": PropertyDefinition(
200 "Location of the observatory.",
201 "astropy.coordinates.EarthLocation",
202 astropy.coordinates.EarthLocation,
203 earthlocation_to_simple,
204 simple_to_earthlocation,
205 ),
206 "exposure_id": PropertyDefinition(
207 "Unique (with instrument) integer identifier for this observation.", "int", int
208 ),
209 "visit_id": PropertyDefinition(
210 """ID of the Visit this Exposure is associated with.
212Science observations should essentially always be
213associated with a visit, but calibration observations
214may not be.""",
215 "int",
216 int,
217 ),
218 "physical_filter": PropertyDefinition("The bandpass filter used for this observation.", "str", str),
219 "datetime_begin": PropertyDefinition(
220 "Time of the start of the observation.",
221 "astropy.time.Time",
222 astropy.time.Time,
223 datetime_to_simple,
224 simple_to_datetime,
225 ),
226 "datetime_end": PropertyDefinition(
227 "Time of the end of the observation.",
228 "astropy.time.Time",
229 astropy.time.Time,
230 datetime_to_simple,
231 simple_to_datetime,
232 ),
233 "exposure_time": PropertyDefinition(
234 "Duration of the exposure with shutter open (seconds).",
235 "astropy.units.Quantity",
236 astropy.units.Quantity,
237 exptime_to_simple,
238 simple_to_exptime,
239 ),
240 "dark_time": PropertyDefinition(
241 "Duration of the exposure with shutter closed (seconds).",
242 "astropy.units.Quantity",
243 astropy.units.Quantity,
244 exptime_to_simple,
245 simple_to_exptime,
246 ),
247 "boresight_airmass": PropertyDefinition("Airmass of the boresight of the telescope.", "float", float),
248 "boresight_rotation_angle": PropertyDefinition(
249 "Angle of the instrument in boresight_rotation_coord frame.",
250 "astropy.coordinates.Angle",
251 astropy.coordinates.Angle,
252 angle_to_simple,
253 simple_to_angle,
254 ),
255 "boresight_rotation_coord": PropertyDefinition(
256 "Coordinate frame of the instrument rotation angle (options: sky, unknown).",
257 "str",
258 str,
259 ),
260 "detector_num": PropertyDefinition(
261 "Unique (for instrument) integer identifier for the sensor.", "int", int
262 ),
263 "detector_name": PropertyDefinition(
264 "Name of the detector within the instrument (might not be unique if there are detector groups).",
265 "str",
266 str,
267 ),
268 "detector_unique_name": PropertyDefinition(
269 (
270 "Unique name of the detector within the focal plane, generally combining detector_group with "
271 "detector_name."
272 ),
273 "str",
274 str,
275 ),
276 "detector_serial": PropertyDefinition("Serial number/string associated with this detector.", "str", str),
277 "detector_group": PropertyDefinition(
278 "Collection name of which this detector is a part. Can be None if there are no detector groupings.",
279 "str",
280 str,
281 ),
282 "detector_exposure_id": PropertyDefinition(
283 "Unique integer identifier for this detector in this exposure.",
284 "int",
285 int,
286 ),
287 "focus_z": PropertyDefinition(
288 "Defocal distance.",
289 "astropy.units.Quantity",
290 astropy.units.Quantity,
291 focusz_to_simple,
292 simple_to_focusz,
293 ),
294 "object": PropertyDefinition("Object of interest or field name.", "str", str),
295 "temperature": PropertyDefinition(
296 "Temperature outside the dome.",
297 "astropy.units.Quantity",
298 astropy.units.Quantity,
299 temperature_to_simple,
300 simple_to_temperature,
301 ),
302 "pressure": PropertyDefinition(
303 "Atmospheric pressure outside the dome.",
304 "astropy.units.Quantity",
305 astropy.units.Quantity,
306 pressure_to_simple,
307 simple_to_pressure,
308 ),
309 "relative_humidity": PropertyDefinition("Relative humidity outside the dome.", "float", float),
310 "tracking_radec": PropertyDefinition(
311 "Requested RA/Dec to track.",
312 "astropy.coordinates.SkyCoord",
313 astropy.coordinates.SkyCoord,
314 skycoord_to_simple,
315 simple_to_skycoord,
316 ),
317 "altaz_begin": PropertyDefinition(
318 "Telescope boresight azimuth and elevation at start of observation.",
319 "astropy.coordinates.AltAz",
320 astropy.coordinates.AltAz,
321 altaz_to_simple,
322 simple_to_altaz,
323 ),
324 "science_program": PropertyDefinition("Observing program (survey or proposal) identifier.", "str", str),
325 "observation_type": PropertyDefinition(
326 "Type of observation (currently: science, dark, flat, bias, focus).",
327 "str",
328 str,
329 ),
330 "observation_id": PropertyDefinition(
331 "Label uniquely identifying this observation (can be related to 'exposure_id').",
332 "str",
333 str,
334 ),
335 "observation_reason": PropertyDefinition(
336 "Reason this observation was taken, or its purpose ('science' and 'calibration' are common values)",
337 "str",
338 str,
339 ),
340 "exposure_group": PropertyDefinition(
341 "Label to use to associate this exposure with others (can be related to 'exposure_id').",
342 "str",
343 str,
344 ),
345 "observing_day": PropertyDefinition(
346 "Integer in YYYYMMDD format corresponding to the day of observation.", "int", int
347 ),
348 "observation_counter": PropertyDefinition(
349 (
350 "Counter of this observation. Can be counter within observing_day or a global counter. "
351 "Likely to be observatory specific."
352 ),
353 "int",
354 int,
355 ),
356 "has_simulated_content": PropertyDefinition(
357 "Boolean indicating whether any part of this observation was simulated.", "bool", bool, None, None
358 ),
359 "group_counter_start": PropertyDefinition(
360 "Observation counter for the start of the exposure group."
361 "Depending on the instrument the relevant group may be "
362 "visit_id or exposure_group.",
363 "int",
364 int,
365 None,
366 None,
367 ),
368 "group_counter_end": PropertyDefinition(
369 "Observation counter for the end of the exposure group. "
370 "Depending on the instrument the relevant group may be "
371 "visit_id or exposure_group.",
372 "int",
373 int,
374 None,
375 None,
376 ),
377}