23This module contains a Task to register (align) multiple images.
25__all__ = [
"RegisterTask",
"RegisterConfig"]
40 """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")
50 """Task to register (align) multiple images.
52 The 'run' method provides a revised Wcs
from matches
and fitting sources.
53 Additional methods are provided
as a convenience to warp an exposure
54 (
'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
61 The sources must have RA,Dec set, and accurate to within the
62 'matchRadius' of the configuration
in order to facilitate source
63 matching. We fit a new Wcs, but do NOT set it
in the input exposure.
68 Sources
from input exposure.
70 Wcs of input exposure.
71 inputBBox : `lsst.geom.Box`
72 Bounding box of input exposure.
74 Sources
from template exposure.
78 result : `lsst.pipe.base.Struct`
79 Results
as a struct
with attributes:
82 Matches between sources (`list`).
86 matches = self.matchSources(inputSources, templateSources)
87 wcs = self.fitWcs(matches, inputWcs, inputBBox)
88 return Struct(matches=matches, wcs=wcs)
91 """Match sources between the input and template.
93 The order of the input arguments matters (because the later Wcs
94 fitting assumes a particular order).
99 Source catalog of the input frame.
101 Source of the target frame.
109 self.config.matchRadius*geom.arcseconds)
110 self.log.info("Matching within %.1f arcsec: %d matches", self.config.matchRadius, len(matches))
111 self.metadata[
"MATCH_NUM"] = len(matches)
112 if len(matches) == 0:
113 raise RuntimeError(
"Unable to match source catalogs")
116 def fitWcs(self, matches, inputWcs, inputBBox):
117 """Fit Wcs to matches.
119 The fitting includes iterative sigma-clipping.
124 List of matches (first is target, second
is input).
127 inputBBox : `lsst.geom.Box`
128 Bounding box of input exposure.
133 Wcs fitted to matches.
135 copyMatches = type(matches)(matches)
136 refCoordKey = copyMatches[0].first.getTable().getCoordKey()
137 inCentroidKey = copyMatches[0].second.getTable().getCentroidSlot().getMeasKey()
138 for i
in range(self.config.sipIter):
139 sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
140 self.log.debug(
"Registration WCS RMS iteration %d: %f pixels",
141 i, sipFit.getScatterInPixels())
142 wcs = sipFit.getNewWcs()
143 dr = [m.first.get(refCoordKey).separation(
144 wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds()
for
147 rms = math.sqrt((dr*dr).mean())
148 rms =
max(rms, 1.0e-9)
149 self.log.debug(
"Registration iteration %d: rms=%f", i, rms)
150 good = numpy.where(dr < self.config.sipRej*rms)[0]
151 numBad = len(copyMatches) - len(good)
152 self.log.debug(
"Registration iteration %d: rejected %d", i, numBad)
155 copyMatches =
type(matches)(copyMatches[i]
for i
in good)
157 sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
158 self.log.info(
"Registration WCS: final WCS RMS=%f pixels from %d matches",
159 sipFit.getScatterInPixels(), len(copyMatches))
160 self.metadata[
"SIP_RMS"] = sipFit.getScatterInPixels()
161 self.metadata[
"SIP_GOOD"] = len(copyMatches)
162 self.metadata[
"SIP_REJECTED"] = len(matches) - len(copyMatches)
163 wcs = sipFit.getNewWcs()
166 def warpExposure(self, inputExp, newWcs, templateWcs, templateBBox):
167 """Warp input exposure to template frame.
169 There are a variety of data attached to the exposure (e.g., PSF, PhotoCalib
170 and other metadata), but we do
not attempt to warp these to the template
176 Input exposure, to be warped.
178 Revised Wcs
for input exposure.
181 templateBBox : `lsst.geom.Box`
189 warper = Warper.fromConfig(self.config.warper)
190 copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
191 alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
194 def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
195 """Warp sources to the new frame.
197 It would be difficult to transform all possible quantities of potential
198 interest between the two frames. We therefore update only the sky and
204 Sources on input exposure, to be warped.
206 Revised Wcs
for input exposure.
209 templateBBox : `lsst.geom.Box`
217 alignedSources = inputSources.copy(True)
221 table = alignedSources.getTable()
222 coordKey = table.getCoordKey()
223 centroidKey = table.getCentroidSlot().getMeasKey()
225 for i, s
in enumerate(alignedSources):
226 oldCentroid = s.get(centroidKey)
227 newCoord = newWcs.pixelToSky(oldCentroid)
228 newCentroid = templateWcs.skyToPixel(newCoord)
229 if not templateBBox.contains(newCentroid):
232 s.set(coordKey, newCoord)
233 s.set(centroidKey, newCentroid)
235 for i
in reversed(deleteList):
236 del alignedSources[i]
238 return alignedSources
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A floating-point coordinate rectangle geometry.
def matchSources(self, inputSources, templateSources)
def fitWcs(self, matches, inputWcs, inputBBox)
def warpSources(self, inputSources, newWcs, templateWcs, templateBBox)
std::vector< Match< typename Cat1::Record, typename Cat2::Record > > matchRaDec(Cat1 const &cat1, Cat2 const &cat2, lsst::geom::Angle radius, MatchControl const &mc=MatchControl())
Compute all tuples (s1,s2,d) where s1 belings to cat1, s2 belongs to cat2 and d, the distance between...