Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of obs_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22__all__ = ("Instrument", "makeExposureRecordFromObsInfo", "makeVisitRecordFromObsInfo", 

23 "addUnboundedCalibrationLabel") 

24 

25import os.path 

26from datetime import datetime 

27from abc import ABCMeta, abstractmethod 

28 

29 

30class Instrument(metaclass=ABCMeta): 

31 """Base class for instrument-specific logic for the Gen3 Butler. 

32 

33 Concrete instrument subclasses should be directly constructable with no 

34 arguments. 

35 """ 

36 

37 configPaths = [] 

38 """Paths to config files to read for specific Tasks. 

39 

40 The paths in this list should contain files of the form `task.py`, for 

41 each of the Tasks that requires special configuration. 

42 """ 

43 

44 @property 

45 @abstractmethod 

46 def filterDefinitions(self): 

47 """`~lsst.obs.base.FilterDefinitionCollection`, defining the filters 

48 for this instrument. 

49 """ 

50 return None 

51 

52 def __init__(self, *args, **kwargs): 

53 self.filterDefinitions.reset() 

54 self.filterDefinitions.defineFilters() 

55 

56 @classmethod 

57 @abstractmethod 

58 def getName(cls): 

59 raise NotImplementedError() 

60 

61 @abstractmethod 

62 def getCamera(self): 

63 """Retrieve the cameraGeom representation of this instrument. 

64 

65 This is a temporary API that should go away once obs_ packages have 

66 a standardized approach to writing versioned cameras to a Gen3 repo. 

67 """ 

68 raise NotImplementedError() 

69 

70 @abstractmethod 

71 def register(self, registry): 

72 """Insert instrument, physical_filter, and detector entries into a 

73 `Registry`. 

74 """ 

75 raise NotImplementedError() 

76 

77 def _registerFilters(self, registry): 

78 """Register the physical and abstract filter Dimension relationships. 

79 This should be called in the ``register`` implementation. 

80 

81 Parameters 

82 ---------- 

83 registry : `lsst.daf.butler.core.Registry` 

84 The registry to add dimensions to. 

85 """ 

86 for filter in self.filterDefinitions: 

87 # fix for undefined abstract filters causing trouble in the registry: 

88 if filter.abstract_filter is None: 

89 abstract_filter = filter.physical_filter 

90 else: 

91 abstract_filter = filter.abstract_filter 

92 

93 registry.insertDimensionData("physical_filter", 

94 {"instrument": self.getName(), 

95 "name": filter.physical_filter, 

96 "abstract_filter": abstract_filter 

97 }) 

98 

99 @abstractmethod 

100 def getRawFormatter(self, dataId): 

101 """Return the Formatter class that should be used to read a particular 

102 raw file. 

103 

104 Parameters 

105 ---------- 

106 dataId : `DataCoordinate` 

107 Dimension-based ID for the raw file or files being ingested. 

108 

109 Returns 

110 ------- 

111 formatter : `Formatter` class 

112 Class to be used that reads the file into an 

113 `lsst.afw.image.Exposure` instance. 

114 """ 

115 raise NotImplementedError() 

116 

117 @abstractmethod 

118 def writeCuratedCalibrations(self, butler): 

119 """Write human-curated calibration Datasets to the given Butler with 

120 the appropriate validity ranges. 

121 

122 This is a temporary API that should go away once obs_ packages have 

123 a standardized approach to this problem. 

124 """ 

125 raise NotImplementedError() 

126 

127 def applyConfigOverrides(self, name, config): 

128 """Apply instrument-specific overrides for a task config. 

129 

130 Parameters 

131 ---------- 

132 name : `str` 

133 Name of the object being configured; typically the _DefaultName 

134 of a Task. 

135 config : `lsst.pex.config.Config` 

136 Config instance to which overrides should be applied. 

137 """ 

138 for root in self.configPaths: 

