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# 

2# This file is part of ap_pipe. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

6# (http://www.lsst.org). 

7# See the COPYRIGHT file at the top-level directory of this distribution 

8# for details of code ownership. 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# 

23 

24__all__ = ["ApPipeConfig", "ApPipeTask"] 

25 

26import warnings 

27 

28import lsst.pex.config as pexConfig 

29import lsst.pipe.base as pipeBase 

30 

31from lsst.pipe.tasks.processCcd import ProcessCcdTask 

32from lsst.pipe.tasks.imageDifference import ImageDifferenceTask 

33from lsst.ap.association import DiaPipelineTask 

34from lsst.ap.association.transformDiaSourceCatalog import TransformDiaSourceCatalogTask 

35from lsst.ap.pipe.apPipeParser import ApPipeParser 

36from lsst.ap.pipe.apPipeTaskRunner import ApPipeTaskRunner 

37 

38 

39class ApPipeConfig(pexConfig.Config): 

40 """Settings and defaults for ApPipeTask. 

41 """ 

42 

43 ccdProcessor = pexConfig.ConfigurableField( 

44 target=ProcessCcdTask, 

45 doc="Task used to perform basic image reduction and characterization.", 

46 ) 

47 differencer = pexConfig.ConfigurableField( 

48 target=ImageDifferenceTask, 

49 doc="Task used to do image subtraction and DiaSource detection.", 

50 ) 

51 transformDiaSrcCat = pexConfig.ConfigurableField( 

52 target=TransformDiaSourceCatalogTask, 

53 doc="Task for converting and calibrating the afw SourceCatalog of " 

54 "DiaSources to Pandas DataFrame for use in Association." 

55 ) 

56 diaPipe = pexConfig.ConfigurableField( 

57 target=DiaPipelineTask, 

58 doc="Pipeline task for loading/store DiaSources and DiaObjects and " 

59 "spatially associating them.", 

60 ) 

61 

62 def setDefaults(self): 

63 """Settings appropriate for most or all ap_pipe runs. 

64 """ 

65 

66 # Write the WarpedExposure to disk for use in Alert Packet creation. 

67 self.differencer.doWriteWarpedExp = True 

68 

69 def validate(self): 

70 pexConfig.Config.validate(self) 

71 if not self.ccdProcessor.calibrate.doWrite or not self.ccdProcessor.calibrate.doWriteExposure: 

72 raise ValueError("Differencing needs calexps [ccdProcessor.calibrate.doWrite, doWriteExposure]") 

73 if not self.differencer.doMeasurement: 

74 raise ValueError("Source association needs diaSource fluxes [differencer.doMeasurement].") 

75 if not self.differencer.doWriteSources: 

76 raise ValueError("Source association needs diaSource catalogs [differencer.doWriteSources].") 

77 if not self.differencer.doWriteSubtractedExp: 

78 raise ValueError("Source association needs difference exposures " 

79 "[differencer.doWriteSubtractedExp].") 

80 

81 

82class ApPipeTask(pipeBase.CmdLineTask): 

83 """Command-line task representing the entire AP pipeline. 

84 

85 ``ApPipeTask`` processes raw DECam images from basic processing through 

86 source association. Other observatories will be supported in the future. 

87 

88 ``ApPipeTask`` can be run from the command line, but it can also be called 

89 from other pipeline code. It provides public methods for executing each 

90 major step of the pipeline by itself. 

91 

92 Parameters 

93 ---------- 

94 butler : `lsst.daf.persistence.Butler` 

95 A Butler providing access to the science, calibration, and (unless 

96 ``config.differencer.getTemplate`` is overridden) template data to 

97 be processed. Its output repository must be both readable 

98 and writable. 

99 """ 

100 

101 ConfigClass = ApPipeConfig 

102 RunnerClass = ApPipeTaskRunner 

103 _DefaultName = "apPipe" 

104 

105 def __init__(self, butler, *args, **kwargs): 

106 pipeBase.CmdLineTask.__init__(self, *args, **kwargs) 

107 

108 self.makeSubtask("ccdProcessor", butler=butler) 

109 self.makeSubtask("differencer", butler=butler) 

110 self.makeSubtask("transformDiaSrcCat", initInputs={"diaSourceSchema": self.differencer.outputSchema}) 

111 self.makeSubtask("diaPipe") 

112 

113 @pipeBase.timeMethod 

114 def runDataRef(self, rawRef, templateIds=None, reuse=None): 

