Coverage for python/lsst/obs/lsst/_instrument.py: 62%

122 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-11 03:03 -0700

1# This file is part of obs_lsst. 

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 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__ = ("LsstCam", "LsstCamImSim", "LsstCamPhoSim", "LsstTS8", 

23 "Latiss", "LsstTS3", "LsstUCDCam", "LsstComCam") 

24 

25import os.path 

26 

27import lsst.obs.base.yamlCamera as yamlCamera 

28from lsst.utils.introspection import get_full_type_name 

29from lsst.utils import getPackageDir 

30from lsst.obs.base import Instrument, VisitSystem 

31from .filters import (LSSTCAM_FILTER_DEFINITIONS, LATISS_FILTER_DEFINITIONS, 

32 LSSTCAM_IMSIM_FILTER_DEFINITIONS, TS3_FILTER_DEFINITIONS, 

33 TS8_FILTER_DEFINITIONS, COMCAM_FILTER_DEFINITIONS, 

34 GENERIC_FILTER_DEFINITIONS, 

35 ) 

36 

37from .translators import LatissTranslator, LsstCamTranslator, \ 

38 LsstUCDCamTranslator, LsstTS3Translator, LsstComCamTranslator, \ 

39 LsstCamPhoSimTranslator, LsstTS8Translator, LsstCamImSimTranslator 

40 

41PACKAGE_DIR = getPackageDir("obs_lsst") 

42 

43 

44class LsstCam(Instrument): 

45 """Gen3 Butler specialization for the LSST Main Camera. 

46 

47 Parameters 

48 ---------- 

49 camera : `lsst.cameraGeom.Camera` 

50 Camera object from which to extract detector information. 

51 filters : `list` of `FilterDefinition` 

52 An ordered list of filters to define the set of PhysicalFilters 

53 associated with this instrument in the registry. 

54 

55 While both the camera geometry and the set of filters associated with a 

56 camera are expected to change with time in general, their Butler Registry 

57 representations defined by an Instrument do not. Instead: 

58 

59 - We only extract names, IDs, and purposes from the detectors in the 

60 camera, which should be static information that actually reflects 

61 detector "slots" rather than the physical sensors themselves. Because 

62 the distinction between physical sensors and slots is unimportant in 

63 the vast majority of Butler use cases, we just use "detector" even 

64 though the concept really maps better to "detector slot". Ideally in 

65 the future this distinction between static and time-dependent 

66 information would be encoded in cameraGeom itself (e.g. by making the 

67 time-dependent Detector class inherit from a related class that only 

68 carries static content). 

69 

70 - The Butler Registry is expected to contain physical_filter entries for 

71 all filters an instrument has ever had, because we really only care 

72 about which filters were used for particular observations, not which 

73 filters were *available* at some point in the past. And changes in 

74 individual filters over time will be captured as changes in their 

75 TransmissionCurve datasets, not changes in the registry content (which 

76 is really just a label). While at present Instrument and Registry 

77 do not provide a way to add new physical_filters, they will in the 

78 future. 

79 """ 

80 filterDefinitions = LSSTCAM_FILTER_DEFINITIONS 

81 instrument = "LSSTCam" 

82 policyName = "lsstCam" 

83 translatorClass = LsstCamTranslator 

84 obsDataPackage = "obs_lsst_data" 

85 visitSystem = VisitSystem.BY_SEQ_START_END 

86 

87 @property 

88 def configPaths(self): 

89 return [os.path.join(PACKAGE_DIR, "config"), 

90 os.path.join(PACKAGE_DIR, "config", self.policyName)] 

91 

92 @classmethod 

93 def getName(cls): 

94 # Docstring inherited from Instrument.getName 

95 return cls.instrument 

96 

97 @classmethod 

98 def getCamera(cls): 

99 # Constructing a YAML camera takes a long time but we rely on 

100 # yamlCamera to cache for us. 

101 cameraYamlFile = os.path.join(PACKAGE_DIR, "policy", f"{cls.policyName}.yaml") 

102 camera = yamlCamera.makeCamera(cameraYamlFile) 

103 if camera.getName() != cls.getName(): 

104 raise RuntimeError(f"Expected to read camera geometry for {cls.instrument}" 

105 f" but instead got geometry for {camera.getName()}") 

106 return camera 

107 

108 def _make_default_dimension_packer( 

109 self, 

110 config_attr, 

111 data_id, 

112 is_exposure=None, 

113 default="rubin", 

114 ): 

115 # Docstring inherited from Instrument._make_default_dimension_packer. 

116 # Only difference is the change to default above. 

117 return super()._make_default_dimension_packer( 

118 config_attr, 

119 data_id, 

120 is_exposure=is_exposure, 

121 default=default, 

122 ) 

123 

124 def getRawFormatter(self, dataId): 

