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

62 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-22 03:09 -0800

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 focusz_to_simple(focusz: astropy.units.Quantity) -> float: 

107 """Convert focusz to meters.""" 

108 return focusz.to_value(astropy.units.m) 

109 

110 

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

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

113 return simple * astropy.units.m 

114 

115 

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

119 

120 

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 

124 

125 

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

127 """Convert pressure Quantity to hPa.""" 

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

129 

130 

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 

134 

135 

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

140 

141 

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) 

145 

146 

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

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

149 

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

154 

155 

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

157 """Convert simple altaz tuple to AltAz. 

158 

159 Will look for location and datetime_begin in kwargs. 

160 """ 

161 location = kwargs.get("location") 

162 obstime = kwargs.get("datetime_begin") 

163 

164 return astropy.coordinates.AltAz( 

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

166 ) 

167 

168 

169@dataclass 

170class PropertyDefinition: 

171 """Definition of an instrumental property.""" 

172 

173 doc: str 

174 """Docstring for the property.""" 

175 

176 str_type: str 

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

178 

179 py_type: type 

180 """Actual python type.""" 

181 

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

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

184 

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

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

187 ``None``).""" 

188 

189 

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. 

211 

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}