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

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.pipe.apPipeParser import ApPipeParser
35from lsst.ap.pipe.apPipeTaskRunner import ApPipeTaskRunner
38class ApPipeConfig(pexConfig.Config):
39 """Settings and defaults for ApPipeTask.
40 """
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 )
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
63 # Don't have source catalogs for templates
64 self.differencer.doSelectSources = False
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("diaPipe", initInputs={"diaSourceSchema": self.differencer.outputSchema})
112 @pipeBase.timeMethod
113 def runDataRef(self, rawRef, templateIds=None, reuse=None):
114 """Execute the ap_pipe pipeline on a single image.
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.
128 Returns
129 -------
130 result : `lsst.pipe.base.Struct`
131 Result struct with components:
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)
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)
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)
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)
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)
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 )
191 @pipeBase.timeMethod
192 def runProcessCcd(self, sensorRef):
193 """Perform ISR with ingested images and calibrations via processCcd.
195 The output repository associated with ``sensorRef`` will be populated with the
196 usual post-ISR data (bkgd, calexp, icExp, icSrc, postISR).
198 Parameters
199 ----------
200 sensorRef : `lsst.daf.persistence.ButlerDataRef`
201 Data reference for raw data.
203 Returns
204 -------
205 result : `lsst.pipe.base.Struct`
206 Output of `config.ccdProcessor.runDataRef`.
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)
215 @pipeBase.timeMethod
216 def runDiffIm(self, sensorRef, templateIds=None):
217 """Do difference imaging with a template and a science image
219 The output repository associated with ``sensorRef`` will be populated with difference images
220 and catalogs of detected sources (diaSrc, diffexp, and metadata files)
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.
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)
239 @pipeBase.timeMethod
240 def runAssociation(self, sensorRef):
241 """Do source association.
243 This method writes an ``apdb_marker`` dataset once all changes related
244 to the current exposure have been committed.
246 Parameters
247 ----------
248 sensorRef : `lsst.daf.persistence.ButlerDataRef`
249 Data reference for multiple input dataset types.
251 Returns
252 -------
253 result : `lsst.pipe.base.Struct`
254 Result struct with components:
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
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"))
269 # apdb_marker triggers metrics processing; let them try to read
270 # something even if association failed
271 sensorRef.put(results.apdbMarker, "apdb_marker")
273 return pipeBase.Struct(
274 l1Database=self.diaPipe.apdb,
275 taskResults=results
276 )
278 @classmethod
279 def _makeArgumentParser(cls):
280 """A parser that can handle extra arguments for ap_pipe.
281 """
282 return ApPipeParser(name=cls._DefaultName)
285def _siblingRef(original, datasetType, dataId):
286 """Construct a new dataRef using an existing dataRef as a template.
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.
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``.
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)