LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
astrometry.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2016 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 __all__ = ["AstrometryConfig", "AstrometryTask"]
24 
25 
26 import lsst.pex.config as pexConfig
27 import lsst.pipe.base as pipeBase
28 from .ref_match import RefMatchTask, RefMatchConfig
29 from .fitTanSipWcs import FitTanSipWcsTask
30 from .display import displayAstrometry
31 
32 
34  """Config for AstrometryTask.
35  """
36  wcsFitter = pexConfig.ConfigurableField(
37  target=FitTanSipWcsTask,
38  doc="WCS fitter",
39  )
40  forceKnownWcs = pexConfig.Field(
41  dtype=bool,
42  doc="If True then load reference objects and match sources but do not fit a WCS; "
43  "this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
44  default=False,
45  )
46  maxIter = pexConfig.RangeField(
47  doc="maximum number of iterations of match sources and fit WCS"
48  "ignored if not fitting a WCS",
49  dtype=int,
50  default=3,
51  min=1,
52  )
53  minMatchDistanceArcSec = pexConfig.RangeField(
54  doc="the match distance below which further iteration is pointless (arcsec); "
55  "ignored if not fitting a WCS",
56  dtype=float,
57  default=0.001,
58  min=0,
59  )
60 
61 
63  """Match an input source catalog with objects from a reference catalog and
64  solve for the WCS.
65 
66  This task is broken into two main subasks: matching and WCS fitting which
67  are very interactive. The matching here can be considered in part a first
68  pass WCS fitter due to the fitter's sensitivity to outliers.
69 
70  Parameters
71  ----------
72  refObjLoader : `lsst.meas.algorithms.ReferenceLoader`
73  A reference object loader object
74  schema : `lsst.afw.table.Schema`
75  Used to set "calib_astrometry_used" flag in output source catalog.
76  **kwargs
77  additional keyword arguments for pipe_base
78  `lsst.pipe.base.Task.__init__`
79  """
80  ConfigClass = AstrometryConfig
81  _DefaultName = "astrometricSolver"
82 
83  def __init__(self, refObjLoader, schema=None, **kwargs):
84  RefMatchTask.__init__(self, refObjLoader, **kwargs)
85 
86  if schema is not None:
87  self.usedKey = schema.addField("calib_astrometry_used", type="Flag",
88  doc="set if source was used in astrometric calibration")
89  else:
90  self.usedKey = None
91 
92  self.makeSubtask("wcsFitter")
93 
94  @pipeBase.timeMethod
95  def run(self, sourceCat, exposure):
96  """Load reference objects, match sources and optionally fit a WCS.
97 
98  This is a thin layer around solve or loadAndMatch, depending on
99  config.forceKnownWcs.
100 
101  Parameters
102  ----------
103  exposure : `lsst.afw.image.Exposure`
104  exposure whose WCS is to be fit
105  The following are read only:
106 
107  - bbox
108  - calib (may be absent)
109  - filter (may be unset)
110  - detector (if wcs is pure tangent; may be absent)
111 
112  The following are updated:
113 
114  - wcs (the initial value is used as an initial guess, and is
115  required)
116 
117  sourceCat : `lsst.afw.table.SourceCatalog`
118  catalog of sources detected on the exposure
119 
120  Returns
121  -------
122  result : `lsst.pipe.base.Struct`
123  with these fields:
124 
125  - ``refCat`` : reference object catalog of objects that overlap the
126  exposure (with some margin) (`lsst.afw.table.SimpleCatalog`).
127  - ``matches`` : astrometric matches
128  (`list` of `lsst.afw.table.ReferenceMatch`).
129  - ``scatterOnSky`` : median on-sky separation between reference
130  objects and sources in "matches"
131  (`lsst.afw.geom.Angle`) or `None` if config.forceKnownWcs True
132  - ``matchMeta`` : metadata needed to unpersist matches
133  (`lsst.daf.base.PropertyList`)
134  """
135  if self.refObjLoader is None:
136  raise RuntimeError("Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
137  if self.config.forceKnownWcs:
138  res = self.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
139  res.scatterOnSky = None
140  else:
141  res = self.solve(exposure=exposure, sourceCat=sourceCat)
142  return res
143 
144  @pipeBase.timeMethod
145  def solve(self, exposure, sourceCat):
146  """Load reference objects overlapping an exposure, match to sources and
147  fit a WCS
148 
149  Returns
150  -------
151  result : `lsst.pipe.base.Struct`
152  Result struct with components:
153 
154  - ``refCat`` : reference object catalog of objects that overlap the
155  exposure (with some margin) (`lsst::afw::table::SimpleCatalog`).
156  - ``matches`` : astrometric matches
157  (`list` of `lsst.afw.table.ReferenceMatch`).
158  - ``scatterOnSky`` : median on-sky separation between reference
159  objects and sources in "matches" (`lsst.geom.Angle`)
160  - ``matchMeta`` : metadata needed to unpersist matches
161  (`lsst.daf.base.PropertyList`)
162 
163  Notes
164  -----
165  ignores config.forceKnownWcs
166  """
167  if self.refObjLoader is None:
168  raise RuntimeError("Running matcher task with no refObjLoader set in __init__ or setRefObjLoader")
169  import lsstDebug
170  debug = lsstDebug.Info(__name__)
171 
172  expMd = self._getExposureMetadata(exposure)
173 
174  loadRes = self.refObjLoader.loadPixelBox(
175  bbox=expMd.bbox,
176  wcs=expMd.wcs,
177  filterName=expMd.filterName,
178  calib=expMd.calib,
179  epoch=expMd.epoch,
180  )
181  matchMeta = self.refObjLoader.getMetadataBox(
182  bbox=expMd.bbox,
183  wcs=expMd.wcs,
184  filterName=expMd.filterName,
185  calib=expMd.calib,
186  epoch=expMd.epoch,
187  )
188 
189  if debug.display:
190  frame = int(debug.frame)
192  refCat=loadRes.refCat,
193  sourceCat=sourceCat,
194  exposure=exposure,
195  bbox=expMd.bbox,
196  frame=frame,
197  title="Reference catalog",
198  )
199 
200  res = None
201  wcs = expMd.wcs
202  match_tolerance = None
203  for i in range(self.config.maxIter):
204  iterNum = i + 1
205  try:
206  tryRes = self._matchAndFitWcs( # refCat, sourceCat, refFluxField, bbox, wcs, exposure=None
207  refCat=loadRes.refCat,
208  sourceCat=sourceCat,
209  refFluxField=loadRes.fluxField,
210  bbox=expMd.bbox,
211  wcs=wcs,
212  exposure=exposure,
213  match_tolerance=match_tolerance,
214  )
215  except Exception as e:
216  # if we have had a succeessful iteration then use that; otherwise fail
217  if i > 0:
218  self.log.info("Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
219  iterNum -= 1
220  break
221  else:
222  raise
223 
224  match_tolerance = tryRes.match_tolerance
225  tryMatchDist = self._computeMatchStatsOnSky(tryRes.matches)
226  self.log.debug(
227  "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
228  "max match distance = %0.3f arcsec",
229  iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
230  tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
231 
232  maxMatchDist = tryMatchDist.maxMatchDist
233  res = tryRes
234  wcs = res.wcs
235  if maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
236  self.log.debug(
237  "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
238  "that's good enough",
239  maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
240  break
241  match_tolerance.maxMatchDist = maxMatchDist
242 
243  self.log.info(
244  "Matched and fit WCS in %d iterations; "
245  "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
246  (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
247  tryMatchDist.distStdDev.asArcseconds()))
248  for m in res.matches:
249  if self.usedKey:
250  m.second.set(self.usedKey, True)
251  exposure.setWcs(res.wcs)
252 
253  return pipeBase.Struct(
254  refCat=loadRes.refCat,
255  matches=res.matches,
256  scatterOnSky=res.scatterOnSky,
257  matchMeta=matchMeta,
258  )
259 
260  @pipeBase.timeMethod
261  def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance,
262  exposure=None):
263  """Match sources to reference objects and fit a WCS.
264 
265  Parameters
266  ----------
267  refCat : `lsst.afw.table.SimpleCatalog`
268  catalog of reference objects
269  sourceCat : `lsst.afw.table.SourceCatalog`
270  catalog of sources detected on the exposure
271  refFluxField : 'str'
272  field of refCat to use for flux
273  bbox : `lsst.geom.Box2I`
274  bounding box of exposure
275  wcs : `lsst.afw.geom.SkyWcs`
276  initial guess for WCS of exposure
277  match_tolerance : `lsst.meas.astrom.MatchTolerance`
278  a MatchTolerance object (or None) specifying
279  internal tolerances to the matcher. See the MatchTolerance
280  definition in the respective matcher for the class definition.
281  exposure : `lsst.afw.image.Exposure`
282  exposure whose WCS is to be fit, or None; used only for the debug
283  display.
284 
285  Returns
286  -------
287  result : `lsst.pipe.base.Struct`
288  Result struct with components:
289 
290  - ``matches``: astrometric matches
291  (`list` of `lsst.afw.table.ReferenceMatch`).
292  - ``wcs``: the fit WCS (lsst.afw.geom.SkyWcs).
293  - ``scatterOnSky`` : median on-sky separation between reference
294  objects and sources in "matches" (`lsst.afw.geom.Angle`).
295  """
296  import lsstDebug
297  debug = lsstDebug.Info(__name__)
298  matchRes = self.matcher.matchObjectsToSources(
299  refCat=refCat,
300  sourceCat=sourceCat,
301  wcs=wcs,
302  refFluxField=refFluxField,
303  match_tolerance=match_tolerance,
304  )
305  self.log.debug("Found %s matches", len(matchRes.matches))
306  if debug.display:
307  frame = int(debug.frame)
309  refCat=refCat,
310  sourceCat=matchRes.usableSourceCat,
311  matches=matchRes.matches,
312  exposure=exposure,
313  bbox=bbox,
314  frame=frame + 1,
315  title="Initial WCS",
316  )
317 
318  self.log.debug("Fitting WCS")
319  fitRes = self.wcsFitter.fitWcs(
320  matches=matchRes.matches,
321  initWcs=wcs,
322  bbox=bbox,
323  refCat=refCat,
324  sourceCat=sourceCat,
325  exposure=exposure,
326  )
327  fitWcs = fitRes.wcs
328  scatterOnSky = fitRes.scatterOnSky
329  if debug.display:
330  frame = int(debug.frame)
332  refCat=refCat,
333  sourceCat=matchRes.usableSourceCat,
334  matches=matchRes.matches,
335  exposure=exposure,
336  bbox=bbox,
337  frame=frame + 2,
338  title="Fit TAN-SIP WCS",
339  )
340 
341  return pipeBase.Struct(
342  matches=matchRes.matches,
343  wcs=fitWcs,
344  scatterOnSky=scatterOnSky,
345  match_tolerance=matchRes.match_tolerance,
346  )
def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, match_tolerance, exposure=None)
Definition: astrometry.py:262
def solve(self, exposure, sourceCat)
Definition: astrometry.py:145
def _computeMatchStatsOnSky(self, matchList)
Definition: ref_match.py:177
def _getExposureMetadata(self, exposure)
Definition: ref_match.py:206
def run(self, sourceCat, exposure)
Definition: astrometry.py:95
def __init__(self, refObjLoader, schema=None, kwargs)
Definition: astrometry.py:83
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:35
def loadAndMatch(self, exposure, sourceCat)
Definition: ref_match.py:91