Coverage for python/lsst/cp/pipe/cpSkyTask.py: 56%

Shortcuts 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

99 statements  

1# This file is part of cp_pipe. 

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/>. 

21import numpy as np 

22 

23import lsst.pex.config as pexConfig 

24import lsst.pipe.base as pipeBase 

25import lsst.pipe.base.connectionTypes as cT 

26 

27from lsst.pipe.drivers.background import (FocalPlaneBackground, MaskObjectsTask, SkyMeasurementTask, 

28 FocalPlaneBackgroundConfig) 

29from lsst.daf.base import PropertyList 

30from ._lookupStaticCalibration import lookupStaticCalibration 

31from .cpCombine import CalibCombineTask 

32 

33__all__ = ['CpSkyImageTask', 'CpSkyImageConfig', 

34 'CpSkyScaleMeasureTask', 'CpSkyScaleMeasureConfig', 

35 'CpSkySubtractBackgroundTask', 'CpSkySubtractBackgroundConfig', 

36 'CpSkyCombineTask', 'CpSkyCombineConfig'] 

37 

38 

39class CpSkyImageConnections(pipeBase.PipelineTaskConnections, 

40 dimensions=("instrument", "physical_filter", "exposure", "detector")): 

41 inputExp = cT.Input( 

42 name="cpSkyIsr", 

43 doc="Input pre-processed exposures to combine.", 

44 storageClass="Exposure", 

45 dimensions=("instrument", "exposure", "detector"), 

46 ) 

47 camera = cT.PrerequisiteInput( 

48 name="camera", 

49 doc="Input camera to use for geometry.", 

50 storageClass="Camera", 

51 dimensions=("instrument",), 

52 lookupFunction=lookupStaticCalibration, 

53 isCalibration=True, 

54 ) 

55 

56 maskedExp = cT.Output( 

57 name="cpSkyMaskedIsr", 

58 doc="Output masked post-ISR exposure.", 

59 storageClass="Exposure", 

60 dimensions=("instrument", "exposure", "detector"), 

61 ) 

62 maskedBkg = cT.Output( 

63 name="cpSkyDetectorBackground", 

64 doc="Initial background model from one image.", 

65 storageClass="FocalPlaneBackground", 

66 dimensions=("instrument", "exposure", "detector"), 

67 ) 

68 

69 

70class CpSkyImageConfig(pipeBase.PipelineTaskConfig, 

71 pipelineConnections=CpSkyImageConnections): 

72 maskTask = pexConfig.ConfigurableField( 

73 target=MaskObjectsTask, 

74 doc="Object masker to use.", 

75 ) 

76 

77 maskThresh = pexConfig.Field( 

78 dtype=float, 

79 default=3.0, 

80 doc="k-sigma threshold for masking pixels.", 

81 ) 

82 maskList = pexConfig.ListField( 

83 dtype=str, 

84 default=["DETECTED", "BAD", "NO_DATA", "SAT"], 

85 doc="Mask planes to reject.", 

86 ) 

87 

88 largeScaleBackground = pexConfig.ConfigField( 

89 dtype=FocalPlaneBackgroundConfig, 

90 doc="Large-scale background configuration.", 

91 ) 

92 

93 def setDefaults(self): 

94 # These values correspond to the HSC recommendation. As noted 

95 # below, the sizes are in millimeters, and correspond to an 

96 # background image 8192x8192 pixels (8192*0.015=122.88). 

97 self.largeScaleBackground.xSize = 122.88 # in mm 

98 self.largeScaleBackground.ySize = 122.88 # in mm 

99 self.largeScaleBackground.pixelSize = 0.015 # in mm per pixel 

100 self.largeScaleBackground.minFrac = 0.1 

101 self.largeScaleBackground.mask = ['BAD', 'SAT', 'INTRP', 'DETECTED', 'DETECTED_NEGATIVE', 

102 'EDGE', 'NO_DATA'] 

103 

104 

105class CpSkyImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

