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

120 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-19 05:51 -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 ) 

35 

36from .translators import LatissTranslator, LsstCamTranslator, \ 

37 LsstUCDCamTranslator, LsstTS3Translator, LsstComCamTranslator, \ 

38 LsstCamPhoSimTranslator, LsstTS8Translator, LsstCamImSimTranslator 

39 

40PACKAGE_DIR = getPackageDir("obs_lsst") 

41 

42 

43class LsstCam(Instrument): 

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

45 

46 Parameters 

47 ---------- 

48 camera : `lsst.cameraGeom.Camera` 

49 Camera object from which to extract detector information. 

50 filters : `list` of `FilterDefinition` 

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

52 associated with this instrument in the registry. 

53 

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

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

56 representations defined by an Instrument do not. Instead: 

57 

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

59 camera, which should be static information that actually reflects 

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

61 the distinction between physical sensors and slots is unimportant in 

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

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

64 the future this distinction between static and time-dependent 

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

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

67 carries static content). 

68 

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

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

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

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

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

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

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

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

77 future. 

78 """ 

79 filterDefinitions = LSSTCAM_FILTER_DEFINITIONS 

80 instrument = "LSSTCam" 

81 policyName = "lsstCam" 

82 translatorClass = LsstCamTranslator 

83 obsDataPackage = "obs_lsst_data" 

84 visitSystem = VisitSystem.BY_SEQ_START_END 

85 

86 @property 

87 def configPaths(self): 

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

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

90 

91 @classmethod 

92 def getName(cls): 

93 # Docstring inherited from Instrument.getName 

94 return cls.instrument 

95 

96 @classmethod 

97 def getCamera(cls): 

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

99 # yamlCamera to cache for us. 

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

101 camera = yamlCamera.makeCamera(cameraYamlFile) 

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

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

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

105 return camera 

106 

107 def _make_default_dimension_packer( 

108 self, 

109 config_attr, 

110 data_id, 

111 is_exposure=None, 

112 default="rubin", 

113 ): 

114 # Docstring inherited from Instrument._make_default_dimension_packer. 

115 # Only difference is the change to default above. 

116 return super()._make_default_dimension_packer( 

117 config_attr, 

118 data_id, 

119 is_exposure=is_exposure, 

120 default=default, 

121 ) 

122 

123 def getRawFormatter(self, dataId): 

124 # Docstring inherited from Instrument.getRawFormatter 

125 # local import to prevent circular dependency 

126 from .rawFormatter import LsstCamRawFormatter 

127 return LsstCamRawFormatter 

128 

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

130 # Docstring inherited from Instrument.register 

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

132 # outputs that match Gen2's ccdExposureId. 

133 obsMax = self.translatorClass.max_exposure_id() 

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

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

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

137 with registry.transaction(): 

138 registry.syncDimensionData( 

139 "instrument", 

140 { 

141 "name": self.getName(), 

142 "detector_max": detectorMax, 

143 "visit_max": obsMax, 

144 "exposure_max": obsMax, 

145 "class_name": get_full_type_name(self), 

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

147 }, 

148 update=update 

149 ) 

150 for detector in self.getCamera(): 

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

152 

153 self._registerFilters(registry, update=update) 

154 

155 def extractDetectorRecord(self, camGeomDetector): 

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

157 """ 

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

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

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

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

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

163 # They are separate in ObservationInfo 

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

165 

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

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

168 # prefixed by the enum type name). 

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

170 

171 return dict( 

172 instrument=self.getName(), 

173 id=camGeomDetector.getId(), 

174 full_name=camGeomDetector.getName(), 

175 name_in_raft=name, 

176 purpose=purpose, 

177 raft=group, 

178 ) 

179 

180 

181class LsstComCam(LsstCam): 

182 """Gen3 Butler specialization for ComCam data. 

183 """ 

184 

185 filterDefinitions = COMCAM_FILTER_DEFINITIONS 

186 instrument = "LSSTComCam" 

187 policyName = "comCam" 

188 translatorClass = LsstComCamTranslator 

189 

190 def getRawFormatter(self, dataId): 

191 # local import to prevent circular dependency 

192 from .rawFormatter import LsstComCamRawFormatter 

193 return LsstComCamRawFormatter 

194 

195 

196class LsstCamImSim(LsstCam): 

197 """Gen3 Butler specialization for ImSim simulations. 

198 """ 

199 

200 instrument = "LSSTCam-imSim" 

201 policyName = "imsim" 

202 translatorClass = LsstCamImSimTranslator 

203 filterDefinitions = LSSTCAM_IMSIM_FILTER_DEFINITIONS 

204 visitSystem = VisitSystem.ONE_TO_ONE 

205 

206 def getRawFormatter(self, dataId): 

207 # local import to prevent circular dependency 

208 from .rawFormatter import LsstCamImSimRawFormatter 