115 """Execute the ap_pipe pipeline on a single image. 

116 

117 Parameters 

118 ---------- 

119 rawRef : `lsst.daf.persistence.ButlerDataRef` 

120 A reference to the raw data to process. 

121 templateIds : `list` of `dict`, optional 

122 A list of parsed data IDs for templates to use. Only used if 

123 ``config.differencer`` is configured to do so. ``differencer`` or 

124 its subtasks may restrict the allowed IDs. 

125 reuse : `list` of `str`, optional 

126 The names of all subtasks that may be skipped if their output is 

127 present. Defaults to skipping nothing. 

128 

129 Returns 

130 ------- 

131 result : `lsst.pipe.base.Struct` 

132 Result struct with components: 

133 

134 - l1Database : handle for accessing the final association database, conforming to 

135 `ap_association`'s DB access API 

136 - ccdProcessor : output of `config.ccdProcessor.runDataRef` (`lsst.pipe.base.Struct` or `None`). 

137 - differencer : output of `config.differencer.runDataRef` (`lsst.pipe.base.Struct` or `None`). 

138 - diaPipe : output of `config.diaPipe.run` (`lsst.pipe.base.Struct` or `None`). 

139 """ 

140 if reuse is None: 

141 reuse = [] 

142 # Work around mismatched HDU lists for raw and processed data 

143 calexpId = rawRef.dataId.copy() 

144 if 'hdu' in calexpId: 

145 del calexpId['hdu'] 

146 calexpRef = rawRef.getButler().dataRef("calexp", dataId=calexpId) 

147 

148 # Ensure that templateIds make it through basic data reduction 

149 # TODO: treat as independent jobs (may need SuperTask framework?) 

150 if templateIds is not None: 

151 for templateId in templateIds: 

152 # templateId is typically visit-only; consider only the same raft/CCD/etc. as rawRef 

153 rawTemplateRef = _siblingRef(rawRef, "raw", templateId) 

154 calexpTemplateRef = _siblingRef(calexpRef, "calexp", templateId) 

155 if "ccdProcessor" not in reuse or not calexpTemplateRef.datasetExists("calexp"): 

156 self.runProcessCcd(rawTemplateRef) 

157 

158 if "ccdProcessor" in reuse and calexpRef.datasetExists("calexp"): 

159 self.log.info("ProcessCcd has already been run for {0}, skipping...".format(rawRef.dataId)) 

160 processResults = None 

161 else: 

162 processResults = self.runProcessCcd(rawRef) 

163 

164 diffType = self.config.differencer.coaddName 

165 if "differencer" in reuse and calexpRef.datasetExists(diffType + "Diff_diaSrc"): 

166 self.log.info("DiffIm has already been run for {0}, skipping...".format(calexpRef.dataId)) 

167 diffImResults = None 

168 else: 

169 diffImResults = self.runDiffIm(calexpRef, templateIds) 

170 

171 if "diaPipe" in reuse: 

172 warnings.warn( 

173 "Reusing association results for some images while rerunning " 

174 "others may change the associations. If exact reproducibility " 

175 "matters, please clear the association database and run " 

176 "ap_pipe.py with --reuse-output-from=differencer to redo all " 

177 "association results consistently.") 

178 if "diaPipe" in reuse and calexpRef.datasetExists("apdb_marker"): 

179 message = "DiaPipeline has already been run for {0}, skipping...".format(calexpRef.dataId) 

180 self.log.info(message) 

181 diaPipeResults = None 

182 else: 

183 diaPipeResults = self.runAssociation(calexpRef) 

184 

185 return pipeBase.Struct( 

186 l1Database=self.diaPipe.apdb, 

187 ccdProcessor=processResults if processResults else None, 

188 differencer=diffImResults if diffImResults else None, 

189 diaPipe=diaPipeResults.taskResults if diaPipeResults else None 

190 ) 

191 

192 @pipeBase.timeMethod 

193 def runProcessCcd(self, sensorRef): 

194 """Perform ISR with ingested images and calibrations via processCcd. 

195 

196 The output repository associated with ``sensorRef`` will be populated with the 

197 usual post-ISR data (bkgd, calexp, icExp, icSrc, postISR). 

198 

199 Parameters 

200 ---------- 

201 sensorRef : `lsst.daf.persistence.ButlerDataRef` 

202 Data reference for raw data. 

203 

204 Returns 

205 ------- 

206 result : `lsst.pipe.base.Struct` 

207 Output of `config.ccdProcessor.runDataRef`. 

208 

209 Notes 

210 ----- 

211 The input repository corresponding to ``sensorRef`` must already contain the refcats. 

212 """ 

