lsst.pipe.tasks  13.0-37-g58c8d4e+3
 All Classes Namespaces Files Functions Variables Groups Pages
warpAndPsfMatch.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 from __future__ import absolute_import, division, print_function
23 import lsst.pex.config as pexConfig
24 import lsst.afw.math as afwMath
25 import lsst.afw.image as afwImage
26 import lsst.afw.geom as afwGeom
27 import lsst.pipe.base as pipeBase
28 from lsst.ip.diffim import ModelPsfMatchTask
29 from lsst.meas.algorithms import WarpedPsf
30 
31 __all__ = ["WarpAndPsfMatchTask"]
32 
33 
34 class WarpAndPsfMatchConfig(pexConfig.Config):
35  """Config for WarpAndPsfMatchTask
36  """
37  psfMatch = pexConfig.ConfigurableField(
38  target=ModelPsfMatchTask,
39  doc="PSF matching model to model task",
40  )
41  warp = pexConfig.ConfigField(
42  dtype=afwMath.Warper.ConfigClass,
43  doc="warper configuration",
44  )
45  matchThenWarp = pexConfig.Field(
46  dtype=bool,
47  doc="Reverse order of warp and match operations to replicate legacy coadd temporary exposures",
48  default=False,
49  )
50 
51 
52 class WarpAndPsfMatchTask(pipeBase.Task):
53  """A task to warp and PSF-match an exposure
54  """
55  ConfigClass = WarpAndPsfMatchConfig
56 
57  def __init__(self, *args, **kwargs):
58  pipeBase.Task.__init__(self, *args, **kwargs)
59  self.makeSubtask("psfMatch")
60  self.warper = afwMath.Warper.fromConfig(self.config.warp)
61 
62  def run(self, exposure, wcs, modelPsf=None, maxBBox=None, destBBox=None,
63  makeDirect=True, makePsfMatched=False):
64  """Warp and PSF-match exposure (if modelPsf is not None)
65 
66  Parameters
67  ----------
68  exposure : :cpp:class: `lsst::afw::image::Exposure`
69  Exposure to preprocess.
70  wcs : :cpp:class:`lsst::afw::image::Wcs`
71  Desired WCS of temporary images.
72  modelPsf : :cpp:class: `lsst::meas::algorithms::KernelPsf` or None
73  Target PSF to which to match.
74  maxBBox : :cpp:class:`lsst::afw::geom::Box2I` or None
75  Maximum allowed parent bbox of warped exposure.
76  If None then the warped exposure will be just big enough to contain all warped pixels;
77  if provided then the warped exposure may be smaller, and so missing some warped pixels;
78  ignored if destBBox is not None.
79  destBBox: :cpp:class: `lsst::afw::geom::Box2I` or None
80  Exact parent bbox of warped exposure.
81  If None then maxBBox is used to determine the bbox, otherwise maxBBox is ignored.
82  makeDirect : bool
83  Return an exposure that has been only warped?
84  makePsfMatched : bool
85  Return an exposure that has been warped and PSF-matched?
86 
87  Returns
88  -------
89  An lsst.pipe.base.Struct with the following fields:
90 
91  direct : :cpp:class:`lsst::afw::image::Exposure`
92  warped exposure
93  psfMatched : :cpp:class: `lsst::afw::image::Exposure`
94  warped and psf-Matched temporary exposure
95  """
96  if makePsfMatched and modelPsf is None:
97  raise RuntimeError("makePsfMatched=True, but no model PSF was provided")
98 
99  if not makePsfMatched and not makeDirect:
100  self.log.warn("Neither makeDirect nor makePsfMatched requested")
101 
102  if self.config.matchThenWarp:
103  # Legacy order of operations:
104  # PSF-matching is performed before warping, which is incorrect.
105  # a position-dependent warping (as is used in the general case) will
106  # re-introduce a position-dependent PSF.
107  if makePsfMatched:
108  exposurePsfMatched = self.psfMatch.run(exposure, modelPsf).psfMatchedExposure
109  with self.timer("warp"):
110  exposurePsfMatched = self.warper.warpExposure(wcs, exposurePsfMatched,
111  maxBBox=maxBBox, destBBox=destBBox)
112  else:
113  exposurePsfMatched = None
114 
115  if makeDirect:
116  # also make an unmatched temp exp
117  with self.timer("warp"):
118  exposure = self.warper.warpExposure(wcs, exposure, maxBBox=maxBBox, destBBox=destBBox)
119  else:
120  exposure = None
121 
122  else:
123  # Warp PSF before overwriting exposure
124  xyTransform = afwImage.XYTransformFromWcsPair(wcs, exposure.getWcs())
125  psfWarped = WarpedPsf(exposure.getPsf(), xyTransform)
126 
127  if makePsfMatched and maxBBox is not None:
128  # grow warped region to provide sufficient area for PSF-matching
129  pixToGrow = 2 * max(self.psfMatch.kConfig.sizeCellX,
130  self.psfMatch.kConfig.sizeCellY)
131  # replace with copy
132  maxBBox = afwGeom.Box2I(maxBBox)
133  maxBBox.grow(pixToGrow)
134 
135  with self.timer("warp"):
136  exposure = self.warper.warpExposure(wcs, exposure, maxBBox=maxBBox, destBBox=destBBox)
137  exposure.setPsf(psfWarped)
138 
139  if makePsfMatched:
140  try:
141  exposurePsfMatched = self.psfMatch.run(exposure, modelPsf).psfMatchedExposure
142  except Exception as e:
143  exposurePsfMatched = None
144  self.log.info("Cannot PSF-Match: %s" % (e))
145 
146  return pipeBase.Struct(
147  direct=exposure if makeDirect else None,
148  psfMatched=exposurePsfMatched if makePsfMatched else None
149  )