LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
registerImage.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2013 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 
23 """
24 This module contains a Task to register (align) multiple images.
25 """
26 
27 __all__ = ["RegisterTask", "RegisterConfig"]
28 
29 import math
30 import numpy
31 
32 from lsst.pex.config import Config, Field, ConfigField
33 from lsst.pipe.base import Task, Struct
34 from lsst.meas.astrom.sip import makeCreateWcsWithSip
35 from lsst.afw.math import Warper
36 
37 import lsst.afw.geom as afwGeom
38 import lsst.afw.table as afwTable
39 
40 
41 class RegisterConfig(Config):
42  """Configuration for RegisterTask"""
43  matchRadius = Field(dtype=float, default=1.0, doc="Matching radius (arcsec)", check=lambda x: x > 0)
44  sipOrder = Field(dtype=int, default=4, doc="Order for SIP WCS", check=lambda x: x > 1)
45  sipIter = Field(dtype=int, default=3, doc="Rejection iterations for SIP WCS", check=lambda x: x > 0)
46  sipRej = Field(dtype=float, default=3.0, doc="Rejection threshold for SIP WCS", check=lambda x: x > 0)
47  warper = ConfigField(dtype=Warper.ConfigClass, doc="Configuration for warping")
48 
49 
50 class RegisterTask(Task):
51  """
52  Task to register (align) multiple images.
53 
54  The 'run' method provides a revised Wcs from matches and fitting sources.
55  Additional methods are provided as a convenience to warp an exposure
56  ('warpExposure') and sources ('warpSources') with the new Wcs.
57  """
58  ConfigClass = RegisterConfig
59 
60  def run(self, inputSources, inputWcs, inputBBox, templateSources):
61  """Register (align) an input exposure to the template
62 
63  The sources must have RA,Dec set, and accurate to within the
64  'matchRadius' of the configuration in order to facilitate source
65  matching. We fit a new Wcs, but do NOT set it in the input exposure.
66 
67  @param inputSources: Sources from input exposure
68  @param inputWcs: Wcs of input exposure
69  @param inputBBox: Bounding box of input exposure
70  @param templateSources: Sources from template exposure
71  @return Struct(matches: Matches between sources,
72  wcs: Wcs for input in frame of template,
73  )
74  """
75  matches = self.matchSources(inputSources, templateSources)
76  wcs = self.fitWcs(matches, inputWcs, inputBBox)
77  return Struct(matches=matches, wcs=wcs)
78 
79  def matchSources(self, inputSources, templateSources):
80  """Match sources between the input and template
81 
82  The order of the input arguments matters (because the later Wcs
83  fitting assumes a particular order).
84 
85  @param inputSources: Source catalog of the input frame
86  @param templateSources: Source of the target frame
87  @return Match list
88  """
89  matches = afwTable.matchRaDec(templateSources, inputSources,
90  self.config.matchRadius*afwGeom.arcseconds)
91  self.log.info("Matching within %.1f arcsec: %d matches" % (self.config.matchRadius, len(matches)))
92  self.metadata.set("MATCH_NUM", len(matches))
93  if len(matches) == 0:
94  raise RuntimeError("Unable to match source catalogs")
95  return matches
96 
97  def fitWcs(self, matches, inputWcs, inputBBox):
98  """Fit Wcs to matches
99 
100  The fitting includes iterative sigma-clipping.
101 
102  @param matches: List of matches (first is target, second is input)
103  @param inputWcs: Original input Wcs
104  @param inputBBox: Bounding box of input image
105  @return Wcs
106  """
107  copyMatches = type(matches)(matches)
108  refCoordKey = copyMatches[0].first.getTable().getCoordKey()
109  inCentroidKey = copyMatches[0].second.getTable().getCentroidKey()
110  for i in range(self.config.sipIter):
111  sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
112  self.log.logdebug("Registration WCS RMS iteration %d: %f pixels" %
113  (i, sipFit.getScatterInPixels()))
114  wcs = sipFit.getNewWcs()
115  dr = [m.first.get(refCoordKey).angularSeparation(
116  wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds() for
117  m in copyMatches]
118  dr = numpy.array(dr)
119  rms = math.sqrt((dr*dr).mean()) # RMS from zero
120  rms = max(rms, 1.0e-9) # Don't believe any RMS smaller than this
121  self.log.logdebug("Registration iteration %d: rms=%f" % (i, rms))
122  good = numpy.where(dr < self.config.sipRej*rms)[0]
123  numBad = len(copyMatches) - len(good)
124  self.log.logdebug("Registration iteration %d: rejected %d" % (i, numBad))
125  if numBad == 0:
126  break
127  copyMatches = type(matches)(copyMatches[i] for i in good)
128 
129  sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
130  self.log.info("Registration WCS: final WCS RMS=%f pixels from %d matches" %
131  (sipFit.getScatterInPixels(), len(copyMatches)))
132  self.metadata.set("SIP_RMS", sipFit.getScatterInPixels())
133  self.metadata.set("SIP_GOOD", len(copyMatches))
134  self.metadata.set("SIP_REJECTED", len(matches) - len(copyMatches))
135  wcs = sipFit.getNewWcs()
136  return wcs
137 
138  def warpExposure(self, inputExp, newWcs, templateWcs, templateBBox):
139  """Warp input exposure to template frame
140 
141  There are a variety of data attached to the exposure (e.g., PSF, Calib
142  and other metadata), but we do not attempt to warp these to the template
143  frame.
144 
145  @param inputExp: Input exposure, to be warped
146  @param newWcs: Revised Wcs for input exposure
147  @param templateWcs: Target Wcs
148  @param templateBBox: Target bounding box
149  @return Warped exposure
150  """
151  warper = Warper.fromConfig(self.config.warper)
152  copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
153  alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
154  return alignedExp
155 
156  def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
157  """Warp sources to the new frame
158 
159  It would be difficult to transform all possible quantities of potential
160  interest between the two frames. We therefore update only the sky and
161  pixel coordinates.
162 
163  @param inputSources: Sources on input exposure, to be warped
164  @param newWcs: Revised Wcs for input exposure
165  @param templateWcs: Target Wcs
166  @param templateBBox: Target bounding box
167  @return Warped sources
168  """
169  alignedSources = inputSources.copy(True)
170  if not isinstance(templateBBox, afwGeom.Box2D):
171  # There is no method Box2I::contains(Point2D)
172  templateBBox = afwGeom.Box2D(templateBBox)
173  table = alignedSources.getTable()
174  coordKey = table.getCoordKey()
175  centroidKey = table.getCentroidKey()
176  centroidErrKey = table.getCentroidErrKey()
177  deleteList = []
178  for i, s in enumerate(alignedSources):
179  oldCentroid = s.get(centroidKey)
180  newCoord = newWcs.pixelToSky(oldCentroid)
181  newCentroid = templateWcs.skyToPixel(newCoord)
182  if not templateBBox.contains(newCentroid):
183  deleteList.append(i)
184  continue
185  s.set(coordKey, newCoord)
186  s.set(centroidKey, newCentroid)
187 
188  for i in reversed(deleteList): # Delete from back so we don't change indices
189  del alignedSources[i]
190 
191  return alignedSources
CreateWcsWithSip< MatchT > makeCreateWcsWithSip(std::vector< MatchT > const &matches, afw::image::Wcs const &linearWcs, int const order, afw::geom::Box2I const &bbox=afw::geom::Box2I(), int const ngrid=0)
Factory function for CreateWcsWithSip.
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric=true)
A floating-point coordinate rectangle geometry.
Definition: Box.h:271