106 """Mask the detections on the postISRCCD. 

107 

108 This task maps the MaskObjectsTask across all of the initial ISR 

109 processed cpSkyIsr images to create cpSkyMaskedIsr products for 

110 all (exposure, detector) values. 

111 """ 

112 

113 ConfigClass = CpSkyImageConfig 

114 _DefaultName = "CpSkyImage" 

115 

116 def __init__(self, **kwargs): 

117 super().__init__(**kwargs) 

118 self.makeSubtask("maskTask") 

119 

120 def run(self, inputExp, camera): 

121 """Mask the detections on the postISRCCD. 

122 

123 Parameters 

124 ---------- 

125 inputExp : `lsst.afw.image.Exposure` 

126 An ISR processed exposure that will have detections 

127 masked. 

128 camera : `lsst.afw.cameraGeom.Camera` 

129 The camera geometry for this exposure. This is needed to 

130 create the background model. 

131 

132 Returns 

133 ------- 

134 results : `lsst.pipe.base.Struct` 

135 The results struct containing: 

136 

137 ``maskedExp`` : `lsst.afw.image.Exposure` 

138 The detection-masked version of the ``inputExp``. 

139 ``maskedBkg`` : `lsst.pipe.drivers.FocalPlaneBackground.` 

140 The partial focal plane background containing only 

141 this exposure/detector's worth of data. 

142 """ 

143 # As constructCalibs.py SkyTask.processSingleBackground() 

144 # Except: check if a detector is fully masked to avoid 

145 # self.maskTask raising. 

146 currentMask = inputExp.getMask() 

147 badMask = currentMask.getPlaneBitMask(self.config.maskList) 

148 if (currentMask.getArray() & badMask).all(): 

149 self.log.warn("All pixels are masked!") 

150 else: 

151 self.maskTask.run(inputExp, self.config.maskList) 

152 

153 # As constructCalibs.py SkyTask.measureBackground() 

154 bgModel = FocalPlaneBackground.fromCamera(self.config.largeScaleBackground, camera) 

155 bgModel.addCcd(inputExp) 

156 

157 return pipeBase.Struct( 

158 maskedExp=inputExp, 

159 maskedBkg=bgModel, 

160 ) 

161 

162 

163class CpSkyScaleMeasureConnections(pipeBase.PipelineTaskConnections, 

164 dimensions=("instrument", "physical_filter", "exposure")): 

165 inputBkgs = cT.Input( 

166 name="cpSkyDetectorBackground", 

167 doc="Initial background model from one exposure/detector", 

168 storageClass="FocalPlaneBackground", 

169 dimensions=("instrument", "exposure", "detector"), 

170 multiple=True 

171 ) 

172 

173 outputBkg = cT.Output( 

174 name="cpSkyExpBackground", 

175 doc="Background model for a full exposure.", 

176 storageClass="FocalPlaneBackground", 

177 dimensions=("instrument", "exposure"), 

178 ) 

179 outputScale = cT.Output( 

180 name="cpSkyExpScale", 

181 doc="Scale for the full exposure.", 

182 storageClass="PropertyList", 

183 dimensions=("instrument", "exposure"), 

184 ) 

185 

186 

187class CpSkyScaleMeasureConfig(pipeBase.PipelineTaskConfig, 

188 pipelineConnections=CpSkyScaleMeasureConnections): 

189 # There are no configurable parameters here. 

190 pass 

191 

192 

193class CpSkyScaleMeasureTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

194 """Measure per-exposure scale factors and merge focal plane backgrounds. 

195 

196 Merge all the per-detector partial backgrounds to a full focal 

197 plane background for each exposure, and measure the scale factor 

198 from that full background. 

199 """ 

200 

201 ConfigClass = CpSkyScaleMeasureConfig 

202 _DefaultName = "cpSkyScaleMeasure" 

203 

204 def run(self, inputBkgs): 

