24 This module contains a Task to register (align) multiple images. 26 from __future__
import absolute_import, division, print_function
27 from builtins
import range
29 __all__ = [
"RegisterTask",
"RegisterConfig"]
34 from lsst.pex.config
import Config, Field, ConfigField
35 from lsst.pipe.base
import Task, Struct
36 from lsst.meas.astrom.sip
import makeCreateWcsWithSip
37 from lsst.afw.math
import Warper
39 import lsst.afw.geom
as afwGeom
40 import lsst.afw.table
as afwTable
44 """Configuration for RegisterTask""" 45 matchRadius = Field(dtype=float, default=1.0, doc=
"Matching radius (arcsec)", check=
lambda x: x > 0)
46 sipOrder = Field(dtype=int, default=4, doc=
"Order for SIP WCS", check=
lambda x: x > 1)
47 sipIter = Field(dtype=int, default=3, doc=
"Rejection iterations for SIP WCS", check=
lambda x: x > 0)
48 sipRej = Field(dtype=float, default=3.0, doc=
"Rejection threshold for SIP WCS", check=
lambda x: x > 0)
49 warper = ConfigField(dtype=Warper.ConfigClass, doc=
"Configuration for warping")
54 Task to register (align) multiple images. 56 The 'run' method provides a revised Wcs from matches and fitting sources. 57 Additional methods are provided as a convenience to warp an exposure 58 ('warpExposure') and sources ('warpSources') with the new Wcs. 60 ConfigClass = RegisterConfig
62 def run(self, inputSources, inputWcs, inputBBox, templateSources):
63 """Register (align) an input exposure to the template 65 The sources must have RA,Dec set, and accurate to within the 66 'matchRadius' of the configuration in order to facilitate source 67 matching. We fit a new Wcs, but do NOT set it in the input exposure. 69 @param inputSources: Sources from input exposure 70 @param inputWcs: Wcs of input exposure 71 @param inputBBox: Bounding box of input exposure 72 @param templateSources: Sources from template exposure 73 @return Struct(matches: Matches between sources, 74 wcs: Wcs for input in frame of template, 77 matches = self.
matchSources(inputSources, templateSources)
78 wcs = self.
fitWcs(matches, inputWcs, inputBBox)
79 return Struct(matches=matches, wcs=wcs)
82 """Match sources between the input and template 84 The order of the input arguments matters (because the later Wcs 85 fitting assumes a particular order). 87 @param inputSources: Source catalog of the input frame 88 @param templateSources: Source of the target frame 91 matches = afwTable.matchRaDec(templateSources, inputSources,
92 self.config.matchRadius*afwGeom.arcseconds)
93 self.log.info(
"Matching within %.1f arcsec: %d matches" % (self.config.matchRadius, len(matches)))
94 self.metadata.set(
"MATCH_NUM", len(matches))
96 raise RuntimeError(
"Unable to match source catalogs")
99 def fitWcs(self, matches, inputWcs, inputBBox):
100 """Fit Wcs to matches 102 The fitting includes iterative sigma-clipping. 104 @param matches: List of matches (first is target, second is input) 105 @param inputWcs: Original input Wcs 106 @param inputBBox: Bounding box of input image 109 copyMatches = type(matches)(matches)
110 refCoordKey = copyMatches[0].first.getTable().getCoordKey()
111 inCentroidKey = copyMatches[0].second.getTable().getCentroidKey()
112 for i
in range(self.config.sipIter):
113 sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
114 self.log.debug(
"Registration WCS RMS iteration %d: %f pixels",
115 i, sipFit.getScatterInPixels())
116 wcs = sipFit.getNewWcs()
117 dr = [m.first.get(refCoordKey).angularSeparation(
118 wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds()
for 121 rms = math.sqrt((dr*dr).mean())
122 rms = max(rms, 1.0e-9)
123 self.log.debug(
"Registration iteration %d: rms=%f", i, rms)
124 good = numpy.where(dr < self.config.sipRej*rms)[0]
125 numBad = len(copyMatches) - len(good)
126 self.log.debug(
"Registration iteration %d: rejected %d", i, numBad)
129 copyMatches = type(matches)(copyMatches[i]
for i
in good)
131 sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
132 self.log.info(
"Registration WCS: final WCS RMS=%f pixels from %d matches" %
133 (sipFit.getScatterInPixels(), len(copyMatches)))
134 self.metadata.set(
"SIP_RMS", sipFit.getScatterInPixels())
135 self.metadata.set(
"SIP_GOOD", len(copyMatches))
136 self.metadata.set(
"SIP_REJECTED", len(matches) - len(copyMatches))
137 wcs = sipFit.getNewWcs()
141 """Warp input exposure to template frame 143 There are a variety of data attached to the exposure (e.g., PSF, Calib 144 and other metadata), but we do not attempt to warp these to the template 147 @param inputExp: Input exposure, to be warped 148 @param newWcs: Revised Wcs for input exposure 149 @param templateWcs: Target Wcs 150 @param templateBBox: Target bounding box 151 @return Warped exposure 153 warper = Warper.fromConfig(self.config.warper)
154 copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
155 alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
158 def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
159 """Warp sources to the new frame 161 It would be difficult to transform all possible quantities of potential 162 interest between the two frames. We therefore update only the sky and 165 @param inputSources: Sources on input exposure, to be warped 166 @param newWcs: Revised Wcs for input exposure 167 @param templateWcs: Target Wcs 168 @param templateBBox: Target bounding box 169 @return Warped sources 171 alignedSources = inputSources.copy(
True)
172 if not isinstance(templateBBox, afwGeom.Box2D):
174 templateBBox = afwGeom.Box2D(templateBBox)
175 table = alignedSources.getTable()
176 coordKey = table.getCoordKey()
177 centroidKey = table.getCentroidKey()
179 for i, s
in enumerate(alignedSources):
180 oldCentroid = s.get(centroidKey)
181 newCoord = newWcs.pixelToSky(oldCentroid)
182 newCentroid = templateWcs.skyToPixel(newCoord)
183 if not templateBBox.contains(newCentroid):
186 s.set(coordKey, newCoord)
187 s.set(centroidKey, newCentroid)
189 for i
in reversed(deleteList):
190 del alignedSources[i]
192 return alignedSources
def warpSources(self, inputSources, newWcs, templateWcs, templateBBox)
def fitWcs(self, matches, inputWcs, inputBBox)
def warpExposure(self, inputExp, newWcs, templateWcs, templateBBox)
def run(self, inputSources, inputWcs, inputBBox, templateSources)
def matchSources(self, inputSources, templateSources)