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 abc import ABCMeta, abstractmethod 

27 

28from lsst.daf.butler import TIMESPAN_MIN, TIMESPAN_MAX 

29 

30 

31class Instrument(metaclass=ABCMeta): 

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

33 

34 Concrete instrument subclasses should be directly constructable with no 

35 arguments. 

36 """ 

37 

38 configPaths = [] 

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

40 

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

42 each of the Tasks that requires special configuration. 

43 """ 

44 

45 @property 

46 @abstractmethod 

47 def filterDefinitions(self): 

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

49 for this instrument. 

50 """ 

51 return None 

52 

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

54 self.filterDefinitions.reset() 

55 self.filterDefinitions.defineFilters() 

56 

57 @classmethod 

58 @abstractmethod 

59 def getName(cls): 

60 raise NotImplementedError() 

61 

62 @abstractmethod 

63 def getCamera(self): 

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

65 

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

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

68 """ 

69 raise NotImplementedError() 

70 

71 @abstractmethod 

72 def register(self, registry): 

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

74 `Registry`. 

75 """ 

76 raise NotImplementedError() 

77 

78 def _registerFilters(self, registry): 

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

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

81 

82 Parameters 

83 ---------- 

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

85 The registry to add dimensions to. 

86 """ 

87 for filter in self.filterDefinitions: 

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

89 if filter.abstract_filter is None: 

90 abstract_filter = filter.physical_filter 

91 else: 

92 abstract_filter = filter.abstract_filter 

93 

94 registry.insertDimensionData("physical_filter", 

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

96 "name": filter.physical_filter, 

97 "abstract_filter": abstract_filter 

98 }) 

99 

100 @abstractmethod 

101 def getRawFormatter(self, dataId): 

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

103 raw file. 

104 

105 Parameters 

106 ---------- 

107 dataId : `DataCoordinate` 

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

109 

110 Returns 

111 ------- 

112 formatter : `Formatter` class 

113 Class to be used that reads the file into an 

114 `lsst.afw.image.Exposure` instance. 

115 """ 

116 raise NotImplementedError() 

117 

118 @abstractmethod 

119 def writeCuratedCalibrations(self, butler): 

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

121 the appropriate validity ranges. 

122 

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

124 a standardized approach to this problem. 

125 """ 

126 raise NotImplementedError() 

127 

128 def applyConfigOverrides(self, name, config): 

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

130 

131 Parameters 

132 ---------- 

133 name : `str` 

134 Name of the object being configured; typically the _DefaultName 

135 of a Task. 

136 config : `lsst.pex.config.Config` 

137 Config instance to which overrides should be applied. 

138 """ 

139 for root in self.configPaths: 

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

141 if os.path.exists(path): 

142 config.load(path) 

143 

144 

145def makeExposureRecordFromObsInfo(obsInfo, universe): 

146 """Construct an exposure DimensionRecord from 

147 `astro_metadata_translator.ObservationInfo`. 

148 

149 Parameters 

150 ---------- 

151 obsInfo : `astro_metadata_translator.ObservationInfo` 

152 A `~astro_metadata_translator.ObservationInfo` object corresponding to 

153 the exposure. 

154 universe : `DimensionUniverse` 

155 Set of all known dimensions. 

156 

157 Returns 

158 ------- 

159 record : `DimensionRecord` 

160 A record containing exposure metadata, suitable for insertion into 

161 a `Registry`. 

162 """ 

163 dimension = universe["exposure"] 

164 return dimension.RecordClass.fromDict({ 

165 "instrument": obsInfo.instrument, 

166 "id": obsInfo.exposure_id, 

167 "name": obsInfo.observation_id, 

168 "group": obsInfo.exposure_group, 

169 "datetime_begin": obsInfo.datetime_begin, 

170 "datetime_end": obsInfo.datetime_end, 

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

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

173 "observation_type": obsInfo.observation_type, 

174 "physical_filter": obsInfo.physical_filter, 

175 "visit": obsInfo.visit_id, 

176 }) 

177 

178 

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

180 """Construct a visit `DimensionRecord` from 

181 `astro_metadata_translator.ObservationInfo`. 

182 

183 Parameters 

184 ---------- 

185 obsInfo : `astro_metadata_translator.ObservationInfo` 

186 A `~astro_metadata_translator.ObservationInfo` object corresponding to 

187 the exposure. 

188 universe : `DimensionUniverse` 

189 Set of all known dimensions. 

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

191 Spatial region for the visit. 

192 

193 Returns 

194 ------- 

195 record : `DimensionRecord` 

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

197 `Registry`. 

198 """ 

199 dimension = universe["visit"] 

200 return dimension.RecordClass.fromDict({ 

201 "instrument": obsInfo.instrument, 

202 "id": obsInfo.visit_id, 

203 "name": obsInfo.observation_id, 

204 "datetime_begin": obsInfo.datetime_begin, 

205 "datetime_end": obsInfo.datetime_end, 

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

207 "physical_filter": obsInfo.physical_filter, 

208 "region": region, 

209 }) 

210 

211 

212def addUnboundedCalibrationLabel(registry, instrumentName): 

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

214 given camera that is valid for any exposure. 

215 

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

217 for the existing entry. 

218 

219 Parameters 

220 ---------- 

221 registry : `Registry` 

222 Registry object in which to insert the dimension entry. 

223 instrumentName : `str` 

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

225 

226 Returns 

227 ------- 

228 dataId : `DataId` 

229 New or existing data ID for the unbounded calibration. 

230 """ 

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

232 try: 

233 return registry.expandDataId(d) 

234 except LookupError: 

235 pass 

236 entry = d.copy() 

237 entry["datetime_begin"] = TIMESPAN_MIN 

238 entry["datetime_end"] = TIMESPAN_MAX 

239 registry.insertDimensionData("calibration_label", entry) 

240 return registry.expandDataId(d)