205 """Merge focal plane backgrounds and measure the scale factor. 

206 

207 Parameters 

208 ---------- 

209 inputBkgs : `list` [`lsst.pipe.drivers.FocalPlaneBackground`] 

210 A list of all of the partial focal plane backgrounds, one 

211 from each detector in this exposure. 

212 

213 Returns 

214 ------- 

215 results : `lsst.pipe.base.Struct` 

216 The results struct containing: 

217 

218 ``outputBkg`` : `lsst.pipe.drivers.FocalPlaneBackground` 

219 The full merged background for the entire focal plane. 

220 ``outputScale`` : `lsst.daf.base.PropertyList` 

221 A metadata containing the median level of the 

222 background, stored in the key 'scale'. 

223 """ 

224 # As constructCalibs.py SkyTask.scatterProcess() 

225 # Merge into the full focal plane. 

226 background = inputBkgs[0] 

227 for bg in inputBkgs[1:]: 

228 background.merge(bg) 

229 

230 backgroundPixels = background.getStatsImage().getArray() 

231 self.log.info("Background model min/max: %f %f. Scale %f", 

232 np.min(backgroundPixels), np.max(backgroundPixels), 

233 np.median(backgroundPixels)) 

234 

235 # A property list is overkill, but FocalPlaneBackground 

236 # doesn't have a metadata object that this can be stored in. 

237 scale = np.median(background.getStatsImage().getArray()) 

238 scaleMD = PropertyList() 

239 scaleMD.set("scale", float(scale)) 

240 

241 return pipeBase.Struct( 

242 outputBkg=background, 

243 outputScale=scaleMD, 

244 ) 

245 

246 

247class CpSkySubtractBackgroundConnections(pipeBase.PipelineTaskConnections, 

248 dimensions=("instrument", "physical_filter", 

249 "exposure", "detector")): 

250 inputExp = cT.Input( 

251 name="cpSkyMaskedIsr", 

252 doc="Masked post-ISR image.", 

253 storageClass="Exposure", 

254 dimensions=("instrument", "exposure", "detector"), 

255 ) 

256 inputBkg = cT.Input( 

257 name="cpSkyExpBackground", 

258 doc="Background model for the full exposure.", 

259 storageClass="FocalPlaneBackground", 

260 dimensions=("instrument", "exposure"), 

261 ) 

262 inputScale = cT.Input( 

263 name="cpSkyExpScale", 

264 doc="Scale for the full exposure.", 

265 storageClass="PropertyList", 

266 dimensions=("instrument", "exposure"), 

267 ) 

268 

269 outputBkg = cT.Output( 

270 name="cpExpBackground", 

271 doc="Normalized, static background.", 

272 storageClass="Background", 

273 dimensions=("instrument", "exposure", "detector"), 

274 ) 

275 

276 

277class CpSkySubtractBackgroundConfig(pipeBase.PipelineTaskConfig, 

278 pipelineConnections=CpSkySubtractBackgroundConnections): 

279 sky = pexConfig.ConfigurableField( 

280 target=SkyMeasurementTask, 

281 doc="Sky measurement", 

282 ) 

283 

284 

285class CpSkySubtractBackgroundTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

286 """Subtract per-exposure background from individual detector masked images. 

287 

288 The cpSkyMaskedIsr images constructed by CpSkyImageTask have the 

289 scaled background constructed by CpSkyScaleMeasureTask subtracted, 

290 and new background models are constructed for the remaining 

291 signal. 

292 

293 The output was called `icExpBackground` in gen2, but the product 

294 created here has definition clashes that prevent that from being 

295 reused. 

296 """ 

297 

298 ConfigClass = CpSkySubtractBackgroundConfig 

299 _DefaultName = "cpSkySubtractBkg" 

300 

301 def __init__(self, **kwargs): 

302 super().__init__(**kwargs) 

303 self.makeSubtask("sky") 

304 

305 def run(self, inputExp, inputBkg, inputScale): 

