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.pipe.apPipeParser import ApPipeParser 

35from lsst.ap.pipe.apPipeTaskRunner import ApPipeTaskRunner 

36 

37 

38class ApPipeConfig(pexConfig.Config): 

39 """Settings and defaults for ApPipeTask. 

40 """ 

41 

42 ccdProcessor = pexConfig.ConfigurableField( 

43 target=ProcessCcdTask, 

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

45 ) 

46 differencer = pexConfig.ConfigurableField( 

47 target=ImageDifferenceTask, 

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

49 ) 

50 diaPipe = pexConfig.ConfigurableField( 

51 target=DiaPipelineTask, 

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

53 "spatially associating them.", 

54 ) 

55 

56 def setDefaults(self): 

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

58 """ 

59 # Always prefer decorrelation; may eventually become ImageDifferenceTask default 

60 self.differencer.doDecorrelation = True 

61 self.differencer.detection.thresholdValue = 5.0 # needed with doDecorrelation 

62 

63 # Don't have source catalogs for templates 

64 self.differencer.doSelectSources = False 

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("diaPipe", initInputs={"diaSourceSchema": self.differencer.outputSchema}) 

111 

112 @pipeBase.timeMethod 

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

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

115 

116 Parameters 

117 ---------- 

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

119 A reference to the raw data to process. 

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

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

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

123 its subtasks may restrict the allowed IDs. 

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

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

126 present. Defaults to skipping nothing. 

127 

128 Returns 

129 ------- 

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

131 Result struct with components: 

132 

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

134 `ap_association`'s DB access API 

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

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

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

138 """ 

139 if reuse is None: 

140 reuse = [] 

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

142 calexpId = rawRef.dataId.copy() 

143 if 'hdu' in calexpId: 

144 del calexpId['hdu'] 

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

146 

147 # Ensure that templateIds make it through basic data reduction 

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

149 if templateIds is not None: 

150 for templateId in templateIds: 

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

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

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

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

155 self.runProcessCcd(rawTemplateRef) 

156 

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

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

159 processResults = None 

160 else: 

161 processResults = self.runProcessCcd(rawRef) 

162 

163 diffType = self.config.differencer.coaddName 

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

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

166 diffImResults = None 

167 else: 

168 diffImResults = self.runDiffIm(calexpRef, templateIds) 

169 

170 if "diaPipe" in reuse: 

171 warnings.warn( 

172 "Reusing association results for some images while rerunning " 

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

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

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

176 "association results consistently.") 

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

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

179 self.log.info(message) 

180 diaPipeResults = None 

181 else: 

182 diaPipeResults = self.runAssociation(calexpRef) 

183 

184 return pipeBase.Struct( 

185 l1Database=self.diaPipe.apdb, 

186 ccdProcessor=processResults if processResults else None, 

187 differencer=diffImResults if diffImResults else None, 

188 diaPipe=diaPipeResults.taskResults if diaPipeResults else None 

189 ) 

190 

191 @pipeBase.timeMethod 

192 def runProcessCcd(self, sensorRef): 

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

194 

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

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

197 

198 Parameters 

199 ---------- 

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

201 Data reference for raw data. 

202 

203 Returns 

204 ------- 

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

206 Output of `config.ccdProcessor.runDataRef`. 

207 

208 Notes 

209 ----- 

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

211 """ 

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

213 return self.ccdProcessor.runDataRef(sensorRef) 

214 

215 @pipeBase.timeMethod 

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

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

218 

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

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

221 

222 Parameters 

223 ---------- 

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

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

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

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

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

229 its subtasks may restrict the allowed IDs. 

230 

231 Returns 

232 ------- 

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

234 Output of `config.differencer.runDataRef`. 

235 """ 

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

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

238 

239 @pipeBase.timeMethod 

240 def runAssociation(self, sensorRef): 

241 """Do source association. 

242 

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

244 to the current exposure have been committed. 

245 

246 Parameters 

247 ---------- 

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

249 Data reference for multiple input dataset types. 

250 

251 Returns 

252 ------- 

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

254 Result struct with components: 

255 

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

257 results. 

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

259 """ 

260 diffType = self.config.differencer.coaddName 

261 

262 results = self.diaPipe.run( 

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

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

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

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

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

268 

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

270 # something even if association failed 

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

272 

273 return pipeBase.Struct( 

274 l1Database=self.diaPipe.apdb, 

275 taskResults=results 

276 ) 

277 

278 @classmethod 

279 def _makeArgumentParser(cls): 

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

281 """ 

282 return ApPipeParser(name=cls._DefaultName) 

283 

284 

285def _siblingRef(original, datasetType, dataId): 

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

287 

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

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

290 partial data ID would be. 

291 

292 Parameters 

293 ---------- 

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

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

296 datasetType : `str` 

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

298 with ``original``. 

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

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

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

302 

303 Returns 

304 ------- 

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

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

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

308 ``original.dataId.update(dataId)``. 

309 """ 

310 butler = original.getButler() 

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