139 path = os.path.join(root, f"{name}.py") 

140 if os.path.exists(path): 

141 config.load(path) 

142 

143 

144def makeExposureRecordFromObsInfo(obsInfo, universe): 

145 """Construct an exposure DimensionRecord from 

146 `astro_metadata_translator.ObservationInfo`. 

147 

148 Parameters 

149 ---------- 

150 obsInfo : `astro_metadata_translator.ObservationInfo` 

151 A `~astro_metadata_translator.ObservationInfo` object corresponding to 

152 the exposure. 

153 universe : `DimensionUniverse` 

154 Set of all known dimensions. 

155 

156 Returns 

157 ------- 

158 record : `DimensionRecord` 

159 A record containing exposure metadata, suitable for insertion into 

160 a `Registry`. 

161 """ 

162 dimension = universe["exposure"] 

163 return dimension.RecordClass.fromDict({ 

164 "instrument": obsInfo.instrument, 

165 "id": obsInfo.exposure_id, 

166 "name": obsInfo.observation_id, 

167 "datetime_begin": obsInfo.datetime_begin.to_datetime(), 

168 "datetime_end": obsInfo.datetime_end.to_datetime(), 

169 "exposure_time": obsInfo.exposure_time.to_value("s"), 

170 "dark_time": obsInfo.dark_time.to_value("s"), 

171 "observation_type": obsInfo.observation_type, 

172 "physical_filter": obsInfo.physical_filter, 

173 "visit": obsInfo.visit_id, 

174 }) 

175 

176 

177def makeVisitRecordFromObsInfo(obsInfo, universe, *, region=None): 

178 """Construct a visit `DimensionRecord` from 

179 `astro_metadata_translator.ObservationInfo`. 

180 

181 Parameters 

182 ---------- 

183 obsInfo : `astro_metadata_translator.ObservationInfo` 

184 A `~astro_metadata_translator.ObservationInfo` object corresponding to 

185 the exposure. 

186 universe : `DimensionUniverse` 

187 Set of all known dimensions. 

188 region : `lsst.sphgeom.Region`, optional 

189 Spatial region for the visit. 

190 

191 Returns 

192 ------- 

193 record : `DimensionRecord` 

194 A record containing visit metadata, suitable for insertion into a 

195 `Registry`. 

196 """ 

197 dimension = universe["visit"] 

198 return dimension.RecordClass.fromDict({ 

199 "instrument": obsInfo.instrument, 

200 "id": obsInfo.visit_id, 

201 "name": obsInfo.observation_id, 

202 "datetime_begin": obsInfo.datetime_begin.to_datetime(), 

203 "datetime_end": obsInfo.datetime_end.to_datetime(), 

204 "exposure_time": obsInfo.exposure_time.to_value("s"), 

205 "physical_filter": obsInfo.physical_filter, 

206 "region": region, 

207 }) 

208 

209 

210def addUnboundedCalibrationLabel(registry, instrumentName): 

211 """Add a special 'unbounded' calibration_label dimension entry for the 

212 given camera that is valid for any exposure. 

213 

214 If such an entry already exists, this function just returns a `DataId` 

215 for the existing entry. 

216 

217 Parameters 

218 ---------- 

219 registry : `Registry` 

220 Registry object in which to insert the dimension entry. 

221 instrumentName : `str` 

222 Name of the instrument this calibration label is associated with. 

223 

224 Returns 

225 ------- 

226 dataId : `DataId` 

227 New or existing data ID for the unbounded calibration. 

228 """ 

229 d = dict(instrument=instrumentName, calibration_label="unbounded") 

230 try: 

231 return registry.expandDataId(d) 

232 except LookupError: 

233 pass 

234 entry = d.copy() 

235 entry["datetime_begin"] = datetime.min 

236 entry["datetime_end"] = datetime.max 

237 registry.insertDimensionData("calibration_label", entry) 

238 return registry.expandDataId(d)