209 return LsstCamImSimRawFormatter 

210 

211 def _make_default_dimension_packer( 

212 self, 

213 config_attr, 

214 data_id, 

215 is_exposure=None, 

216 default="observation", 

217 ): 

218 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

221 return super()._make_default_dimension_packer( 

222 config_attr, 

223 data_id, 

224 is_exposure=is_exposure, 

225 default=default, 

226 ) 

227 

228 

229class LsstCamPhoSim(LsstCam): 

230 """Gen3 Butler specialization for Phosim simulations. 

231 """ 

232 

233 instrument = "LSSTCam-PhoSim" 

234 policyName = "phosim" 

235 translatorClass = LsstCamPhoSimTranslator 

236 visitSystem = VisitSystem.ONE_TO_ONE 

237 

238 def getRawFormatter(self, dataId): 

239 # local import to prevent circular dependency 

240 from .rawFormatter import LsstCamPhoSimRawFormatter 

241 return LsstCamPhoSimRawFormatter 

242 

243 def _make_default_dimension_packer( 

244 self, 

245 config_attr, 

246 data_id, 

247 is_exposure=None, 

248 default="observation", 

249 ): 

250 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

253 return super()._make_default_dimension_packer( 

254 config_attr, 

255 data_id, 

256 is_exposure=is_exposure, 

257 default=default, 

258 ) 

259 

260 

261class LsstTS8(LsstCam): 

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

263 """ 

264 

265 filterDefinitions = TS8_FILTER_DEFINITIONS 

266 instrument = "LSST-TS8" 

267 policyName = "ts8" 

268 translatorClass = LsstTS8Translator 

269 visitSystem = VisitSystem.ONE_TO_ONE 

270 

271 def getRawFormatter(self, dataId): 

272 # local import to prevent circular dependency 

273 from .rawFormatter import LsstTS8RawFormatter 

274 return LsstTS8RawFormatter 

275 

276 def _make_default_dimension_packer( 

277 self, 

278 config_attr, 

279 data_id, 

280 is_exposure=None, 

281 default="observation", 

282 ): 

283 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

286 return super()._make_default_dimension_packer( 

287 config_attr, 

288 data_id, 

289 is_exposure=is_exposure, 

290 default=default, 

291 ) 

292 

293 

294class LsstUCDCam(LsstCam): 

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

296 """ 

297 

298 instrument = "LSST-UCDCam" 

299 policyName = "ucd" 

300 translatorClass = LsstUCDCamTranslator 

301 visitSystem = VisitSystem.ONE_TO_ONE 

302 

303 def getRawFormatter(self, dataId): 

304 # local import to prevent circular dependency 

305 from .rawFormatter import LsstUCDCamRawFormatter 

306 return LsstUCDCamRawFormatter 

307 

308 def _make_default_dimension_packer( 

309 self, 

310 config_attr, 

311 data_id, 

312 is_exposure=None, 

313 default="observation", 

314 ): 

315 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

318 return super()._make_default_dimension_packer( 

319 config_attr, 

320 data_id, 

321 is_exposure=is_exposure, 

322 default=default, 

323 ) 

324 

325 

326class LsstTS3(LsstCam): 

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

328 """ 

329 

330 filterDefinitions = TS3_FILTER_DEFINITIONS 

331 instrument = "LSST-TS3" 

332 policyName = "ts3" 

333 translatorClass = LsstTS3Translator 

334 visitSystem = VisitSystem.ONE_TO_ONE 

335 

336 def getRawFormatter(self, dataId): 

337 # local import to prevent circular dependency 

338 from .rawFormatter import LsstTS3RawFormatter 

339 return LsstTS3RawFormatter 

340 

341 def _make_default_dimension_packer( 

342 self, 

343 config_attr, 

344 data_id, 

345 is_exposure=None, 

346 default="observation", 

347 ): 

348 # Docstring inherited from Instrument._make_default_dimension_packer. 

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

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

351 return super()._make_default_dimension_packer( 

352 config_attr, 

353 data_id, 

354 is_exposure=is_exposure, 

355 default=default, 

356 ) 

357 

358 

359class Latiss(LsstCam): 

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

361 """ 

362 filterDefinitions = LATISS_FILTER_DEFINITIONS 

363 instrument = "LATISS" 

364 policyName = "latiss" 

365 translatorClass = LatissTranslator 

366 

367 def extractDetectorRecord(self, camGeomDetector): 

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

369 # detector. 

370 record = super().extractDetectorRecord(camGeomDetector) 

371 record["raft"] = None 

372 record["name_in_raft"] = record["full_name"] 

373 return record 

374 

375 def getRawFormatter(self, dataId): 

376 # local import to prevent circular dependency 

377 from .rawFormatter import LatissRawFormatter 

378 return LatissRawFormatter