LSST Applications g0265f82a02+0e5473021a,g02d81e74bb+f5613e8b4f,g1470d8bcf6+190ad2ba91,g14a832a312+311607e4ab,g2079a07aa2+86d27d4dc4,g2305ad1205+a8e3196225,g295015adf3+b67ee847e5,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g3ddfee87b4+a761f810f3,g487adcacf7+17c8fdbcbd,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+65b5bd823e,g5a732f18d5+53520f316c,g64a986408d+f5613e8b4f,g6c1bc301e9+51106c2951,g858d7b2824+f5613e8b4f,g8a8a8dda67+585e252eca,g99cad8db69+6729933424,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+ef4e3a5875,gb0e22166c9+60f28cb32d,gb6a65358fc+0e5473021a,gba4ed39666+c2a2e4ac27,gbb8dafda3b+e9bba80f27,gc120e1dc64+eee469a5e5,gc28159a63d+0e5473021a,gcf0d15dbbd+a761f810f3,gdaeeff99f8+f9a426f77a,ge6526c86ff+d4c1d4bfef,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gf1cff7945b+f5613e8b4f,w.2024.16
LSST Data Management Base Package
Loading...
Searching...
No Matches
Public Member Functions | Public Attributes | Static Public Attributes | Protected Member Functions | Static Protected Attributes | List of all members
lsst.meas.astrom.astrometry.AstrometryTask Class Reference
Inheritance diagram for lsst.meas.astrom.astrometry.AstrometryTask:
lsst.meas.astrom.ref_match.RefMatchTask

Public Member Functions

 __init__ (self, refObjLoader=None, schema=None, **kwargs)
 
 run (self, sourceCat, exposure)
 
 solve (self, exposure, sourceCat)
 

Public Attributes

 usedKey
 

Static Public Attributes

 ConfigClass = AstrometryConfig
 

Protected Member Functions

 _matchAndFitWcs (self, refCat, sourceCat, goodSourceCat, refFluxField, bbox, wcs, match_tolerance, exposure=None)
 
 _removeMagnitudeOutliers (self, sourceFluxField, refFluxField, matchesIn)
 

Static Protected Attributes

str _DefaultName = "astrometricSolver"
 

Detailed Description

Match an input source catalog with objects from a reference catalog and
solve for the WCS.

This task is broken into two main subasks: matching and WCS fitting which
are very interactive. The matching here can be considered in part a first
pass WCS fitter due to the fitter's sensitivity to outliers.

Parameters
----------
refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
    A reference object loader object; gen3 pipeline tasks will pass `None`
    and call `setRefObjLoader` in `runQuantum`.
schema : `lsst.afw.table.Schema`
    Used to set "calib_astrometry_used" flag in output source catalog.
**kwargs
    Additional keyword arguments for pipe_base
    `lsst.pipe.base.Task.__init__`.

Definition at line 106 of file astrometry.py.

Constructor & Destructor Documentation

◆ __init__()

lsst.meas.astrom.astrometry.AstrometryTask.__init__ ( self,
refObjLoader = None,
schema = None,
** kwargs )

Reimplemented from lsst.meas.astrom.ref_match.RefMatchTask.

Definition at line 128 of file astrometry.py.

128 def __init__(self, refObjLoader=None, schema=None, **kwargs):
129 RefMatchTask.__init__(self, refObjLoader=refObjLoader, **kwargs)
130
131 if schema is not None:
132 self.usedKey = schema.addField("calib_astrometry_used", type="Flag",
133 doc="set if source was used in astrometric calibration")
134 else:
135 self.usedKey = None
136
137 self.makeSubtask("wcsFitter")
138

Member Function Documentation

◆ _matchAndFitWcs()

lsst.meas.astrom.astrometry.AstrometryTask._matchAndFitWcs ( self,
refCat,
sourceCat,
goodSourceCat,
refFluxField,
bbox,
wcs,
match_tolerance,
exposure = None )
protected
Match sources to reference objects and fit a WCS.

Parameters
----------
refCat : `lsst.afw.table.SimpleCatalog`
    catalog of reference objects
sourceCat : `lsst.afw.table.SourceCatalog`
    catalog of sources detected on the exposure
goodSourceCat : `lsst.afw.table.SourceCatalog`
    catalog of down-selected good sources detected on the exposure
refFluxField : 'str'
    field of refCat to use for flux
bbox : `lsst.geom.Box2I`
    bounding box of exposure