306 """Subtract per-exposure background from individual detector masked 

307 images. 

308 

309 Parameters 

310 ---------- 

311 inputExp : `lsst.afw.image.Exposure` 

312 The ISR processed, detection masked image. 

313 inputBkg : `lsst.pipe.drivers.FocalPlaneBackground. 

314 Full focal plane background for this exposure. 

315 inputScale : `lsst.daf.base.PropertyList` 

316 Metadata containing the scale factor. 

317 

318 Returns 

319 ------- 

320 results : `lsst.pipe.base.Struct` 

321 The results struct containing: 

322 

323 outputBkg : `lsst.afw.math.BackgroundList` 

324 Remnant sky background with the full-exposure 

325 component removed. 

326 """ 

327 # As constructCalibs.py SkyTask.processSingle() 

328 image = inputExp.getMaskedImage() 

329 detector = inputExp.getDetector() 

330 bbox = image.getBBox() 

331 

332 scale = inputScale.get('scale') 

333 background = inputBkg.toCcdBackground(detector, bbox) 

334 image -= background.getImage() 

335 image /= scale 

336 

337 newBackground = self.sky.measureBackground(image) 

338 return pipeBase.Struct( 

339 outputBkg=newBackground 

340 ) 

341 

342 

343class CpSkyCombineConnections(pipeBase.PipelineTaskConnections, 

344 dimensions=("instrument", "physical_filter", "detector")): 

345 inputBkgs = cT.Input( 

346 name="cpExpBackground", 

347 doc="Normalized, static background.", 

348 storageClass="Background", 

349 dimensions=("instrument", "exposure", "detector"), 

350 multiple=True, 

351 ) 

352 inputExps = cT.Input( 

353 name="cpSkyMaskedIsr", 

354 doc="Masked post-ISR image.", 

355 storageClass="Exposure", 

356 dimensions=("instrument", "exposure", "detector"), 

357 multiple=True, 

358 ) 

359 

360 outputCalib = cT.Output( 

361 name="sky", 

362 doc="Averaged static background.", 

363 storageClass="ExposureF", 

364 dimensions=("instrument", "detector", "physical_filter"), 

365 isCalibration=True, 

366 ) 

367 

368 

369class CpSkyCombineConfig(pipeBase.PipelineTaskConfig, 

370 pipelineConnections=CpSkyCombineConnections): 

371 sky = pexConfig.ConfigurableField( 

372 target=SkyMeasurementTask, 

373 doc="Sky measurement", 

374 ) 

375 

376 

377class CpSkyCombineTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

378 """Merge per-exposure measurements into a detector level calibration. 

379 

380 Each of the per-detector results from all input exposures are 

381 averaged to produce the final SKY calibration. 

382 

383 As before, this is written to a skyCalib instead of a SKY to avoid 

384 definition classes in gen3. 

385 """ 

386 

387 ConfigClass = CpSkyCombineConfig 

388 _DefaultName = "cpSkyCombine" 

389 

390 def __init__(self, **kwargs): 

391 super().__init__(**kwargs) 

392 self.makeSubtask("sky") 

393 

394 def run(self, inputBkgs, inputExps): 

395 """Merge per-exposure measurements into a detector level calibration. 

396 

397 Parameters 

398 ---------- 

399 inputBkgs : `list` [`lsst.afw.math.BackgroundList`] 

400 Remnant backgrounds from each exposure. 

401 inputExps : `list` [`lsst.afw.image.Exposure`] 

402 The ISR processed, detection masked images. 

403 

404 Returns 

405 ------- 

406 results : `lsst.pipe.base.Struct` 

407 The results struct containing: 

408 

409 `outputCalib` : `lsst.afw.image.Exposure` 

410 The final sky calibration product. 

411 """ 

412 skyCalib = self.sky.averageBackgrounds(inputBkgs) 

413 skyCalib.setDetector(inputExps[0].getDetector()) 

414 

415 CalibCombineTask().combineHeaders(inputExps, skyCalib, calibType='SKY') 

416 

417 return pipeBase.Struct( 

418 outputCalib=skyCalib, 

419 )