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

130 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-14 10:44 +0000

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

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, UCD_FILTER_DEFINITIONS, 

35 ) 

36 

37from .translators import LatissTranslator, LsstCamTranslator, \ 

38 LsstUCDCamTranslator, LsstTS3Translator, LsstComCamTranslator, \ 

39 LsstCamPhoSimTranslator, LsstTS8Translator, LsstCamImSimTranslator, \ 

40 LsstComCamSimTranslator 

41 

42PACKAGE_DIR = getPackageDir("obs_lsst") 

43 

44 

45class LsstCam(Instrument): 

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

47 

48 Parameters 

49 ---------- 

50 camera : `lsst.cameraGeom.Camera` 

51 Camera object from which to extract detector information. 

52 filters : `list` of `FilterDefinition` 

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

54 associated with this instrument in the registry. 

55 

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

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

58 representations defined by an Instrument do not. Instead: 

59 

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

61 camera, which should be static information that actually reflects 

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

63 the distinction between physical sensors and slots is unimportant in 

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

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

66 the future this distinction between static and time-dependent 

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

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

69 carries static content). 

70 

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

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

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

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

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

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

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

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

79 future. 

80 """ 

81 filterDefinitions = LSSTCAM_FILTER_DEFINITIONS 

82 instrument = "LSSTCam" 

83 policyName = "lsstCam" 

84 translatorClass = LsstCamTranslator 

85 obsDataPackage = "obs_lsst_data" 

86 visitSystem = VisitSystem.BY_SEQ_START_END 

87 

88 @property 

89 def configPaths(self): 

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

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

92 

93 @classmethod 

94 def getName(cls): 

95 # Docstring inherited from Instrument.getName 

96 return cls.instrument 

97 

98 @classmethod 

99 def getCamera(cls): 

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

101 # yamlCamera to cache for us. 

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

103 camera = yamlCamera.makeCamera(cameraYamlFile) 

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

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

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

107 return camera 

108 

109 def _make_default_dimension_packer( 

110 self, 

111 config_attr, 

112 data_id, 

113 is_exposure=None, 

114 default="rubin", 

115 ): 

116 # Docstring inherited from Instrument._make_default_dimension_packer. 

117 # Only difference is the change to default above. 

118 return super()._make_default_dimension_packer( 

119 config_attr, 

120 data_id, 

121 is_exposure=is_exposure, 

122 default=default, 

123 ) 

124 

125 def getRawFormatter(self, dataId): 

126 # Docstring inherited from Instrument.getRawFormatter 

127 # local import to prevent circular dependency 

128 from .rawFormatter import LsstCamRawFormatter 

129 return LsstCamRawFormatter 

130 

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

132 # Docstring inherited from Instrument.register 

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

134 # outputs that match Gen2's ccdExposureId. 

135 obsMax = self.translatorClass.max_exposure_id() 

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

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

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

139 with registry.transaction(): 

140 registry.syncDimensionData( 

141 "instrument", 

142 { 

143 "name": self.getName(), 

144 "detector_max": detectorMax, 

145 "visit_max": obsMax, 

146 "exposure_max": obsMax, 

147 "class_name": get_full_type_name(self), 

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

149 }, 

150 update=update 

151 ) 

152 for detector in self.getCamera(): 

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

154 

155 self._registerFilters(registry, update=update) 

156 

157 def extractDetectorRecord(self, camGeomDetector): 

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

159 """ 

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

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

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

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

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

165 # They are separate in ObservationInfo 

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

167 

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

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

170 # prefixed by the enum type name). 

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

172 

173 return dict( 

174 instrument=self.getName(), 

175 id=camGeomDetector.getId(), 

176 full_name=camGeomDetector.getName(), 

177 name_in_raft=name, 

178 purpose=purpose, 

179 raft=group, 

180 ) 

181 

182 

183class LsstComCam(LsstCam): 

184 """Gen3 Butler specialization for ComCam data. 

185 """ 

186 

187 filterDefinitions = COMCAM_FILTER_DEFINITIONS 

188 instrument = "LSSTComCam" 

189 policyName = "comCam" 

190 translatorClass = LsstComCamTranslator 

191 

192 def getRawFormatter(self, dataId): 

193 # local import to prevent circular dependency 

194 from .rawFormatter import LsstComCamRawFormatter 

195 return LsstComCamRawFormatter 

196 

197 

198class LsstComCamSim(LsstCam): 

199 """Gen3 Butler specialization for ComCamSim data. 

200 """ 

201 

202 filterDefinitions = COMCAM_FILTER_DEFINITIONS 

203 instrument = "LSSTComCamSim" 

204 policyName = "comCamSim" 

205 translatorClass = LsstComCamSimTranslator 

206 

207 def getRawFormatter(self, dataId): 

208 # local import to prevent circular dependency 

209 from .rawFormatter import LsstComCamSimRawFormatter 

210 return LsstComCamSimRawFormatter 

211 

212 

213class LsstCamImSim(LsstCam): 

214 """Gen3 Butler specialization for ImSim simulations. 

215 """ 

216 

217 instrument = "LSSTCam-imSim" 

218 policyName = "imsim" 

219 translatorClass = LsstCamImSimTranslator 