125 # Docstring inherited from Instrument.getRawFormatter 

126 # local import to prevent circular dependency 

127 from .rawFormatter import LsstCamRawFormatter 

128 return LsstCamRawFormatter 

129 

130 def register(self, registry, update=False): 

131 # Docstring inherited from Instrument.register 

132 # The maximum values below make Gen3's ObservationDataIdPacker produce 

133 # outputs that match Gen2's ccdExposureId. 

134 obsMax = self.translatorClass.max_exposure_id() 

135 # Make sure this is at least 1 to avoid non-uniqueness issues (e.g. 

136 # for data ids that also get used in indexing). 

137 detectorMax = max(self.translatorClass.DETECTOR_MAX, 1) 

138 with registry.transaction(): 

139 registry.syncDimensionData( 

140 "instrument", 

141 { 

142 "name": self.getName(), 

143 "detector_max": detectorMax, 

144 "visit_max": obsMax, 

145 "exposure_max": obsMax, 

146 "class_name": get_full_type_name(self), 

147 "visit_system": None if self.visitSystem is None else self.visitSystem.value, 

148 }, 

149 update=update 

150 ) 

151 for detector in self.getCamera(): 

152 registry.syncDimensionData("detector", self.extractDetectorRecord(detector), update=update) 

153 

154 self._registerFilters(registry, update=update) 

155 

156 def extractDetectorRecord(self, camGeomDetector): 

157 """Create a Gen3 Detector entry dict from a cameraGeom.Detector. 

158 """ 

159 # All of the LSST instruments have detector names like R??_S??; we'll 

160 # split them up here, and instruments with only one raft can override 

161 # to change the group to something else if desired. 

162 # Long-term, we should get these fields into cameraGeom separately 

163 # so there's no need to specialize at this stage. 

164 # They are separate in ObservationInfo 

165 group, name = camGeomDetector.getName().split("_") 

166 

167 # getType() returns a pybind11-wrapped enum, which unfortunately 

168 # has no way to extract the name of just the value (it's always 

169 # prefixed by the enum type name). 

170 purpose = str(camGeomDetector.getType()).split(".")[-1] 

171 

172 return dict( 

173 instrument=self.getName(), 

174 id=camGeomDetector.getId(), 

175 full_name=camGeomDetector.getName(), 

176 name_in_raft=name, 

177 purpose=purpose, 

178 raft=group, 

179 ) 

180 

181 

182class LsstComCam(LsstCam): 

183 """Gen3 Butler specialization for ComCam data. 

184 """ 

185 

186 filterDefinitions = COMCAM_FILTER_DEFINITIONS 

187 instrument = "LSSTComCam" 

188 policyName = "comCam" 

189 translatorClass = LsstComCamTranslator 

190 

191 def getRawFormatter(self, dataId): 

192 # local import to prevent circular dependency 

193 from .rawFormatter import LsstComCamRawFormatter 

194 return LsstComCamRawFormatter 

195 

196 

197class LsstCamImSim(LsstCam): 

198 """Gen3 Butler specialization for ImSim simulations. 

199 """ 

200 

201 instrument = "LSSTCam-imSim" 

202 policyName = "imsim" 

203 translatorClass = LsstCamImSimTranslator 

204 filterDefinitions = LSSTCAM_IMSIM_FILTER_DEFINITIONS 

205 visitSystem = VisitSystem.ONE_TO_ONE 

206 

207 def getRawFormatter(self, dataId): 

208 # local import to prevent circular dependency 

209 from .rawFormatter import LsstCamImSimRawFormatter 

210 return LsstCamImSimRawFormatter 

211 

212 def _make_default_dimension_packer( 

213 self, 

214 config_attr, 

215 data_id, 

216 is_exposure=None, 

217 default="observation", 

218 ): 

219 # Docstring inherited from Instrument._make_default_dimension_packer. 

220 # Only difference is the change to default above (which reverts back 

221 # the default in lsst.pipe.base.Instrument). 

222 return super()._make_default_dimension_packer( 

223 config_attr, 

224 data_id, 

225 is_exposure=is_exposure, 

226 default=default, 

227 ) 

228 

229 

230class LsstCamPhoSim(LsstCam): 

231 """Gen3 Butler specialization for Phosim simulations. 

232 """ 

233 

234 instrument = "LSSTCam-PhoSim" 

235 policyName = "phosim" 

236 translatorClass = LsstCamPhoSimTranslator 

237 filterDefinitions = GENERIC_FILTER_DEFINITIONS 

238 visitSystem = VisitSystem.ONE_TO_ONE 

239 

240 def getRawFormatter(self, dataId): 

241 # local import to prevent circular dependency 

242 from .rawFormatter import LsstCamPhoSimRawFormatter 

243 return LsstCamPhoSimRawFormatter 

