1 from __future__
import absolute_import, division, print_function
9 from .loadAstrometryNetObjects
import LoadAstrometryNetObjectsTask
10 from .matchOptimisticB
import MatchOptimisticBTask
11 from .fitTanSipWcs
import FitTanSipWcsTask
12 from .display
import displayAstrometry
15 refObjLoader = pexConfig.ConfigurableField(
16 target = LoadAstrometryNetObjectsTask,
17 doc =
"reference object loader",
19 matcher = pexConfig.ConfigurableField(
20 target = MatchOptimisticBTask,
21 doc =
"reference object/source matcher",
23 wcsFitter = pexConfig.ConfigurableField(
24 target = FitTanSipWcsTask,
27 forceKnownWcs = pexConfig.Field(
29 doc=
"If True then load reference objects and match sources but do not fit a WCS; " +
30 " this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
33 maxIter = pexConfig.RangeField(
34 doc =
"maximum number of iterations of match sources and fit WCS" +
35 "ignored if not fitting a WCS",
40 matchDistanceSigma = pexConfig.RangeField(
41 doc =
"the maximum match distance is set to "
42 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; " +
43 "ignored if not fitting a WCS",
48 minMatchDistanceArcSec = pexConfig.RangeField(
49 doc =
"the match distance below which further iteration is pointless (arcsec); "
50 "ignored if not fitting a WCS",
65 """!Match an input source catalog with objects from a reference catalog and solve for the WCS
67 @anchor AstrometryTask_
69 @section meas_astrom_astrometry_Contents Contents
71 - @ref meas_astrom_astrometry_Purpose
72 - @ref meas_astrom_astrometry_Initialize
73 - @ref meas_astrom_astrometry_IO
74 - @ref meas_astrom_astrometry_Config
75 - @ref meas_astrom_astrometry_Example
76 - @ref meas_astrom_astrometry_Debug
78 @section meas_astrom_astrometry_Purpose Description
80 Match input sourceCat with a reference catalog and solve for the Wcs
82 There are three steps, each performed by different subtasks:
83 - Find position reference stars that overlap the exposure
84 - Match sourceCat to position reference stars
85 - Fit a WCS based on the matches
87 @section meas_astrom_astrometry_Initialize Task initialisation
91 @section meas_astrom_astrometry_IO Invoking the Task
97 @section meas_astrom_astrometry_Config Configuration parameters
99 See @ref AstrometryConfig
101 @section meas_astrom_astrometry_Example A complete example of using AstrometryTask
103 See \ref meas_photocal_photocal_Example.
105 @section meas_astrom_astrometry_Debug Debug variables
107 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
108 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
111 The available variables in AstrometryTask are:
113 <DT> @c display (bool)
114 <DD> If True display information at three stages: after finding reference objects,
115 after matching sources to reference objects, and after fitting the WCS; defaults to False
117 <DD> ds9 frame to use to display the reference objects; the next two frames are used
118 to display the match list and the results of the final WCS; defaults to 0
121 To investigate the @ref meas_astrom_astrometry_Debug, put something like
125 debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
126 if name == "lsst.meas.astrom.astrometry":
131 lsstDebug.Info = DebugInfo
133 into your debug.py file and run this task with the @c --debug flag.
135 ConfigClass = AstrometryConfig
136 _DefaultName =
"astrometricSolver"
139 """!Construct an AstrometryTask
141 @param[in] schema ignored; available for compatibility with an older astrometry task
143 pipeBase.Task.__init__(self, **kwargs)
144 self.makeSubtask(
"refObjLoader")
145 self.makeSubtask(
"matcher")
146 self.makeSubtask(
"wcsFitter")
149 def run(self, exposure, sourceCat):
150 """!Load reference objects, match sources and optionally fit a WCS
152 This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs
154 @param[in,out] exposure exposure whose WCS is to be fit
155 The following are read only:
157 - calib (may be absent)
158 - filter (may be unset)
159 - detector (if wcs is pure tangent; may be absent)
160 The following are updated:
161 - wcs (the initial value is used as an initial guess, and is required)
162 @param[in] sourceCat catalog of sourceCat detected on the exposure (an lsst.afw.table.SourceCatalog)
163 @return an lsst.pipe.base.Struct with these fields:
164 - refCat reference object catalog of objects that overlap the exposure (with some margin)
165 (an lsst::afw::table::SimpleCatalog)
166 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
167 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
168 (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True
169 - matchMeta metadata about the field (an lsst.daf.base.PropertyList)
171 if self.config.forceKnownWcs:
172 res = self.
loadAndMatch(exposure=exposure, sourceCat=sourceCat)
173 res.scatterOnSky =
None
175 res = self.
solve(exposure=exposure, sourceCat=sourceCat)
180 """!Load reference objects overlapping an exposure and match to sources detected on that exposure
182 @param[in] exposure exposure whose WCS is to be fit
183 @param[in] sourceCat catalog of sourceCat detected on the exposure (an lsst.afw.table.SourceCatalog)
185 @return an lsst.pipe.base.Struct with these fields:
186 - refCat reference object catalog of objects that overlap the exposure (with some margin)
187 (an lsst::afw::table::SimpleCatalog)
188 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
189 - matchMeta metadata about the field (an lsst.daf.base.PropertyList)
191 @note ignores config.forceKnownWcs, config.maxIter, config.matchDistanceSigma
192 and config.minMatchDistanceArcSec
199 loadRes = self.refObjLoader.loadPixelBox(
202 filterName = expMd.filterName,
206 matchRes = self.matcher.matchObjectsToSources(
207 refCat = loadRes.refCat,
208 sourceCat = sourceCat,
210 refFluxField = loadRes.fluxField,
211 maxMatchDistArcSec =
None,
214 distRadList = [match.distance
for match
in matchRes.matches]
216 distArcsecMean =
radToArcsec(distRadStats.getValue(afwMath.MEANCLIP))
217 distArcsecStdDev =
radToArcsec(distRadStats.getValue(afwMath.STDEVCLIP))
219 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " %
220 (len(matchRes.matches), distArcsecMean, distArcsecStdDev))
223 frame = int(debug.frame)
225 refCat = loadRes.refCat,
226 sourceCat = sourceCat,
227 matches = matchRes.matches,
234 return pipeBase.Struct(
235 refCat = loadRes.refCat,
236 matches = matchRes.matches,
237 matchMeta = self.
_createMatchMetadata(bbox=expMd.bbox, wcs=expMd.wcs, filterName=expMd.filterName),
241 def solve(self, exposure, sourceCat):
242 """!Load reference objects overlapping an exposure, match to sources and fit a WCS
244 @return an lsst.pipe.base.Struct with these fields:
245 - refCat reference object catalog of objects that overlap the exposure (with some margin)
246 (an lsst::afw::table::SimpleCatalog)
247 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
248 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
249 (an lsst.afw.geom.Angle)
250 - matchMeta metadata about the field (an lsst.daf.base.PropertyList)
252 @note ignores config.forceKnownWcs
259 loadRes = self.refObjLoader.loadPixelBox(
262 filterName = expMd.filterName,
266 frame = int(debug.frame)
268 refCat = loadRes.refCat,
269 sourceCat = sourceCat,
273 title=
"Reference catalog",
278 maxMatchDistArcSec =
None
279 for i
in range(self.config.maxIter):
283 refCat = loadRes.refCat,
284 sourceCat = sourceCat,
285 refFluxField = loadRes.fluxField,
289 maxMatchDistArcSec = maxMatchDistArcSec,
291 except Exception
as e:
294 self.log.info(
"Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
300 distRadList = [match.distance
for match
in tryRes.matches]
302 distArcsecMean =
radToArcsec(distRadStats.getValue(afwMath.MEANCLIP))
303 distArcsecStdDev =
radToArcsec(distRadStats.getValue(afwMath.STDEVCLIP))
305 newMaxMatchDistArcSec = distArcsecMean + self.config.matchDistanceSigma*distArcsecStdDev
307 "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
308 "max match distance = %0.3f arcsec" %
309 (iterNum, len(tryRes.matches), distArcsecMean, distArcsecStdDev, newMaxMatchDistArcSec))
310 if maxMatchDistArcSec
is not None:
311 if newMaxMatchDistArcSec >= maxMatchDistArcSec:
313 "Iteration %d had no better maxMatchDist; using previous iteration" % (iterNum,))
317 maxMatchDistArcSec = newMaxMatchDistArcSec
320 if newMaxMatchDistArcSec < self.config.minMatchDistanceArcSec:
322 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
323 "that's good enough" %
324 (newMaxMatchDistArcSec, self.config.minMatchDistanceArcSec))
328 "Matched and fit WCS in %d iterations; "
329 "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
330 (iterNum, len(tryRes.matches), distArcsecMean, distArcsecStdDev))
332 exposure.setWcs(res.wcs)
334 return pipeBase.Struct(
335 refCat = loadRes.refCat,
336 matches = res.matches,
337 scatterOnSky = res.scatterOnSky,
338 matchMeta = self.
_createMatchMetadata(bbox=expMd.bbox, wcs=res.wcs, filterName=expMd.filterName)
342 """!Extract metadata from an exposure
344 @return an lsst.pipe.base.Struct containing the following exposure metadata:
345 - bbox: parent bounding box
346 - wcs: WCS (an lsst.afw.image.Wcs)
347 - calib calibration (an lsst.afw.image.Calib), or None if unknown
348 - filterName: name of filter, or None if unknown
350 exposureInfo = exposure.getInfo()
351 filterName = exposureInfo.getFilter().getName()
or None
352 if filterName ==
"_unknown_":
354 return pipeBase.Struct(
355 bbox = exposure.getBBox(),
357 calib = exposureInfo.getCalib()
if exposureInfo.hasCalib()
else None,
358 filterName = filterName,
362 def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, maxMatchDistArcSec=None,
364 """!Match sources to reference objects and fit a WCS
366 @param[in] refCat catalog of reference objects
367 @param[in] sourceCat catalog of sourceCat detected on the exposure (an lsst.afw.table.SourceCatalog)
368 @param[in] refFluxField field of refCat to use for flux
369 @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I)
370 @param[in] wcs initial guess for WCS of exposure (an lsst.afw.image.Wcs)
371 @param[in] maxMatchDistArcSec maximum distance between reference objects and sources (arcsec);
372 if None then use the matcher's default
373 @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display
375 @return an lsst.pipe.base.Struct with these fields:
376 - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
377 - wcs the fit WCS (an lsst.afw.image.Wcs)
378 - scatterOnSky median on-sky separation between reference objects and sources in "matches"
379 (an lsst.afw.geom.Angle)
383 matchRes = self.matcher.matchObjectsToSources(
385 sourceCat = sourceCat,
387 refFluxField = refFluxField,
388 maxMatchDistArcSec = maxMatchDistArcSec,
390 self.log.logdebug(
"Found %s matches" % (len(matchRes.matches),))
392 frame = int(debug.frame)
395 sourceCat = matchRes.usableSourceCat,
396 matches = matchRes.matches,
403 self.log.logdebug(
"Fitting WCS")
404 fitRes = self.wcsFitter.fitWcs(
405 matches = matchRes.matches,
409 sourceCat = sourceCat,
412 scatterOnSky = fitRes.scatterOnSky
414 frame = int(debug.frame)
417 sourceCat = matchRes.usableSourceCat,
418 matches = matchRes.matches,
422 title=
"Fit TAN-SIP WCS",
425 return pipeBase.Struct(
426 matches = matchRes.matches,
428 scatterOnSky = scatterOnSky,
433 """Create matchMeta metadata required for regenerating the catalog
435 This is copied from Astrom and I'm not sure why it is needed.
437 @param bbox bounding box of exposure (an lsst.afw.geom.Box2I or Box2D)
438 @param wcs WCS of exposure
439 @param filterName Name of filter, used for magnitudes
440 @return metadata about the field (a daf_base PropertyList)
444 ctrPos = bboxd.getCenter()
445 ctrCoord = wcs.pixelToSky(ctrPos).toIcrs()
446 llCoord = wcs.pixelToSky(bboxd.getMin())
447 approxRadius = ctrCoord.angularSeparation(llCoord)
448 matchMeta.add(
'RA', ctrCoord.getRa().asDegrees(),
'field center in degrees')
449 matchMeta.add(
'DEC', ctrCoord.getDec().asDegrees(),
'field center in degrees')
450 matchMeta.add(
'RADIUS', approxRadius.asDegrees(),
'field radius in degrees, approximate')
451 matchMeta.add(
'SMATCHV', 1,
'SourceMatchVector version number')
452 if filterName
is not None:
453 matchMeta.add(
'FILTER', filterName,
'filter name for tagalong data')
def _matchAndFitWcs
Match sources to reference objects and fit a WCS.
Class for storing ordered metadata with comments.
def _getExposureMetadata
Extract metadata from an exposure.
double radToArcsec(double x)
def __init__
Construct an AstrometryTask.
def getDistortedWcs
Get a WCS from an exposureInfo, with distortion terms if possible.
def run
Load reference objects, match sources and optionally fit a WCS.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
def solve
Load reference objects overlapping an exposure, match to sources and fit a WCS.
A floating-point coordinate rectangle geometry.
Match an input source catalog with objects from a reference catalog and solve for the WCS...
def loadAndMatch
Load reference objects overlapping an exposure and match to sources detected on that exposure...