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

63 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-20 10:39 +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. 

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 collections.abc import Callable 

29from dataclasses import dataclass 

30from typing import Any 

31 

32import astropy.coordinates 

33import astropy.time 

34import astropy.units 

35 

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. 

41 

42 

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

44 """Convert EarthLocation to tuple. 

45 

46 Parameters 

47 ---------- 

48 location : `astropy.coordinates.EarthLocation` 

49 The location to simplify. 

50 

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) 

58 

59 

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) 

63 

64 

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

66 """Convert Time to tuple. 

67 

68 Parameters 

69 ---------- 

70 datetime : `astropy.time.Time` 

71 The time to simplify. 

72 

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) 

80 

81 

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

85 

86 

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

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

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

90 

91 

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

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

94 return simple * astropy.units.s 

95 

96 

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

98 """Convert Angle to degrees.""" 

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

100 

101 

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) 

105 

106 

107def focusz_to_simple(focusz: astropy.units.Quantity) -> float: 

108 """Convert focusz to meters.""" 

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

110 

111 

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

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

114 return simple * astropy.units.m 

115 

116 

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

120 

121 

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 

125 

126 

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

128 """Convert pressure Quantity to hPa.""" 

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

130 

131 

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 

135 

136 

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

141 

142 

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) 

146 

147 

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

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

150 

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

155 

156 

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

158 """Convert simple altaz tuple to AltAz. 

159 

160 Will look for location and datetime_begin in kwargs. 

161 """ 

162 location = kwargs.get("location") 

163 obstime = kwargs.get("datetime_begin") 

164 

165 return astropy.coordinates.AltAz( 

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

167 ) 

168 

169 

170@dataclass 

171class PropertyDefinition: 

172 """Definition of an instrumental property.""" 

173 

174 doc: str 

175 """Docstring for the property.""" 

176 

177 str_type: str 

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

179 

180 py_type: type 

181 """Actual python type.""" 

182 

183 to_simple: Callable[[Any], Any] | None = None 

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

185 

186 from_simple: Callable[[Any], Any] | None = None 

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

188 ``None``).""" 

189 

190 

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. 

212 

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}