Coverage for python/astro_metadata_translator/properties.py: 63%

58 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-06-02 03:27 -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. 

11 

12"""Properties calculated by this package. 

13 

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. 

19 

20""" 

21from __future__ import annotations 

22 

23__all__ = ( 

24 "PropertyDefinition", 

25 "PROPERTIES", 

26) 

27 

28from dataclasses import dataclass 

29from typing import Any, Callable, Optional, Tuple 

30 

31import astropy.coordinates 

32import astropy.time 

33import astropy.units 

34 

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. 

40 

41 

42def earthlocation_to_simple(location: astropy.coordinates.EarthLocation) -> Tuple[float, ...]: 

43 """Convert EarthLocation to tuple. 

44 

45 Parameters 

46 ---------- 

47 location : `astropy.coordinates.EarthLocation` 

48 The location to simplify. 

49 

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) 

57 

58 

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) 

62 

63 

64def datetime_to_simple(datetime: astropy.time.Time) -> Tuple[float, float]: 

65 """Convert Time to tuple. 

66 

67 Parameters 

68 ---------- 

69 datetime : `astropy.time.Time` 

70 The time to simplify. 

71 

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) 

79 

80 

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") 

84 

85 

86def exptime_to_simple(exptime: astropy.units.Quantity) -> float: 

87 """Convert exposure time Quantity to seconds.""" 

88 return exptime.to_value(astropy.units.s) 

89 

90 

91def simple_to_exptime(simple: float, **kwargs: Any) -> astropy.units.Quantity: 

92 """Convert simple form back to Quantity.""" 

93 return simple * astropy.units.s 

94 

95 

96def angle_to_simple(angle: astropy.coordinates.Angle) -> float: 

97 """Convert Angle to degrees.""" 

98 return angle.to_value(astropy.units.deg) 

99 

100 

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) 

104 

105 

106def temperature_to_simple(temp: astropy.units.Quantity) -> float: 

107 """Convert temperature to kelvin.""" 

108 return temp.to(astropy.units.K, equivalencies=astropy.units.temperature()).to_value() 

109 

110 

111def simple_to_temperature(simple: float, **kwargs: Any) -> astropy.units.Quantity: 

112 """Convert scalar kelvin value back to quantity.""" 

113 return simple * astropy.units.K 

114 

115 

116def pressure_to_simple(press: astropy.units.Quantity) -> float: 

117 """Convert pressure Quantity to hPa.""" 

118 return press.to_value(astropy.units.hPa) 

119 

120 

121def simple_to_pressure(simple: float, **kwargs: Any) -> astropy.units.Quantity: 

122 """Convert the pressure scalar back to Quantity.""" 

123 return simple * astropy.units.hPa 

124 

125 

126def skycoord_to_simple(skycoord: astropy.coordinates.SkyCoord) -> Tuple[float, float]: 

127 """Convert SkyCoord to ICRS RA/Dec tuple""" 

128 icrs = skycoord.icrs 

129 return (icrs.ra.to_value(astropy.units.deg), icrs.dec.to_value(astropy.units.deg)) 

130 

131 

132def simple_to_skycoord(simple: Tuple[float, float], **kwargs: Any) -> astropy.coordinates.SkyCoord: 

133 """Convert ICRS tuple to SkyCoord.""" 

134 return astropy.coordinates.SkyCoord(*simple, unit=astropy.units.deg) 

135 

136 

137def altaz_to_simple(altaz: astropy.coordinates.AltAz) -> Tuple[float, float]: 

138 """Convert AltAz to Alt/Az tuple. 

139 

140 Do not include obstime or location in simplification. It is assumed 

141 that those will be present from other properties. 

142 """ 

143 return (altaz.az.to_value(astropy.units.deg), altaz.alt.to_value(astropy.units.deg)) 

144 

145 

146def simple_to_altaz(simple: Tuple[float, float], **kwargs: Any) -> astropy.coordinates.AltAz: 

147 """Convert simple altaz tuple to AltAz. 

148 

149 Will look for location and datetime_begin in kwargs. 

150 """ 