213 self.log.info("Running ProcessCcd...") 

214 return self.ccdProcessor.runDataRef(sensorRef) 

215 

216 @pipeBase.timeMethod 

217 def runDiffIm(self, sensorRef, templateIds=None): 

218 """Do difference imaging with a template and a science image 

219 

220 The output repository associated with ``sensorRef`` will be populated with difference images 

221 and catalogs of detected sources (diaSrc, diffexp, and metadata files) 

222 

223 Parameters 

224 ---------- 

225 sensorRef : `lsst.daf.persistence.ButlerDataRef` 

226 Data reference for multiple dataset types, both input and output. 

227 templateIds : `list` of `dict`, optional 

228 A list of parsed data IDs for templates to use. Only used if 

229 ``config.differencer`` is configured to do so. ``differencer`` or 

230 its subtasks may restrict the allowed IDs. 

231 

232 Returns 

233 ------- 

234 result : `lsst.pipe.base.Struct` 

235 Output of `config.differencer.runDataRef`. 

236 """ 

237 self.log.info("Running ImageDifference...") 

238 return self.differencer.runDataRef(sensorRef, templateIdList=templateIds) 

239 

240 @pipeBase.timeMethod 

241 def runAssociation(self, sensorRef): 

242 """Do source association. 

243 

244 This method writes an ``apdb_marker`` dataset once all changes related 

245 to the current exposure have been committed. 

246 

247 Parameters 

248 ---------- 

249 sensorRef : `lsst.daf.persistence.ButlerDataRef` 

250 Data reference for multiple input dataset types. 

251 

252 Returns 

253 ------- 

254 result : `lsst.pipe.base.Struct` 

255 Result struct with components: 

256 

257 - apdb : `lsst.dax.apdb.Apdb` Initialized association database containing final association 

258 results. 

259 - taskResults : output of `config.diaPipe.run` (`lsst.pipe.base.Struct`). 

260 """ 

261 diffType = self.config.differencer.coaddName 

262 diffIm = sensorRef.get(diffType + "Diff_differenceExp") 

263 

264 transformResult = self.transformDiaSrcCat.run( 

265 diaSourceCat=sensorRef.get(diffType + "Diff_diaSrc"), 

266 diffIm=diffIm, 

267 band=diffIm.getFilterLabel().bandLabel, 

268 ccdVisitId=diffIm.getInfo().getVisitInfo().getExposureId()) 

269 

270 results = self.diaPipe.run( 

271 diaSourceTable=transformResult.diaSourceTable, 

272 diffIm=sensorRef.get(diffType + "Diff_differenceExp"), 

273 exposure=sensorRef.get("calexp"), 

274 warpedExposure=sensorRef.get(diffType + "Diff_warpedExp"), 

275 ccdExposureIdBits=sensorRef.get("ccdExposureId_bits")) 

276 

277 # apdb_marker triggers metrics processing; let them try to read 

278 # something even if association failed 

279 sensorRef.put(results.apdbMarker, "apdb_marker") 

280 

281 return pipeBase.Struct( 

282 l1Database=self.diaPipe.apdb, 

283 taskResults=results 

284 ) 

285 

286 @classmethod 

287 def _makeArgumentParser(cls): 

288 """A parser that can handle extra arguments for ap_pipe. 

289 """ 

290 return ApPipeParser(name=cls._DefaultName) 

291 

292 

293def _siblingRef(original, datasetType, dataId): 

294 """Construct a new dataRef using an existing dataRef as a template. 

295 

296 The typical application is to construct a data ID that differs from an 

297 existing ID in one or two keys, but is more specific than expanding a 

298 partial data ID would be. 

299 

300 Parameters 

301 ---------- 

302 original : `lsst.daf.persistence.ButlerDataRef` 

303 A dataRef related to the desired one. Assumed to represent a unique dataset. 

304 datasetType : `str` 

305 The desired type of the new dataRef. Must be compatible 

306 with ``original``. 

307 dataId : `dict` from `str` to any 

308 A possibly partial data ID for the new dataRef. Any properties left 

309 unspecified shall be copied from ``original``. 

310 

311 Returns 

312 ------- 

313 dataRef : `lsst.daf.persistence.ButlerDataRef` 

314 A dataRef to the same butler as ``original``, but of type 

315 ``datasetType`` and with data ID equivalent to 

316 ``original.dataId.update(dataId)``. 

317 """ 

318 butler = original.getButler() 

319 return butler.dataRef(datasetType, dataId=original.dataId, **dataId)