wcs : `lsst.afw.geom.SkyWcs`
    initial guess for WCS of exposure
match_tolerance : `lsst.meas.astrom.MatchTolerance`
    a MatchTolerance object (or None) specifying
    internal tolerances to the matcher. See the MatchTolerance
    definition in the respective matcher for the class definition.
exposure : `lsst.afw.image.Exposure`
    exposure whose WCS is to be fit, or None; used only for the debug
    display.

Returns
-------
result : `lsst.pipe.base.Struct`
    Result struct with components:

    - ``matches``:  astrometric matches
      (`list` of `lsst.afw.table.ReferenceMatch`).
    - ``wcs``:  the fit WCS (lsst.afw.geom.SkyWcs).
    - ``scatterOnSky`` :  median on-sky separation between reference
      objects and sources in "matches" (`lsst.afw.geom.Angle`).

Definition at line 350 of file astrometry.py.

351 exposure=None):
352 """Match sources to reference objects and fit a WCS.
353
354 Parameters
355 ----------
356 refCat : `lsst.afw.table.SimpleCatalog`
357 catalog of reference objects
358 sourceCat : `lsst.afw.table.SourceCatalog`
359 catalog of sources detected on the exposure
360 goodSourceCat : `lsst.afw.table.SourceCatalog`
361 catalog of down-selected good sources detected on the exposure
362 refFluxField : 'str'
363 field of refCat to use for flux
364 bbox : `lsst.geom.Box2I`
365 bounding box of exposure
366 wcs : `lsst.afw.geom.SkyWcs`
367 initial guess for WCS of exposure
368 match_tolerance : `lsst.meas.astrom.MatchTolerance`
369 a MatchTolerance object (or None) specifying
370 internal tolerances to the matcher. See the MatchTolerance
371 definition in the respective matcher for the class definition.
372 exposure : `lsst.afw.image.Exposure`
373 exposure whose WCS is to be fit, or None; used only for the debug
374 display.
375
376 Returns
377 -------
378 result : `lsst.pipe.base.Struct`
379 Result struct with components:
380
381 - ``matches``: astrometric matches
382 (`list` of `lsst.afw.table.ReferenceMatch`).
383 - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs).
384 - ``scatterOnSky`` : median on-sky separation between reference
385 objects and sources in "matches" (`lsst.afw.geom.Angle`).
386 """
387 import lsstDebug
388 debug = lsstDebug.Info(__name__)
389
390 sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType)
391
392 matchRes = self.matcher.matchObjectsToSources(
393 refCat=refCat,
394 sourceCat=goodSourceCat,
395 wcs=wcs,
396 sourceFluxField=sourceFluxField,
397 refFluxField=refFluxField,
398 match_tolerance=match_tolerance,
399 )
400 self.log.debug("Found %s matches", len(matchRes.matches))
401 if debug.display:
402 frame = int(debug.frame)
403 displayAstrometry(
404 refCat=refCat,
405 sourceCat=matchRes.usableSourceCat,
406 matches=matchRes.matches,
407 exposure=exposure,
408 bbox=bbox,
409 frame=frame + 1,
410 title="Initial WCS",
411 )
412
413 if self.config.doMagnitudeOutlierRejection:
414 matches = self._removeMagnitudeOutliers(sourceFluxField, refFluxField, matchRes.matches)
415 else:
416 matches = matchRes.matches
417
418 self.log.debug("Fitting WCS")
419 fitRes = self.wcsFitter.fitWcs(
420 matches=matches,
421 initWcs=wcs,
422 bbox=bbox,
423 refCat=refCat,
424 sourceCat=sourceCat,
425 exposure=exposure,
426 )
427 fitWcs = fitRes.wcs
428 scatterOnSky = fitRes.scatterOnSky
429 if debug.display:
430 frame = int(debug.frame)
431 displayAstrometry(
432 refCat=refCat,
433 sourceCat=matchRes.usableSourceCat,
434 matches=matches,
435 exposure=exposure,
436 bbox=bbox,
437 frame=frame + 2,
438 title=f"Fitter: {self.wcsFitter._DefaultName}",
439 )
440
441 return pipeBase.Struct(
442 matches=matches,
443 wcs=fitWcs,
444 scatterOnSky=scatterOnSky,
445 match_tolerance=matchRes.match_tolerance,
446 )
447

◆ _removeMagnitudeOutliers()

