Coverage for python/lsst/ap/pipe/ap_pipe.py : 28%

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#
24__all__ = ["ApPipeConfig", "ApPipeTask"]
26import warnings
28import lsst.pex.config as pexConfig
29import lsst.pipe.base as pipeBase
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
39class ApPipeConfig(pexConfig.Config):
40 """Settings and defaults for ApPipeTask.
41 """
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 )
62 def setDefaults(self):
63 """Settings appropriate for most or all ap_pipe runs.
64 """
66 # Write the WarpedExposure to disk for use in Alert Packet creation.
67 self.differencer.doWriteWarpedExp = True
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].")
82class ApPipeTask(pipeBase.CmdLineTask):
83 """Command-line task representing the entire AP pipeline.
85 ``ApPipeTask`` processes raw DECam images from basic processing through
86 source association. Other observatories will be supported in the future.
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.
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 """
101 ConfigClass = ApPipeConfig
102 RunnerClass = ApPipeTaskRunner
103 _DefaultName = "apPipe"
105 def __init__(self, butler, *args, **kwargs):
106 pipeBase.CmdLineTask.__init__(self, *args, **kwargs)
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")
113 @pipeBase.timeMethod
114 def runDataRef(self, rawRef, templateIds=None, reuse=None):
115 """Execute the ap_pipe pipeline on a single image.
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.
129 Returns
130 -------
131 result : `lsst.pipe.base.Struct`
132 Result struct with components:
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)
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)
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)
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)
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)
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 )
192 @pipeBase.timeMethod
193 def runProcessCcd(self, sensorRef):
194 """Perform ISR with ingested images and calibrations via processCcd.
196 The output repository associated with ``sensorRef`` will be populated with the
197 usual post-ISR data (bkgd, calexp, icExp, icSrc, postISR).
199 Parameters
200 ----------
201 sensorRef : `lsst.daf.persistence.ButlerDataRef`
202 Data reference for raw data.
204 Returns
205 -------
206 result : `lsst.pipe.base.Struct`
207 Output of `config.ccdProcessor.runDataRef`.
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)
216 @pipeBase.timeMethod
217 def runDiffIm(self, sensorRef, templateIds=None):
218 """Do difference imaging with a template and a science image
220 The output repository associated with ``sensorRef`` will be populated with difference images
221 and catalogs of detected sources (diaSrc, diffexp, and metadata files)
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.
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)
240 @pipeBase.timeMethod
241 def runAssociation(self, sensorRef):
242 """Do source association.
244 This method writes an ``apdb_marker`` dataset once all changes related
245 to the current exposure have been committed.
247 Parameters
248 ----------
249 sensorRef : `lsst.daf.persistence.ButlerDataRef`
250 Data reference for multiple input dataset types.
252 Returns
253 -------
254 result : `lsst.pipe.base.Struct`
255 Result struct with components:
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")
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())
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"))
277 # apdb_marker triggers metrics processing; let them try to read
278 # something even if association failed
279 sensorRef.put(results.apdbMarker, "apdb_marker")
281 return pipeBase.Struct(
282 l1Database=self.diaPipe.apdb,
283 taskResults=results
284 )
286 @classmethod
287 def _makeArgumentParser(cls):
288 """A parser that can handle extra arguments for ap_pipe.
289 """
290 return ApPipeParser(name=cls._DefaultName)
293def _siblingRef(original, datasetType, dataId):
294 """Construct a new dataRef using an existing dataRef as a template.
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.
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``.
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)