LSST Applications g06d8191974+b5247657d3,g180d380827+b23588344e,g2079a07aa2+86d27d4dc4,g2305ad1205+0130fb9023,g29320951ab+7714a6b20a,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+8783ab7716,g48712c4677+72a8b1060b,g487adcacf7+bbaada240a,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+ecccb6240b,g5a732f18d5+53520f316c,g5ea96fc03c+33ab2bc355,g64a986408d+b5247657d3,g858d7b2824+b5247657d3,g8a8a8dda67+585e252eca,g99cad8db69+1453026da9,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+3751ca9c65,gc120e1dc64+c91d1388df,gc28159a63d+0e5473021a,gc3e9b769f7+241adb7c58,gcf0d15dbbd+8783ab7716,gdaeeff99f8+f9a426f77a,ge6526c86ff+acdbe9a537,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+b5247657d3,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
matchOptimisticBTask.py
Go to the documentation of this file.
1# This file is part of meas_astrom.
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__all__ = ["MatchOptimisticBTask", "MatchOptimisticBConfig",
23 "MatchTolerance"]
24
25import math
26
27import lsst.pex.config as pexConfig
28import lsst.pipe.base as pipeBase
29from lsst.utils.timer import timeMethod
30
31from .setMatchDistance import setMatchDistance
32from .matchOptimisticB import matchOptimisticB, MatchOptimisticBControl
33
34
36 """Stores match tolerances for use in `lsst.meas.astrom.AstrometryTask` and
37 later iterations of the matcher.
38
39 MatchOptimsiticBTask relies on a maximum distance for matching
40 set by either the default in MatchOptimisticBConfig or the 2 sigma
41 scatter found after AstrometryTask has fit for a wcs.
42
43 Parameters
44 ----------
45 maxMatchDist : `lsst.geom.Angle`
46 Current maximum distance to consider a match.
47 """
48
49 def __init__(self, maxMatchDist=None):
50 self.maxMatchDist = maxMatchDist
51
52
53class MatchOptimisticBConfig(pexConfig.Config):
54 """Configuration for MatchOptimisticBTask
55 """
56 maxMatchDistArcSec = pexConfig.RangeField(
57 doc="Maximum separation between reference objects and sources "
58 "beyond which they will not be considered a match (arcsec)",
59 dtype=float,
60 default=2.0,
61 min=0,
62 )
63 numBrightStars = pexConfig.RangeField(
64 doc="Maximum number of bright stars to use in fit.",
65 dtype=int,
66 default=150,
67 min=2,
68 )
69 minMatchedPairs = pexConfig.RangeField(
70 doc="Minimum number of matched pairs; see also minFracMatchedPairs",
71 dtype=int,
72 default=30,
73 min=2,
74 )
75 minFracMatchedPairs = pexConfig.RangeField(
76 doc="Minimum number of matched pairs as a fraction of the smaller of "
77 "the number of reference stars or the number of good sources; "
78 "the actual minimum is the smaller of this value or minMatchedPairs",
79 dtype=float,
80 default=0.3,
81 min=0,
82 max=1,
83 )
84 maxOffsetPix = pexConfig.RangeField(
85 doc="Maximum allowed shift of WCS, due to matching (pixel). "
86 "When changing this value, the LoadReferenceObjectsConfig.pixelMargin should also be updated.",
87 dtype=int,
88 default=250,
89 max=4000,
90 )
91 maxRotationDeg = pexConfig.RangeField(
92 doc="Rotation angle allowed between sources and position reference objects (degrees)",
93 dtype=float,
94 default=1.0,
95 max=6.0,
96 )
97 allowedNonperpDeg = pexConfig.RangeField(
98 doc="Allowed non-perpendicularity of x and y (degree)",
99 dtype=float,
100 default=0.2,
101 max=45.0,
102 )
103 numPointsForShape = pexConfig.Field(
104 doc="number of points to define a shape for matching",
105 dtype=int,
106 default=6,
107 )
108 maxDeterminant = pexConfig.Field(
109 doc="maximum determinant of linear transformation matrix for a usable solution",
110 dtype=float,
111 default=0.02,
112 )
113
114
115# The following block adds links to this task from the Task Documentation page.
116# \addtogroup LSST_task_documentation
117# \{
118# \page measAstrom_matchOptimisticBTask
119# \ref MatchOptimisticBTask "MatchOptimisticBTask"
120# Match sources to reference objects
121# \}
122
123
124class MatchOptimisticBTask(pipeBase.Task):
125 """Match sources to reference objects using the Optimistic Pattern Matcher
126 B algorithm of Tabur 2007.
127 """
128 ConfigClass = MatchOptimisticBConfig
129 _DefaultName = "matchObjectsToSources"
130
131 def __init__(self, **kwargs):
132 pipeBase.Task.__init__(self, **kwargs)
133
134 def filterStars(self, refCat):
135 """Extra filtering pass; subclass if desired.
136
137 Parameters
138 ----------
139 refCat : `lsst.afw.table.SimpleCatalog`
140 Catalog of reference objects.
141
142 Returns
143 -------
144 trimmedRefCat : `lsst.afw.table.SimpleCatalog`
145 Reference catalog with some filtering applied. Currently no
146 filtering is applied.
147 """
148 return refCat
149
150 @timeMethod
151 def matchObjectsToSources(self, refCat, sourceCat, wcs, sourceFluxField, refFluxField,
152 matchTolerance=None):
153 """Match sources to position reference stars.
154
155 Parameters
156 ----------
157 refCat : `lsst.afw.table.SimpleCatalog`
158 Reference catalog to match.
159 sourceCat : `lsst.afw.table.SourceCatalog`
160 Catalog of sources found on an exposure. This should already be
161 down-selected to "good"/"usable" sources in the calling Task.
162 wcs : `lsst.afw.geom.SkyWcs`
163 Current WCS of the exposure containing the sources.
164 sourceFluxField : `str`
165 Field of the sourceCat to use for flux
166 refFluxField : `str`
167 Field of the refCat to use for flux
168 matchTolerance : `lsst.meas.astrom.MatchTolerance`
169 Object containing information from previous
170 `lsst.meas.astrom.AstrometryTask` match/fit cycles for use in
171 matching. If `None` is config defaults.
172
173 Returns
174 -------
175 matchResult : `lsst.pipe.base.Struct`
176 Result struct with components
177
178 - ``matches`` : List of matches with distance below the maximum match
179 distance (`list` of `lsst.afw.table.ReferenceMatch`).
180 - ``useableSourceCat`` : Catalog of sources matched and suited for
181 WCS fitting (`lsst.afw.table.SourceCatalog`).
182 - ``matchTolerance`` : MatchTolerance object updated from this
183 match iteration (`lsst.meas.astrom.MatchTolerance`).
184 """
185 import lsstDebug
186 debug = lsstDebug.Info(__name__)
187
188 preNumObj = len(refCat)
189 refCat = self.filterStars(refCat)
190 numRefObj = len(refCat)
191
192 if self.log:
193 self.log.info("filterStars purged %d reference stars, leaving %d stars",
194 preNumObj - numRefObj, numRefObj)
195
196 if matchTolerance is None:
197 matchTolerance = MatchTolerance()
198
199 # Make a name alias here for consistency with older code, and to make
200 # it clear that this is a good/usable (cleaned) source catalog.
201 usableSourceCat = sourceCat
202
203 numUsableSources = len(usableSourceCat)
204
205 if len(usableSourceCat) == 0:
206 raise pipeBase.TaskError("No sources are usable")
207
208 minMatchedPairs = min(self.config.minMatchedPairs,
209 int(self.config.minFracMatchedPairs * min([len(refCat), len(usableSourceCat)])))
210
211 # match usable (possibly saturated) sources and then purge saturated sources from the match list
212 usableMatches = self._doMatch(
213 refCat=refCat,
214 sourceCat=usableSourceCat,
215 wcs=wcs,
216 refFluxField=refFluxField,
217 numUsableSources=numUsableSources,
218 minMatchedPairs=minMatchedPairs,
219 maxMatchDist=matchTolerance.maxMatchDist,
220 sourceFluxField=sourceFluxField,
221 verbose=debug.verbose,
222 )
223
224 # cull non-good sources
225 matches = []
226 self._getIsGoodKeys(usableSourceCat.schema)
227 for match in usableMatches:
228 if self._isGoodTest(match.second):
229 # Append the isGood match.
230 matches.append(match)
231
232 self.log.debug("Found %d usable matches, of which %d had good sources",
233 len(usableMatches), len(matches))
234
235 if len(matches) == 0:
236 raise RuntimeError("Unable to match sources")
237
238 self.log.info("Matched %d sources", len(matches))
239 if len(matches) < minMatchedPairs:
240 self.log.warning("Number of matches is smaller than request")
241
242 return pipeBase.Struct(
243 matches=matches,
244 usableSourceCat=usableSourceCat,
245 matchTolerance=matchTolerance,
246 )
247
248 def _getIsGoodKeys(self, schema):
249 """Retrieve the keys needed for the isGoodTest from the source catalog
250 schema.
251
252 Parameters
253 ----------
254 schema : `lsst.afw.table.Schema`
255 Source schema to retrieve `lsst.afw.table.Key` s from.
256 """
257 self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
258 self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
259 self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
260
261 def _isGoodTest(self, source):
262 """Test that an object is good for use in the WCS fitter.
263
264 This is a hard coded version of the isGood flag from the old SourceInfo
265 class that used to be part of this class.
266
267 Parameters
268 ----------
269 source : `lsst.afw.table.SourceRecord`
270 Source to test.
271
272 Returns
273 -------
274 isGood : `bool`
275 Source passes CCD edge and saturated tests.
276 """
277 return (not source.get(self.edgeKey)
278 and not source.get(self.interpolatedCenterKey)
279 and not source.get(self.saturatedKey))
280
281 @timeMethod
282 def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs,
283 maxMatchDist, sourceFluxField, verbose):
284 """Implementation of matching sources to position reference stars.
285
286 Unlike matchObjectsToSources, this method does not check if the sources
287 are suitable.
288
289 Parameters
290 ----------
291 refCat : `lsst.afw.table.SimpleCatalog`
292 Catalog of reference objects.
293 sourceCat : `lsst.afw.table.SourceCatalog`
294 Catalog of detected sources.
295 wcs : `lsst.afw.geom.SkyWcs`
296 Current best WCS of the image.
297 refFluxFioeld : `str`
298 Name of flux field in refCat to use.
299 numUsableSources : `int`
300 Total number of source usable for matching.
301 mintMatchPairs : `int`
302 Minimum number of objects to match between the refCat and sourceCat
303 to consider a valid match.
304 maxMatchDist : `lsst.geom.Angle`
305 Maximum separation to considering a reference and a source a match.
306 sourceFluxField : `str`
307 Name of source catalog flux field.
308 verbose : `bool`
309 Print diagnostic information std::cout
310
311 Returns
312 -------
313 matches : `list` of `lsst.afw.table.ReferenceMatch`
314 """
315 numSources = len(sourceCat)
316 posRefBegInd = numUsableSources - numSources
317 if maxMatchDist is None:
318 maxMatchDistArcSec = self.config.maxMatchDistArcSec
319 else:
320 maxMatchDistArcSec = min(maxMatchDist.asArcseconds(), self.config.maxMatchDistArcSec)
321 configMatchDistPix = maxMatchDistArcSec/wcs.getPixelScale().asArcseconds()
322
323 matchControl = MatchOptimisticBControl()
324 matchControl.refFluxField = refFluxField
325 matchControl.sourceFluxField = sourceFluxField
326 matchControl.numBrightStars = self.config.numBrightStars
327 matchControl.minMatchedPairs = self.config.minMatchedPairs
328 matchControl.maxOffsetPix = self.config.maxOffsetPix
329 matchControl.numPointsForShape = self.config.numPointsForShape
330 matchControl.maxDeterminant = self.config.maxDeterminant
331
332 for maxRotInd in range(4):
333 matchControl.maxRotationDeg = self.config.maxRotationDeg * math.pow(2.0, 0.5*maxRotInd)
334 for matchRadInd in range(3):
335 matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd)
336
337 for angleDiffInd in range(3):
338 matchControl.allowedNonperpDeg = self.config.allowedNonperpDeg*(angleDiffInd+1)
339 matches = matchOptimisticB(
340 refCat,
341 sourceCat,
342 matchControl,
343 wcs,
344 posRefBegInd,
345 verbose,
346 )
347 if matches is not None and len(matches) > 0:
348 setMatchDistance(matches)
349 return matches
350 return matches
int min
matchObjectsToSources(self, refCat, sourceCat, wcs, sourceFluxField, refFluxField, matchTolerance=None)
_doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs, maxMatchDist, sourceFluxField, verbose)