lsst.meas.astrom.astrometry.AstrometryTask._removeMagnitudeOutliers ( self,
sourceFluxField,
refFluxField,
matchesIn )
protected
Remove magnitude outliers, computing a simple zeropoint.

Parameters
----------
sourceFluxField : `str`
    Field in source catalog for instrumental fluxes.
refFluxField : `str`
    Field in reference catalog for fluxes (nJy).
matchesIn : `list` [`lsst.afw.table.ReferenceMatch`]
    List of source/reference matches input

Returns
-------
matchesOut : `list` [`lsst.afw.table.ReferenceMatch`]
    List of source/reference matches with magnitude
    outliers removed.

Definition at line 448 of file astrometry.py.

448 def _removeMagnitudeOutliers(self, sourceFluxField, refFluxField, matchesIn):
449 """Remove magnitude outliers, computing a simple zeropoint.
450
451 Parameters
452 ----------
453 sourceFluxField : `str`
454 Field in source catalog for instrumental fluxes.
455 refFluxField : `str`
456 Field in reference catalog for fluxes (nJy).
457 matchesIn : `list` [`lsst.afw.table.ReferenceMatch`]
458 List of source/reference matches input
459
460 Returns
461 -------
462 matchesOut : `list` [`lsst.afw.table.ReferenceMatch`]
463 List of source/reference matches with magnitude
464 outliers removed.
465 """
466 nMatch = len(matchesIn)
467 sourceMag = np.zeros(nMatch)
468 refMag = np.zeros(nMatch)
469 for i, match in enumerate(matchesIn):
470 sourceMag[i] = -2.5*np.log10(match[1][sourceFluxField])
471 refMag[i] = (match[0][refFluxField]*units.nJy).to_value(units.ABmag)
472
473 deltaMag = refMag - sourceMag
474 # Protect against negative fluxes and nans in the reference catalog.
475 goodDelta, = np.where(np.isfinite(deltaMag))
476 zp = np.median(deltaMag[goodDelta])
477 # Use median absolute deviation (MAD) for zpSigma.
478 # Also require a minimum scatter to prevent floating-point errors from
479 # rejecting objects in zero-noise tests.
480 zpSigma = np.clip(scipy.stats.median_abs_deviation(deltaMag[goodDelta], scale='normal'),
481 1e-3,
482 None)
483
484 self.log.info("Rough zeropoint from astrometry matches is %.4f +/- %.4f.",
485 zp, zpSigma)
486
487 goodStars = goodDelta[(np.abs(deltaMag[goodDelta] - zp)
488 <= self.config.magnitudeOutlierRejectionNSigma*zpSigma)]
489
490 nOutlier = nMatch - goodStars.size
491 self.log.info("Removed %d magnitude outliers out of %d total astrometry matches.",
492 nOutlier, nMatch)
493
494 matchesOut = [matchesIn[idx] for idx in goodStars]
495
496 return matchesOut

◆ run()

lsst.meas.astrom.astrometry.AstrometryTask.run ( self,
sourceCat,
exposure )
Load reference objects, match sources and optionally fit a WCS.

This is a thin layer around solve or loadAndMatch, depending on
config.forceKnownWcs.

Parameters
----------
exposure : `lsst.afw.image.Exposure`
    exposure whose WCS is to be fit
    The following are read only:

    - bbox
    - filter (may be unset)
    - detector (if wcs is pure tangent; may be absent)

    The following are updated:

    - wcs (the initial value is used as an initial guess, and is
      required)

sourceCat : `lsst.afw.table.SourceCatalog`
    catalog of sources detected on the exposure