151 location = kwargs.get("location") 

152 obstime = kwargs.get("datetime_begin") 

153 

154 return astropy.coordinates.AltAz( 

155 simple[0] * astropy.units.deg, simple[1] * astropy.units.deg, obstime=obstime, location=location 

156 ) 

157 

158 

159@dataclass 

160class PropertyDefinition: 

161 """Definition of an instrumental property.""" 

162 

163 doc: str 

164 """Docstring for the property.""" 

165 

166 str_type: str 

167 """Python type of property as a string (suitable for docstrings).""" 

168 

169 py_type: type 

170 """Actual python type.""" 

171 

172 to_simple: Optional[Callable[[Any], Any]] = None 

173 """Function to convert value to simple form (can be ``None``).""" 

174 

175 from_simple: Optional[Callable[[Any], Any]] = None 

176 """Function to convert from simple form back to required type (can be 

177 ``None``).""" 

178 

179 

180# Dict of properties to tuple where tuple is: 

181# - description of property 

182# - Python type of property as a string (suitable for docstrings) 

183# - Actual python type as a type 

184# - Simplification function (can be None) 

185# - Function to convert simple form back to required type (can be None) 

186PROPERTIES = { 

187 "telescope": PropertyDefinition("Full name of the telescope.", "str", str), 

188 "instrument": PropertyDefinition("The instrument used to observe the exposure.", "str", str), 

189 "location": PropertyDefinition( 

190 "Location of the observatory.", 

191 "astropy.coordinates.EarthLocation", 

192 astropy.coordinates.EarthLocation, 

193 earthlocation_to_simple, 

194 simple_to_earthlocation, 

195 ), 

196 "exposure_id": PropertyDefinition( 

197 "Unique (with instrument) integer identifier for this observation.", "int", int 

198 ), 

199 "visit_id": PropertyDefinition( 

200 """ID of the Visit this Exposure is associated with. 

201 

202Science observations should essentially always be 

203associated with a visit, but calibration observations 

204may not be.""", 

205 "int", 

206 int, 

207 ), 

208 "physical_filter": PropertyDefinition("The bandpass filter used for this observation.", "str", str), 

209 "datetime_begin": PropertyDefinition( 

210 "Time of the start of the observation.", 

211 "astropy.time.Time", 

212 astropy.time.Time, 

213 datetime_to_simple, 

214 simple_to_datetime, 

215 ), 

216 "datetime_end": PropertyDefinition( 

217 "Time of the end of the observation.", 

218 "astropy.time.Time", 

219 astropy.time.Time, 

220 datetime_to_simple, 

221 simple_to_datetime, 

222 ), 

223 "exposure_time": PropertyDefinition( 

224 "Duration of the exposure with shutter open (seconds).", 

225 "astropy.units.Quantity", 

226 astropy.units.Quantity, 

227 exptime_to_simple, 

228 simple_to_exptime, 

229 ), 

230 "dark_time": PropertyDefinition( 

231 "Duration of the exposure with shutter closed (seconds).", 

232 "astropy.units.Quantity", 

233 astropy.units.Quantity, 

234 exptime_to_simple, 

235 simple_to_exptime, 

236 ), 

237 "boresight_airmass": PropertyDefinition("Airmass of the boresight of the telescope.", "float", float), 

238 "boresight_rotation_angle": PropertyDefinition( 

239 "Angle of the instrument in boresight_rotation_coord frame.", 

240 "astropy.coordinates.Angle", 

241 astropy.coordinates.Angle, 

242 angle_to_simple, 

243 simple_to_angle, 

244 ), 

245 "boresight_rotation_coord": PropertyDefinition( 

246 "Coordinate frame of the instrument rotation angle (options: sky, unknown).", 

247 "str", 

248 str, 

249 ), 

250 "detector_num": PropertyDefinition( 

251 "Unique (for instrument) integer identifier for the sensor.", "int", int 

252 ), 

253 "detector_name": PropertyDefinition( 

254 "Name of the detector within the instrument (might not be unique if there are detector groups).", 

255 "str", 

256 str, 

257 ), 

258 "detector_unique_name": PropertyDefinition( 

259 ( 

260 "Unique name of the detector within the focal plane, generally combining detector_group with " 

261 "detector_name." 

262 ), 

263 "str", 

264 str, 

265 ), 

266 "detector_serial": PropertyDefinition("Serial number/string associated with this detector.", "str", str), 

267 "detector_group": PropertyDefinition( 

268 "Collection name of which this detector is a part. Can be None if there are no detector groupings.", 

269 "str", 

270 str, 

271 ), 

272 "detector_exposure_id": PropertyDefinition( 

273 "Unique integer identifier for this detector in this exposure.", 

274 "int", 

275 int, 

276 ), 

277 "object": PropertyDefinition("Object of interest or field name.", "str", str), 

278 "temperature": PropertyDefinition( 

279 "Temperature outside the dome.", 

280 "astropy.units.Quantity", 

281 astropy.units.Quantity, 

282 temperature_to_simple, 

283 simple_to_temperature, 

284 ), 

285 "pressure": PropertyDefinition( 

286 "Atmospheric pressure outside the dome.", 

287 "astropy.units.Quantity", 

288 astropy.units.Quantity, 

289 pressure_to_simple, 

290 simple_to_pressure, 

291 ), 

292 "relative_humidity": PropertyDefinition("Relative humidity outside the dome.", "float", float), 

293 "tracking_radec": PropertyDefinition( 

294 "Requested RA/Dec to track.", 

295 "astropy.coordinates.SkyCoord", 

296 astropy.coordinates.SkyCoord, 

297 skycoord_to_simple, 

298 simple_to_skycoord, 

299 ), 

300 "altaz_begin": PropertyDefinition( 

301 "Telescope boresight azimuth and elevation at start of observation.", 

302 "astropy.coordinates.AltAz", 

303 astropy.coordinates.AltAz, 

304 altaz_to_simple, 

305 simple_to_altaz, 

306 ), 

307 "science_program": PropertyDefinition("Observing program (survey or proposal) identifier.", "str", str), 

308 "observation_type": PropertyDefinition( 

309 "Type of observation (currently: science, dark, flat, bias, focus).", 

310 "str", 

311 str, 

312 ), 

313 "observation_id": PropertyDefinition( 

314 "Label uniquely identifying this observation (can be related to 'exposure_id').", 

315 "str", 

316 str, 

317 ), 

318 "observation_reason": PropertyDefinition( 

319 "Reason this observation was taken, or its purpose ('science' and 'calibration' are common values)", 

320 "str", 

321 str, 

322 ), 

323 "exposure_group": PropertyDefinition( 

324 "Label to use to associate this exposure with others (can be related to 'exposure_id').", 

325 "str", 

326 str, 

327 ), 

328 "observing_day": PropertyDefinition( 

329 "Integer in YYYYMMDD format corresponding to the day of observation.", "int", int 

330 ), 

331 "observation_counter": PropertyDefinition( 

332 ( 

333 "Counter of this observation. Can be counter within observing_day or a global counter. " 

334 "Likely to be observatory specific." 

335 ), 

336 "int", 

337 int, 

338 ), 

339 "has_simulated_content": PropertyDefinition( 

340 "Boolean indicating whether any part of this observation was simulated.", "bool", bool, None, None 

341 ), 

342 "group_counter_start": PropertyDefinition( 

343 "Observation counter for the start of the exposure group." 

344 "Depending on the instrument the relevant group may be " 

345 "visit_id or exposure_group.", 

346 "int", 

347 int, 

348 None, 

349 None, 

350 ), 

351 "group_counter_end": PropertyDefinition( 

352 "Observation counter for the end of the exposure group. " 

353 "Depending on the instrument the relevant group may be " 

354 "visit_id or exposure_group.", 

355 "int", 

356 int, 

357 None, 

358 None, 

359 ), 

360}