24 This module contains a Task to register (align) multiple images.
26 from builtins
import range
28 __all__ = [
"RegisterTask",
"RegisterConfig"]
43 """Configuration for RegisterTask"""
44 matchRadius = Field(dtype=float, default=1.0, doc=
"Matching radius (arcsec)", check=
lambda x: x > 0)
45 sipOrder = Field(dtype=int, default=4, doc=
"Order for SIP WCS", check=
lambda x: x > 1)
46 sipIter = Field(dtype=int, default=3, doc=
"Rejection iterations for SIP WCS", check=
lambda x: x > 0)
47 sipRej = Field(dtype=float, default=3.0, doc=
"Rejection threshold for SIP WCS", check=
lambda x: x > 0)
48 warper = ConfigField(dtype=Warper.ConfigClass, doc=
"Configuration for warping")
53 Task to register (align) multiple images.
55 The 'run' method provides a revised Wcs from matches and fitting sources.
56 Additional methods are provided as a convenience to warp an exposure
57 ('warpExposure') and sources ('warpSources') with the new Wcs.
59 ConfigClass = RegisterConfig
61 def run(self, inputSources, inputWcs, inputBBox, templateSources):
62 """Register (align) an input exposure to the template
64 The sources must have RA,Dec set, and accurate to within the
65 'matchRadius' of the configuration in order to facilitate source
66 matching. We fit a new Wcs, but do NOT set it in the input exposure.
68 @param inputSources: Sources from input exposure
69 @param inputWcs: Wcs of input exposure
70 @param inputBBox: Bounding box of input exposure
71 @param templateSources: Sources from template exposure
72 @return Struct(matches: Matches between sources,
73 wcs: Wcs for input in frame of template,
76 matches = self.
matchSources(inputSources, templateSources)
77 wcs = self.
fitWcs(matches, inputWcs, inputBBox)
78 return Struct(matches=matches, wcs=wcs)
81 """Match sources between the input and template
83 The order of the input arguments matters (because the later Wcs
84 fitting assumes a particular order).
86 @param inputSources: Source catalog of the input frame
87 @param templateSources: Source of the target frame
91 self.config.matchRadius*afwGeom.arcseconds)
92 self.log.info(
"Matching within %.1f arcsec: %d matches" % (self.config.matchRadius, len(matches)))
93 self.metadata.set(
"MATCH_NUM", len(matches))
95 raise RuntimeError(
"Unable to match source catalogs")
98 def fitWcs(self, matches, inputWcs, inputBBox):
101 The fitting includes iterative sigma-clipping.
103 @param matches: List of matches (first is target, second is input)
104 @param inputWcs: Original input Wcs
105 @param inputBBox: Bounding box of input image
108 copyMatches = type(matches)(matches)
109 refCoordKey = copyMatches[0].first.getTable().getCoordKey()
110 inCentroidKey = copyMatches[0].second.getTable().getCentroidKey()
111 for i
in range(self.config.sipIter):
113 self.log.debug(
"Registration WCS RMS iteration %d: %f pixels",
114 i, sipFit.getScatterInPixels())
115 wcs = sipFit.getNewWcs()
116 dr = [m.first.get(refCoordKey).angularSeparation(
117 wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds()
for
120 rms = math.sqrt((dr*dr).mean())
121 rms = max(rms, 1.0e-9)
122 self.log.debug(
"Registration iteration %d: rms=%f", i, rms)
123 good = numpy.where(dr < self.config.sipRej*rms)[0]
124 numBad = len(copyMatches) - len(good)
125 self.log.debug(
"Registration iteration %d: rejected %d", i, numBad)
128 copyMatches = type(matches)(copyMatches[i]
for i
in good)
131 self.log.info(
"Registration WCS: final WCS RMS=%f pixels from %d matches" %
132 (sipFit.getScatterInPixels(), len(copyMatches)))
133 self.metadata.set(
"SIP_RMS", sipFit.getScatterInPixels())
134 self.metadata.set(
"SIP_GOOD", len(copyMatches))
135 self.metadata.set(
"SIP_REJECTED", len(matches) - len(copyMatches))
136 wcs = sipFit.getNewWcs()
140 """Warp input exposure to template frame
142 There are a variety of data attached to the exposure (e.g., PSF, Calib
143 and other metadata), but we do not attempt to warp these to the template
146 @param inputExp: Input exposure, to be warped
147 @param newWcs: Revised Wcs for input exposure
148 @param templateWcs: Target Wcs
149 @param templateBBox: Target bounding box
150 @return Warped exposure
152 warper = Warper.fromConfig(self.config.warper)
153 copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
154 alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
157 def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
158 """Warp sources to the new frame
160 It would be difficult to transform all possible quantities of potential
161 interest between the two frames. We therefore update only the sky and
164 @param inputSources: Sources on input exposure, to be warped
165 @param newWcs: Revised Wcs for input exposure
166 @param templateWcs: Target Wcs
167 @param templateBBox: Target bounding box
168 @return Warped sources
170 alignedSources = inputSources.copy(
True)
174 table = alignedSources.getTable()
175 coordKey = table.getCoordKey()
176 centroidKey = table.getCentroidKey()
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
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric)
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.
A floating-point coordinate rectangle geometry.