Returns
-------
result : `lsst.pipe.base.Struct`
    with these fields:

    - ``refCat`` : reference object catalog of objects that overlap the
      exposure (with some margin) (`lsst.afw.table.SimpleCatalog`).
    - ``matches`` : astrometric matches
      (`list` of `lsst.afw.table.ReferenceMatch`).
    - ``scatterOnSky`` :  median on-sky separation between reference
      objects and sources in "matches"
      (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True
    - ``matchMeta`` :  metadata needed to unpersist matches
      (`lsst.daf.base.PropertyList`)

Definition at line 140 of file astrometry.py.

140 def run(self, sourceCat, exposure):
141 """Load reference objects, match sources and optionally fit a WCS.
142
143 This is a thin layer around solve or loadAndMatch, depending on
144 config.forceKnownWcs.
145
146 Parameters
147 ----------
148 exposure : `lsst.afw.image.Exposure`
149 exposure whose WCS is to be fit
150 The following are read only:
151
152 - bbox
153 - filter (may be unset)
154 - detector (if wcs is pure tangent; may be absent)
155
156 The following are updated:
157
158 - wcs (the initial value is used as an initial guess, and is
159 required)
160
161 sourceCat : `lsst.afw.table.SourceCatalog`
162 catalog of sources detected on the exposure
163
164 Returns
165 -------
166 result : `lsst.pipe.base.Struct`
167 with these fields:
168
169 - ``refCat`` : reference object catalog of objects that overlap the
170 exposure (with some margin) (`lsst.afw.table.SimpleCatalog`).
171 - ``matches`` : astrometric matches
172 (`list` of `lsst.afw.table.ReferenceMatch`).
173 - ``scatterOnSky`` : median on-sky separation between reference
174 objects and sources in "matches"
175 (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True
176 - ``matchMeta`` : metadata needed to unpersist matches
177 (`lsst.daf.base.PropertyList`)
178 """
179 if self.refObjLoader is None:
180 raise RuntimeError("Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
181 if self.config.forceKnownWcs:
182 res = self.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
183 res.scatterOnSky = None
184 else:
185 res = self.solve(exposure=exposure, sourceCat=sourceCat)
186 return res
187

◆ solve()

lsst.meas.astrom.astrometry.AstrometryTask.solve ( self,
exposure,
sourceCat )
Load reference objects overlapping an exposure, match to sources and
fit a WCS

Returns
-------
result : `lsst.pipe.base.Struct`
    Result struct with components:

    - ``refCat`` : reference object catalog of objects that overlap the
      exposure (with some margin) (`lsst::afw::table::SimpleCatalog`).
    - ``matches`` :  astrometric matches
      (`list` of `lsst.afw.table.ReferenceMatch`).
    - ``scatterOnSky`` :  median on-sky separation between reference
      objects and sources in "matches" (`lsst.geom.Angle`)
    - ``matchMeta`` :  metadata needed to unpersist matches
      (`lsst.daf.base.PropertyList`)

Raises
------
TaskError
    If the measured mean on-sky distance between the matched source and
    reference objects is greater than
    ``self.config.maxMeanDistanceArcsec``.

Notes
-----
ignores config.forceKnownWcs

Definition at line 189 of file astrometry.py.

189 def solve(self, exposure, sourceCat):
190 """Load reference objects overlapping an exposure, match to sources and
191 fit a WCS
192
193 Returns
194 -------
195 result : `lsst.pipe.base.Struct`
196 Result struct with components:
197
198 - ``refCat`` : reference object catalog of objects that overlap the
199 exposure (with some margin) (`lsst::afw::table::SimpleCatalog`).
200 - ``matches`` : astrometric matches
201 (`list` of `lsst.afw.table.ReferenceMatch`).
202 - ``scatterOnSky`` : median on-sky separation between reference
203 objects and sources in "matches" (`lsst.geom.Angle`)
204 - ``matchMeta`` : metadata needed to unpersist matches
205 (`lsst.daf.base.PropertyList`)
206
207 Raises
208 ------
209 TaskError
210 If the measured mean on-sky distance between the matched source and
211 reference objects is greater than
212 ``self.config.maxMeanDistanceArcsec``.
213
214 Notes
215 -----
216 ignores config.forceKnownWcs
217 """
218 if self.refObjLoader is None:
219 raise RuntimeError("Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
220 import lsstDebug
221 debug = lsstDebug.Info(__name__)
222
223 expMd = self._getExposureMetadata(exposure)
224
225 sourceSelection = self.sourceSelector.run(sourceCat)
226
227 self.log.info("Purged %d sources, leaving %d good sources",
228 len(sourceCat) - len(sourceSelection.sourceCat),
229 len(sourceSelection.sourceCat))
230
231 loadRes = self.refObjLoader.loadPixelBox(
232 bbox=expMd.bbox,
233 wcs=expMd.wcs,
234 filterName=expMd.filterName,
235 epoch=expMd.epoch,
236 )
237
238 refSelection = self.referenceSelector.run(loadRes.refCat)
239
240 matchMeta = self.refObjLoader.getMetadataBox(
241 bbox=expMd.bbox,
242 wcs=expMd.wcs,
243 filterName=expMd.filterName,
244 epoch=expMd.epoch,
245 )
246
247 if debug.display:
248 frame = int(debug.frame)
249 displayAstrometry(
250 refCat=refSelection.sourceCat,
251 sourceCat=sourceSelection.sourceCat,
252 exposure=exposure,
253 bbox=expMd.bbox,
254 frame=frame,
255 title="Reference catalog",
256 )
257
258 res = None
259 wcs = expMd.wcs
260 match_tolerance = None
261 fitFailed = False
262 for i in range(self.config.maxIter):
263 if not fitFailed:
264 iterNum = i + 1
265 try:
266 tryRes = self._matchAndFitWcs(
267 refCat=refSelection.sourceCat,
268 sourceCat=sourceCat,
269 goodSourceCat=sourceSelection.sourceCat,
270 refFluxField=loadRes.fluxField,
271 bbox=expMd.bbox,
272 wcs=wcs,
273 exposure=exposure,
274 match_tolerance=match_tolerance,
275 )
276 except Exception as e:
277 # If we have had a succeessful iteration then use that;
278 # otherwise fail.
279 if i > 0:
280 self.log.info("Fit WCS iter %d failed; using previous iteration: %s", iterNum, e)
281 iterNum -= 1
282 break
283 else:
284 self.log.info("Fit WCS iter %d failed: %s" % (iterNum, e))
285 fitFailed = True
286
287 if not fitFailed:
288 match_tolerance = tryRes.match_tolerance
289 tryMatchDist = self._computeMatchStatsOnSky(tryRes.matches)
290 self.log.debug(
291 "Match and fit WCS iteration %d: found %d matches with on-sky distance mean and "
292 "scatter = %0.3f +- %0.3f arcsec; max match distance = %0.3f arcsec",
293 iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
294 tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
295
296 maxMatchDist = tryMatchDist.maxMatchDist
297 res = tryRes
298 wcs = res.wcs
299 if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
300 self.log.debug(
301 "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
302 "that's good enough",
303 maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
304 break
305 match_tolerance.maxMatchDist = maxMatchDist
306
307 if not fitFailed:
308 self.log.info("Matched and fit WCS in %d iterations; "
309 "found %d matches with mean and scatter = %0.3f +- %0.3f arcsec" %
310 (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
311 tryMatchDist.distStdDev.asArcseconds()))
312 if tryMatchDist.distMean.asArcseconds() > self.config.maxMeanDistanceArcsec:
313 self.log.info("Assigning as a fit failure: mean on-sky distance = %0.3f arcsec > %0.3f "
314 "(maxMeanDistanceArcsec)" % (tryMatchDist.distMean.asArcseconds(),
315 self.config.maxMeanDistanceArcsec))
316 fitFailed = True
317
318 if fitFailed:
319 self.log.warning("WCS fit failed. Setting exposure's WCS to None and coord_ra & coord_dec "
320 "cols in sourceCat to nan.")
321 sourceCat["coord_ra"] = np.nan
322 sourceCat["coord_dec"] = np.nan
323 exposure.setWcs(None)
324 matches = None
325 scatterOnSky = None
326 else:
327 for m in res.matches:
328 if self.usedKey:
329 m.second.set(self.usedKey, True)
330 exposure.setWcs(res.wcs)
331 matches = res.matches
332 scatterOnSky = res.scatterOnSky
333
334 # If fitter converged, record the scatter in the exposure metadata
335 # even if the fit was deemed a failure according to the value of
336 # the maxMeanDistanceArcsec config.
337 if res is not None:
338 md = exposure.getMetadata()
339 md['SFM_ASTROM_OFFSET_MEAN'] = tryMatchDist.distMean.asArcseconds()
340 md['SFM_ASTROM_OFFSET_STD'] = tryMatchDist.distStdDev.asArcseconds()
341
342 return pipeBase.Struct(
343 refCat=refSelection.sourceCat,
344 matches=matches,
345 scatterOnSky=scatterOnSky,
346 matchMeta=matchMeta,
347 )
348

Member Data Documentation

◆ _DefaultName

str lsst.meas.astrom.astrometry.AstrometryTask._DefaultName = "astrometricSolver"
staticprotected

Definition at line 126 of file astrometry.py.

◆ ConfigClass

lsst.meas.astrom.astrometry.AstrometryTask.ConfigClass = AstrometryConfig
static

Definition at line 125 of file astrometry.py.

◆ usedKey

lsst.meas.astrom.astrometry.AstrometryTask.usedKey

Definition at line 132 of file astrometry.py.


The documentation for this class was generated from the following file: