24 This module contains a Task to register (align) multiple images.
27 __all__ = [
"RegisterTask",
"RegisterConfig"]
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")
52 Task to register (align) multiple images.
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.
58 ConfigClass = RegisterConfig
60 def run(self, inputSources, inputWcs, inputBBox, templateSources):
61 """Register (align) an input exposure to the template
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.
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,
75 matches = self.
matchSources(inputSources, templateSources)
76 wcs = self.
fitWcs(matches, inputWcs, inputBBox)
77 return Struct(matches=matches, wcs=wcs)
80 """Match sources between the input and template
82 The order of the input arguments matters (because the later Wcs
83 fitting assumes a particular order).
85 @param inputSources: Source catalog of the input frame
86 @param templateSources: Source of the target frame
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))
94 raise RuntimeError(
"Unable to match source catalogs")
97 def fitWcs(self, matches, inputWcs, inputBBox):
100 The fitting includes iterative sigma-clipping.
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
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):
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
119 rms = math.sqrt((dr*dr).mean())
120 rms = max(rms, 1.0e-9)
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))
127 copyMatches = type(matches)(copyMatches[i]
for i
in good)
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()
139 """Warp input exposure to template frame
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
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
151 warper = Warper.fromConfig(self.config.warper)
152 copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
153 alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
156 def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
157 """Warp sources to the new frame
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
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
169 alignedSources = inputSources.copy(
True)
173 table = alignedSources.getTable()
174 coordKey = table.getCoordKey()
175 centroidKey = table.getCentroidKey()
176 centroidErrKey = table.getCentroidErrKey()
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):
185 s.set(coordKey, newCoord)
186 s.set(centroidKey, newCentroid)
188 for i
in reversed(deleteList):
189 del alignedSources[i]
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.