lsst.pipe.tasks
g2c8cee2ce7+363d2db7ea
Toggle main menu visibility
Loading...
Searching...
No Matches
python
lsst
pipe
tasks
registerImage.py
Go to the documentation of this file.
1
# This file is part of pipe_tasks.
2
#
3
# Developed for the LSST Data Management System.
4
# This product includes software developed by the LSST Project
5
# (https://www.lsst.org).
6
# See the COPYRIGHT file at the top-level directory of this distribution
7
# for details of code ownership.
8
#
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22
"""
23
This module contains a Task to register (align) multiple images.
24
"""
25
__all__ = [
"RegisterTask"
,
"RegisterConfig"
]
26
27
import
math
28
import
numpy
29
30
from
lsst.pex.config
import
Config, Field, ConfigField
31
from
lsst.pipe.base
import
Task, Struct
32
from
lsst.meas.astrom.sip
import
makeCreateWcsWithSip
33
from
lsst.afw.math
import
Warper
34
35
import
lsst.geom
as
geom
36
import
lsst.afw.table
as
afwTable
37
38
39
class
RegisterConfig
(Config):
40
"""Configuration for RegisterTask."""
41
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"
)
47
48
49
class
RegisterTask
(Task):
50
"""Task to register (align) multiple images.
51
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.
55
"""
56
57
ConfigClass = RegisterConfig
58
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.
64
65
Parameters
66
----------
67
inputSources : `lsst.afw.table.SourceCatalog`
68
Sources from input exposure.
69
inputWcs : `lsst.afw.geom.SkyWcs`
70
Wcs of input exposure.
71
inputBBox : `lsst.geom.Box`
72
Bounding box of input exposure.
73
templateSources : `lsst.afw.table.SourceCatalog`
74
Sources from template exposure.
75
76
Returns
77
-------
78
result : `lsst.pipe.base.Struct`
79
Results as a struct with attributes:
80
81
``matches``
82
Matches between sources (`list`).
83
``wcs``
84
Wcs for input in frame of template (`lsst.afw.geom.SkyWcs`).
85
"""
86
matches = self.
matchSources
(inputSources, templateSources)
87
wcs = self.
fitWcs
(matches, inputWcs, inputBBox)
88
return
Struct(matches=matches, wcs=wcs)
89
90
def
matchSources
(self, inputSources, templateSources):
91
"""Match sources between the input and template.
92
93
The order of the input arguments matters (because the later Wcs
94
fitting assumes a particular order).
95
96
Parameters
97
----------
98
inputSources : `lsst.afw.table.SourceCatalog`
99
Source catalog of the input frame.
100
templateSources : `lsst.afw.table.SourceCatalog`
101
Source of the target frame.
102
103
Returns
104
-------
105
matches: `list`
106
Match list.
107
"""
108
matches = afwTable.matchRaDec(templateSources, inputSources,
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"
)
114
return
matches
115
116
def
fitWcs
(self, matches, inputWcs, inputBBox):
117
"""Fit Wcs to matches.
118
119
The fitting includes iterative sigma-clipping.
120
121
Parameters
122
----------
123
matches : `list`
124
List of matches (first is target, second is input).
125
inputWcs : `lsst.afw.geom.SkyWcs`
126
Original input Wcs.
127
inputBBox : `lsst.geom.Box`
128
Bounding box of input exposure.
129
130
Returns
131
-------
132
wcs: `lsst.afw.geom.SkyWcs`
133
Wcs fitted to matches.
134
"""
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
145
m
in
copyMatches]
146
dr = numpy.array(dr)
147
rms = math.sqrt((dr*dr).mean())
# RMS from zero
148
rms = max(rms, 1.0e-9)
# Don't believe any RMS smaller than this
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)
153
if
numBad == 0:
154
break
155
copyMatches = type(matches)(copyMatches[i]
for
i
in
good)
156
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()
164
return
wcs
165
166
def
warpExposure
(self, inputExp, newWcs, templateWcs, templateBBox):
167
"""Warp input exposure to template frame.
168
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
171
frame.
172
173
Parameters
174
----------
175
inputExp : `lsst.afw.image.Exposure`
176
Input exposure, to be warped.
177
newWcs : `lsst.afw.geom.SkyWcs`
178
Revised Wcs for input exposure.
179
templateWcs : `lsst.afw.geom.SkyWcs`
180
Target Wcs.
181
templateBBox : `lsst.geom.Box`
182
Target bounding box.
183
184
Returns
185
-------
186
alignedExp : `lsst.afw.image.Exposure`
187
Warped exposure.
188
"""
189
warper = Warper.fromConfig(self.config.warper)
190
copyExp = inputExp.Factory(inputExp.getMaskedImage(), newWcs)
191
alignedExp = warper.warpExposure(templateWcs, copyExp, destBBox=templateBBox)
192
return
alignedExp
193
194
def
warpSources
(self, inputSources, newWcs, templateWcs, templateBBox):
195
"""Warp sources to the new frame.
196
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
199
pixel coordinates.
200
201
Parameters
202
----------
203
inputSources : `lsst.afw.table.SourceCatalog`
204
Sources on input exposure, to be warped.
205
newWcs : `lsst.afw.geom.SkyWcs`
206
Revised Wcs for input exposure.
207
templateWcs : `lsst.afw.geom.SkyWcs`
208
Target Wcs.
209
templateBBox : `lsst.geom.Box`
210
Target bounding box.
211
212
Returns
213
-------
214
alignedSources : `lsst.afw.table.SourceCatalog`
215
Warped sources.
216
"""
217
alignedSources = inputSources.copy(
True
)
218
if
not
isinstance(templateBBox,
geom.Box2D
):
219
# There is no method Box2I::contains(Point2D)
220
templateBBox =
geom.Box2D
(templateBBox)
221
table = alignedSources.getTable()
222
coordKey = table.getCoordKey()
223
centroidKey = table.getCentroidSlot().getMeasKey()
224
deleteList = []
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):
230
deleteList.append(i)
231
continue
232
s.set(coordKey, newCoord)
233
s.set(centroidKey, newCentroid)
234
235
for
i
in
reversed(deleteList):
# Delete from back so we don't change indices
236
del alignedSources[i]
237
238
return
alignedSources
lsst::geom::Box2D
lsst.pipe.tasks.registerImage.RegisterConfig
Definition
registerImage.py:39
lsst.pipe.tasks.registerImage.RegisterTask
Definition
registerImage.py:49
lsst.pipe.tasks.registerImage.RegisterTask.matchSources
matchSources(self, inputSources, templateSources)
Definition
registerImage.py:90
lsst.pipe.tasks.registerImage.RegisterTask.warpExposure
warpExposure(self, inputExp, newWcs, templateWcs, templateBBox)
Definition
registerImage.py:166
lsst.pipe.tasks.registerImage.RegisterTask.fitWcs
fitWcs(self, matches, inputWcs, inputBBox)
Definition
registerImage.py:116
lsst.pipe.tasks.registerImage.RegisterTask.warpSources
warpSources(self, inputSources, newWcs, templateWcs, templateBBox)
Definition
registerImage.py:194
lsst.pipe.tasks.registerImage.RegisterTask.run
run(self, inputSources, inputWcs, inputBBox, templateSources)
Definition
registerImage.py:59
lsst::afw::math
lsst::afw::table
lsst::geom
lsst::meas::astrom::sip
lsst::pex::config
Generated on
for lsst.pipe.tasks by
1.17.0