1 from __future__
import absolute_import, division, print_function
2 from builtins
import range
3 from builtins
import object
11 from .setMatchDistance
import setMatchDistance
12 from .astromLib
import matchOptimisticB, MatchOptimisticBControl
14 __all__ = [
"MatchOptimisticBTask",
"MatchOptimisticBConfig",
"SourceInfo"]
18 """Configuration for MatchOptimisticBTask
20 sourceFluxType = pexConfig.Field(
21 doc=
"Type of source flux; typically one of Ap or Psf",
25 maxMatchDistArcSec = pexConfig.RangeField(
26 doc=
"Maximum separation between reference objects and sources "
27 "beyond which they will not be considered a match (arcsec)",
32 numBrightStars = pexConfig.RangeField(
33 doc=
"Number of bright stars to use",
38 minMatchedPairs = pexConfig.RangeField(
39 doc=
"Minimum number of matched pairs; see also minFracMatchedPairs",
44 minFracMatchedPairs = pexConfig.RangeField(
45 doc=
"Minimum number of matched pairs as a fraction of the smaller of "
46 "the number of reference stars or the number of good sources; "
47 "the actual minimum is the smaller of this value or minMatchedPairs",
53 maxOffsetPix = pexConfig.RangeField(
54 doc=
"Maximum allowed shift of WCS, due to matching (pixel)",
59 maxRotationDeg = pexConfig.RangeField(
60 doc=
"Rotation angle allowed between sources and position reference objects (degrees)",
65 allowedNonperpDeg = pexConfig.RangeField(
66 doc=
"Allowed non-perpendicularity of x and y (degree)",
71 numPointsForShape = pexConfig.Field(
72 doc=
"number of points to define a shape for matching",
76 maxDeterminant = pexConfig.Field(
77 doc=
"maximum determinant of linear transformation matrix for a usable solution",
81 minSnr = pexConfig.Field(
83 doc=
"Minimum allowed signal-to-noise ratio for sources used for matching "
84 "(in the flux specified by sourceFluxType); <=0 for no limit",
90 """Provide usability tests and catalog keys for sources in a source catalog
93 - centroidKey key for centroid
94 - centroidFlagKey key for flag that is True if centroid is valid
95 - edgeKey key for field that is True if source is near an edge
96 - saturatedKey key for field that is True if source has any saturated pixels
97 - interpolatedCenterKey key for field that is True if center pixels have interpolated values;
98 interpolation is triggered by saturation, cosmic rays and bad pixels, and possibly other reasons
99 - fluxField name of flux field
101 @throw RuntimeError if schema version unsupported or a needed field is not found
104 def __init__(self, schema, fluxType="Ap", minSnr=50):
105 """Construct a SourceInfo
107 @param[in] schema source catalog schema
108 @param[in] fluxType flux type: typically one of "Ap" or "Psf"
109 @param[in] minSnr minimum allowed signal-to-noise ratio for sources used for matching
110 (in the flux specified by fluxType); <=0 for no limit
112 @throw RuntimeError if the flux field is not found
116 self.
edgeKey = schema[
"base_PixelFlags_flag_edge"].asKey()
118 fluxPrefix =
"slot_%sFlux_" % (fluxType,)
120 self.
fluxKey = schema[fluxPrefix +
"flux"].asKey()
128 raise RuntimeError(
"Could not find flux field %s in source schema" % (self.
fluxField,))
131 """Return True if source is likely multiple sources
135 footprint = source.getFootprint()
136 return footprint
is not None and len(footprint.getPeaks()) > 1
139 """Return True if the source has a valid centroid
142 return np.all(np.isfinite(centroid))
and not source.getCentroidFlag()
145 """Return True if the source is usable for matching, even if it may have a poor centroid
147 For a source to be usable it must:
148 - have a valid centroid
150 - have a valid flux (of the type specified in this object's constructor)
151 - have adequate signal-to-noise
160 """Return True if source is usable for matching (as per isUsable) and likely has a good centroid
162 The additional tests for a good centroid, beyond isUsable, are:
163 - not interpolated in the center (this includes saturated sources,
164 so we don't test separately for that)
169 and not source.get(self.
edgeKey)
181 """!Match sources to reference objects
183 @anchor MatchOptimisticBTask_
185 @section meas_astrom_matchOptimisticB_Contents Contents
187 - @ref meas_astrom_matchOptimisticB_Purpose
188 - @ref meas_astrom_matchOptimisticB_Initialize
189 - @ref meas_astrom_matchOptimisticB_IO
190 - @ref meas_astrom_matchOptimisticB_Config
191 - @ref meas_astrom_matchOptimisticB_Example
192 - @ref meas_astrom_matchOptimisticB_Debug
194 @section meas_astrom_matchOptimisticB_Purpose Description
196 Match sources to reference objects. This is often done as a preliminary step to fitting an astrometric
197 or photometric solution. For details about the matching algorithm see matchOptimisticB.h
199 @section meas_astrom_matchOptimisticB_Initialize Task initialisation
201 @copydoc \_\_init\_\_
203 @section meas_astrom_matchOptimisticB_IO Invoking the Task
205 @copydoc matchObjectsToSources
207 @section meas_astrom_matchOptimisticB_Config Configuration parameters
209 See @ref MatchOptimisticBConfig
211 To modify the tests for usable sources and good sources, subclass SourceInfo and
212 set MatchOptimisticBTask.SourceInfoClass to your subclass.
214 @section meas_astrom_matchOptimisticB_Example A complete example of using MatchOptimisticBTask
216 MatchOptimisticBTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
217 See \ref meas_photocal_photocal_Example.
219 @section meas_astrom_matchOptimisticB_Debug Debug variables
221 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
222 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
225 The available variables in MatchOptimisticBTask are:
227 <DT> @c verbose (bool)
228 <DD> If True then the matcher prints debug messages to stdout
231 To investigate the @ref meas_astrom_matchOptimisticB_Debug, put something like
235 debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
236 if name == "lsst.pipe.tasks.astrometry":
241 lsstDebug.Info = DebugInfo
243 into your debug.py file and run this task with the @c --debug flag.
245 ConfigClass = MatchOptimisticBConfig
246 _DefaultName =
"matchObjectsToSources"
247 SourceInfoClass = SourceInfo
250 """Extra filtering pass; subclass if desired
256 """!Match sources to position reference stars
258 @param[in] refCat catalog of reference objects that overlap the exposure; reads fields for:
260 - the specified flux field
261 @param[in] sourceCat catalog of sources found on an exposure; reads fields for:
266 - aperture flux, if found, else PSF flux
267 @param[in] wcs estimated WCS
268 @param[in] refFluxField field of refCat to use for flux
269 @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
270 (an lsst.afw.geom.Angle); if specified then the smaller of config.maxMatchDistArcSec or
271 maxMatchDist is used; if None then config.maxMatchDistArcSec is used
272 @return an lsst.pipe.base.Struct with fields:
273 - matches a list of matches, each instance of lsst.afw.table.ReferenceMatch
274 - usableSourcCat a catalog of sources potentially usable for matching.
275 For this fitter usable sources include unresolved sources not too near the edge.
276 It includes saturated sources, even those these are removed from the final match list,
277 because saturated sources may be used to determine the match list.
282 preNumObj = len(refCat)
284 numRefObj = len(refCat)
287 self.log.info(
"filterStars purged %d reference stars, leaving %d stars" %
288 (preNumObj - numRefObj, numRefObj))
291 schema=sourceCat.schema,
292 fluxType=self.config.sourceFluxType,
293 minSnr=self.config.minSnr,
297 numSources = len(sourceCat)
299 usableSourceCat.extend(s
for s
in sourceCat
if sourceInfo.isUsable(s))
300 numUsableSources = len(usableSourceCat)
301 self.log.info(
"Purged %d unusable sources, leaving %d usable sources" %
302 (numSources - numUsableSources, numUsableSources))
304 if len(usableSourceCat) == 0:
305 raise pipeBase.TaskError(
"No sources are usable")
309 minMatchedPairs = min(self.config.minMatchedPairs,
310 int(self.config.minFracMatchedPairs * min([len(refCat), len(usableSourceCat)])))
315 sourceCat=usableSourceCat,
317 refFluxField=refFluxField,
318 numUsableSources=numUsableSources,
319 minMatchedPairs=minMatchedPairs,
320 maxMatchDist=maxMatchDist,
321 sourceInfo=sourceInfo,
322 verbose=debug.verbose,
327 for match
in usableMatches:
328 if sourceInfo.isGood(match.second):
329 matches.append(match)
331 self.log.debug(
"Found %d usable matches, of which %d had good sources",
332 len(usableMatches), len(matches))
334 if len(matches) == 0:
335 raise RuntimeError(
"Unable to match sources")
337 self.log.info(
"Matched %d sources" % len(matches))
338 if len(matches) < minMatchedPairs:
339 self.log.warn(
"Number of matches is smaller than request")
341 return pipeBase.Struct(
343 usableSourceCat=usableSourceCat,
347 def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs,
348 maxMatchDist, sourceInfo, verbose):
349 """!Implementation of matching sources to position reference stars
351 Unlike matchObjectsToSources, this method does not check if the sources are suitable.
353 @param[in] refCat catalog of position reference stars that overlap an exposure
354 @param[in] sourceCat catalog of sources found on the exposure
355 @param[in] wcs estimated WCS of exposure
356 @param[in] refFluxField field of refCat to use for flux
357 @param[in] numUsableSources number of usable sources (sources with known centroid
358 that are not near the edge, but may be saturated)
359 @param[in] minMatchedPairs minimum number of matches
360 @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
361 (an lsst.afw.geom.Angle); if specified then the smaller of config.maxMatchDistArcSec or
362 maxMatchDist is used; if None then config.maxMatchDistArcSec is used
363 @param[in] sourceInfo SourceInfo for the sourceCat
364 @param[in] verbose true to print diagnostic information to std::cout
366 @return a list of matches, an instance of lsst.afw.table.ReferenceMatch
368 numSources = len(sourceCat)
369 posRefBegInd = numUsableSources - numSources
370 if maxMatchDist
is None:
371 maxMatchDistArcSec = self.config.maxMatchDistArcSec
373 maxMatchDistArcSec = min(maxMatchDist.asArcseconds(), self.config.maxMatchDistArcSec)
374 configMatchDistPix = maxMatchDistArcSec/wcs.pixelScale().asArcseconds()
377 matchControl.refFluxField = refFluxField
378 matchControl.sourceFluxField = sourceInfo.fluxField
379 matchControl.numBrightStars = self.config.numBrightStars
380 matchControl.minMatchedPairs = self.config.minMatchedPairs
381 matchControl.maxOffsetPix = self.config.maxOffsetPix
382 matchControl.numPointsForShape = self.config.numPointsForShape
383 matchControl.maxDeterminant = self.config.maxDeterminant
385 for maxRotInd
in range(4):
386 matchControl.maxRotationDeg = self.config.maxRotationDeg * math.pow(2.0, 0.5*maxRotInd)
387 for matchRadInd
in range(3):
388 matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd)
390 for angleDiffInd
in range(3):
391 matchControl.allowedNonperpDeg = self.config.allowedNonperpDeg*(angleDiffInd+1)
400 if matches
is not None and len(matches) > 0:
Match sources to reference objects.
def matchObjectsToSources
Match sources to position reference stars.
def _doMatch
Implementation of matching sources to position reference stars.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
afwTable::ReferenceMatchVector matchOptimisticB(afwTable::SimpleCatalog const &posRefCat, afwTable::SourceCatalog const &sourceCat, MatchOptimisticBControl const &control, afw::image::Wcs const &wcs, int posRefBegInd, bool verbose)