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