220 filterDefinitions = LSSTCAM_IMSIM_FILTER_DEFINITIONS 

221 visitSystem = VisitSystem.ONE_TO_ONE 

222 

223 def getRawFormatter(self, dataId): 

224 # local import to prevent circular dependency 

225 from .rawFormatter import LsstCamImSimRawFormatter 

226 return LsstCamImSimRawFormatter 

227 

228 def _make_default_dimension_packer( 

229 self, 

230 config_attr, 

231 data_id, 

232 is_exposure=None, 

233 default="observation", 

234 ): 

235 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

238 return super()._make_default_dimension_packer( 

239 config_attr, 

240 data_id, 

241 is_exposure=is_exposure, 

242 default=default, 

243 ) 

244 

245 

246class LsstCamPhoSim(LsstCam): 

247 """Gen3 Butler specialization for Phosim simulations. 

248 """ 

249 

250 instrument = "LSSTCam-PhoSim" 

251 policyName = "phosim" 

252 translatorClass = LsstCamPhoSimTranslator 

253 filterDefinitions = GENERIC_FILTER_DEFINITIONS 

254 visitSystem = VisitSystem.ONE_TO_ONE 

255 

256 def getRawFormatter(self, dataId): 

257 # local import to prevent circular dependency 

258 from .rawFormatter import LsstCamPhoSimRawFormatter 

259 return LsstCamPhoSimRawFormatter 

260 

261 def _make_default_dimension_packer( 

262 self, 

263 config_attr, 

264 data_id, 

265 is_exposure=None, 

266 default="observation", 

267 ): 

268 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

271 return super()._make_default_dimension_packer( 

272 config_attr, 

273 data_id, 

274 is_exposure=is_exposure, 

275 default=default, 

276 ) 

277 

278 

279class LsstTS8(LsstCam): 

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

281 """ 

282 

283 filterDefinitions = TS8_FILTER_DEFINITIONS 

284 instrument = "LSST-TS8" 

285 policyName = "ts8" 

286 translatorClass = LsstTS8Translator 

287 visitSystem = VisitSystem.ONE_TO_ONE 

288 

289 def getRawFormatter(self, dataId): 

290 # local import to prevent circular dependency 

291 from .rawFormatter import LsstTS8RawFormatter 

292 return LsstTS8RawFormatter 

293 

294 def _make_default_dimension_packer( 

295 self, 

296 config_attr, 

297 data_id, 

298 is_exposure=None, 

299 default="observation", 

300 ): 

301 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

304 return super()._make_default_dimension_packer( 

305 config_attr, 

306 data_id, 

307 is_exposure=is_exposure, 

308 default=default, 

309 ) 

310 

311 

312class LsstUCDCam(LsstCam): 

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

314 """ 

315 filterDefinitions = UCD_FILTER_DEFINITIONS 

316 instrument = "LSST-UCDCam" 

317 policyName = "ucd" 

318 translatorClass = LsstUCDCamTranslator 

319 visitSystem = VisitSystem.ONE_TO_ONE 

320 

321 def getRawFormatter(self, dataId): 

322 # local import to prevent circular dependency 

323 from .rawFormatter import LsstUCDCamRawFormatter 

324 return LsstUCDCamRawFormatter 

325 

326 def _make_default_dimension_packer( 

327 self, 

328 config_attr, 

329 data_id, 

330 is_exposure=None, 

331 default="observation", 

332 ): 

333 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

336 return super()._make_default_dimension_packer( 

337 config_attr, 

338 data_id, 

339 is_exposure=is_exposure, 

340 default=default, 

341 ) 

342 

343 

344class LsstTS3(LsstCam): 

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

346 """ 

347 

348 filterDefinitions = TS3_FILTER_DEFINITIONS 

349 instrument = "LSST-TS3" 

350 policyName = "ts3" 

351 translatorClass = LsstTS3Translator 

352 visitSystem = VisitSystem.ONE_TO_ONE 

353 

354 def getRawFormatter(self, dataId): 

355 # local import to prevent circular dependency 

356 from .rawFormatter import LsstTS3RawFormatter 

357 return LsstTS3RawFormatter 

358 

359 def _make_default_dimension_packer( 

360 self, 

361 config_attr, 

362 data_id, 

363 is_exposure=None, 

364 default="observation", 

365 ): 

366 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

369 return super()._make_default_dimension_packer( 

370 config_attr, 

371 data_id, 

372 is_exposure=is_exposure, 

373 default=default, 

374 ) 

375 

376 

377class Latiss(LsstCam): 

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

379 """ 

380 filterDefinitions = LATISS_FILTER_DEFINITIONS 

381 instrument = "LATISS" 

382 policyName = "latiss" 

383 translatorClass = LatissTranslator 

384 

385 def extractDetectorRecord(self, camGeomDetector): 

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

387 # detector. 

388 record = super().extractDetectorRecord(camGeomDetector) 

389 record["raft"] = None 

390 record["name_in_raft"] = record["full_name"] 

391 return record 

392 

393 def getRawFormatter(self, dataId): 

394 # local import to prevent circular dependency 

395 from .rawFormatter import LatissRawFormatter 

396 return LatissRawFormatter