24 This module contains a Task to register (align) multiple images. 26 __all__ = [
"RegisterTask",
"RegisterConfig"]
41 """Configuration for RegisterTask""" 42 matchRadius =
Field(dtype=float, default=1.0, doc=
"Matching radius (arcsec)", check=
lambda x: x > 0)
43 sipOrder =
Field(dtype=int, default=4, doc=
"Order for SIP WCS", check=
lambda x: x > 1)
44 sipIter =
Field(dtype=int, default=3, doc=
"Rejection iterations for SIP WCS", check=
lambda x: x > 0)
45 sipRej =
Field(dtype=float, default=3.0, doc=
"Rejection threshold for SIP WCS", check=
lambda x: x > 0)
46 warper =
ConfigField(dtype=Warper.ConfigClass, doc=
"Configuration for warping")
51 Task to register (align) multiple images. 53 The 'run' method provides a revised Wcs from matches and fitting sources. 54 Additional methods are provided as a convenience to warp an exposure 55 ('warpExposure') and sources ('warpSources') with the new Wcs. 57 ConfigClass = RegisterConfig
59 def run(self, inputSources, inputWcs, inputBBox, templateSources):
60 """Register (align) an input exposure to the template 62 The sources must have RA,Dec set, and accurate to within the 63 'matchRadius' of the configuration in order to facilitate source 64 matching. We fit a new Wcs, but do NOT set it in the input exposure. 66 @param inputSources: Sources from input exposure 67 @param inputWcs: Wcs of input exposure 68 @param inputBBox: Bounding box of input exposure 69 @param templateSources: Sources from template exposure 70 @return Struct(matches: Matches between sources, 71 wcs: Wcs for input in frame of template, 74 matches = self.
matchSources(inputSources, templateSources)
75 wcs = self.
fitWcs(matches, inputWcs, inputBBox)
76 return Struct(matches=matches, wcs=wcs)
79 """Match sources between the input and template 81 The order of the input arguments matters (because the later Wcs 82 fitting assumes a particular order). 84 @param inputSources: Source catalog of the input frame 85 @param templateSources: Source of the target frame 89 self.
config.matchRadius*afwGeom.arcseconds)
90 self.
log.
info(
"Matching within %.1f arcsec: %d matches" % (self.
config.matchRadius, len(matches)))
93 raise RuntimeError(
"Unable to match source catalogs")
96 def fitWcs(self, matches, inputWcs, inputBBox):
99 The fitting includes iterative sigma-clipping. 101 @param matches: List of matches (first is target, second is input) 102 @param inputWcs: Original input Wcs 103 @param inputBBox: Bounding box of input image 106 copyMatches =
type(matches)(matches)
107 refCoordKey = copyMatches[0].first.getTable().getCoordKey()
108 inCentroidKey = copyMatches[0].second.getTable().getCentroidKey()
109 for i
in range(self.
config.sipIter):
111 self.
log.
debug(
"Registration WCS RMS iteration %d: %f pixels",
112 i, sipFit.getScatterInPixels())
113 wcs = sipFit.getNewWcs()
114 dr = [m.first.get(refCoordKey).separation(
115 wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds()
for 118 rms = math.sqrt((dr*dr).mean())
119 rms =
max(rms, 1.0e-9)
120 self.
log.
debug(
"Registration iteration %d: rms=%f", i, rms)
121 good = numpy.where(dr < self.
config.sipRej*rms)[0]
122 numBad = len(copyMatches) - len(good)
123 self.
log.
debug(
"Registration iteration %d: rejected %d", i, numBad)
126 copyMatches =
type(matches)(copyMatches[i]
for i
in good)
129 self.
log.
info(
"Registration WCS: final WCS RMS=%f pixels from %d matches" %
130 (sipFit.getScatterInPixels(), len(copyMatches)))
131 self.
metadata.
set(
"SIP_RMS", sipFit.getScatterInPixels())
133 self.
metadata.
set(
"SIP_REJECTED", len(matches) - len(copyMatches))
134 wcs = sipFit.getNewWcs()
138 """Warp input exposure to template frame 140 There are a variety of data attached to the exposure (e.g., PSF, PhotoCalib 141 and other metadata), but we do not attempt to warp these to the template 144 @param inputExp: Input exposure, to be warped 145 @param newWcs: Revised Wcs for input exposure 146 @param templateWcs: Target Wcs 147 @param templateBBox: Target bounding box 148 @return Warped exposure 150 warper = Warper.fromConfig(self.
config.warper)
151 copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
152 alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
155 def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
156 """Warp sources to the new frame 158 It would be difficult to transform all possible quantities of potential 159 interest between the two frames. We therefore update only the sky and 162 @param inputSources: Sources on input exposure, to be warped 163 @param newWcs: Revised Wcs for input exposure 164 @param templateWcs: Target Wcs 165 @param templateBBox: Target bounding box 166 @return Warped sources 168 alignedSources = inputSources.copy(
True)
172 table = alignedSources.getTable()
173 coordKey = table.getCoordKey()
174 centroidKey = table.getCentroidKey()
176 for i, s
in enumerate(alignedSources):
177 oldCentroid = s.get(centroidKey)
178 newCoord = newWcs.pixelToSky(oldCentroid)
179 newCentroid = templateWcs.skyToPixel(newCoord)
180 if not templateBBox.contains(newCentroid):
183 s.set(coordKey, newCoord)
184 s.set(centroidKey, newCentroid)
186 for i
in reversed(deleteList):
187 del alignedSources[i]
189 return alignedSources
def warpSources(self, inputSources, newWcs, templateWcs, templateBBox)
A floating-point coordinate rectangle geometry.
def fitWcs(self, matches, inputWcs, inputBBox)
def warpExposure(self, inputExp, newWcs, templateWcs, templateBBox)
daf::base::PropertySet * set
template SourceMatchVector matchRaDec(SourceCatalog const &, lsst::geom::Angle, MatchControl const &)
CreateWcsWithSip< MatchT > makeCreateWcsWithSip(std::vector< MatchT > const &matches, afw::geom::SkyWcs const &linearWcs, int const order, geom::Box2I const &bbox=geom::Box2I(), int const ngrid=0)
Factory function for CreateWcsWithSip.
def run(self, inputSources, inputWcs, inputBBox, templateSources)
def matchSources(self, inputSources, templateSources)