244 

245 def _make_default_dimension_packer( 

246 self, 

247 config_attr, 

248 data_id, 

249 is_exposure=None, 

250 default="observation", 

251 ): 

252 # Docstring inherited from Instrument._make_default_dimension_packer. 

253 # Only difference is the change to default above (which reverts back 

254 # the default in lsst.pipe.base.Instrument). 

255 return super()._make_default_dimension_packer( 

256 config_attr, 

257 data_id, 

258 is_exposure=is_exposure, 

259 default=default, 

260 ) 

261 

262 

263class LsstTS8(LsstCam): 

264 """Gen3 Butler specialization for raft test stand data. 

265 """ 

266 

267 filterDefinitions = TS8_FILTER_DEFINITIONS 

268 instrument = "LSST-TS8" 

269 policyName = "ts8" 

270 translatorClass = LsstTS8Translator 

271 visitSystem = VisitSystem.ONE_TO_ONE 

272 

273 def getRawFormatter(self, dataId): 

274 # local import to prevent circular dependency 

275 from .rawFormatter import LsstTS8RawFormatter 

276 return LsstTS8RawFormatter 

277 

278 def _make_default_dimension_packer( 

279 self, 

280 config_attr, 

281 data_id, 

282 is_exposure=None, 

283 default="observation", 

284 ): 

285 # Docstring inherited from Instrument._make_default_dimension_packer. 

286 # Only difference is the change to default above (which reverts back 

287 # the default in lsst.pipe.base.Instrument). 

288 return super()._make_default_dimension_packer( 

289 config_attr, 

290 data_id, 

291 is_exposure=is_exposure, 

292 default=default, 

293 ) 

294 

295 

296class LsstUCDCam(LsstCam): 

297 """Gen3 Butler specialization for UCDCam test stand data. 

298 """ 

299 

300 instrument = "LSST-UCDCam" 

301 policyName = "ucd" 

302 translatorClass = LsstUCDCamTranslator 

303 filterDefinitions = GENERIC_FILTER_DEFINITIONS 

304 visitSystem = VisitSystem.ONE_TO_ONE 

305 

306 def getRawFormatter(self, dataId): 

307 # local import to prevent circular dependency 

308 from .rawFormatter import LsstUCDCamRawFormatter 

309 return LsstUCDCamRawFormatter 

310 

311 def _make_default_dimension_packer( 

312 self, 

313 config_attr, 

314 data_id, 

315 is_exposure=None, 

316 default="observation", 

317 ): 

318 # Docstring inherited from Instrument._make_default_dimension_packer. 

319 # Only difference is the change to default above (which reverts back 

320 # the default in lsst.pipe.base.Instrument). 

321 return super()._make_default_dimension_packer( 

322 config_attr, 

323 data_id, 

324 is_exposure=is_exposure, 

325 default=default, 

326 ) 

327 

328 

329class LsstTS3(LsstCam): 

330 """Gen3 Butler specialization for TS3 test stand data. 

331 """ 

332 

333 filterDefinitions = TS3_FILTER_DEFINITIONS 

334 instrument = "LSST-TS3" 

335 policyName = "ts3" 

336 translatorClass = LsstTS3Translator 

337 visitSystem = VisitSystem.ONE_TO_ONE 

338 

339 def getRawFormatter(self, dataId): 

340 # local import to prevent circular dependency 

341 from .rawFormatter import LsstTS3RawFormatter 

342 return LsstTS3RawFormatter 

343 

344 def _make_default_dimension_packer( 

345 self, 

346 config_attr, 

347 data_id, 

348 is_exposure=None, 

349 default="observation", 

350 ): 

351 # Docstring inherited from Instrument._make_default_dimension_packer. 

352 # Only difference is the change to default above (which reverts back 

353 # the default in lsst.pipe.base.Instrument). 

354 return super()._make_default_dimension_packer( 

355 config_attr, 

356 data_id, 

357 is_exposure=is_exposure, 

358 default=default, 

359 ) 

360 

361 

362class Latiss(LsstCam): 

363 """Gen3 Butler specialization for AuxTel LATISS data. 

364 """ 

365 filterDefinitions = LATISS_FILTER_DEFINITIONS 

366 instrument = "LATISS" 

367 policyName = "latiss" 

368 translatorClass = LatissTranslator 

369 

370 def extractDetectorRecord(self, camGeomDetector): 

371 # Override to remove group (raft) name, because LATISS only has one 

372 # detector. 

373 record = super().extractDetectorRecord(camGeomDetector) 

374 record["raft"] = None 

375 record["name_in_raft"] = record["full_name"] 

376 return record 

377 

378 def getRawFormatter(self, dataId): 

379 # local import to prevent circular dependency 

380 from .rawFormatter import LatissRawFormatter 

381 return LatissRawFormatter