LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
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 from __future__ import absolute_import, division, print_function
23 from builtins import range
24 
25 import lsst.pex.config as pexConfig
26 import lsst.pipe.base as pipeBase
27 from .ref_match import RefMatchTask, RefMatchConfig
28 from .fitTanSipWcs import FitTanSipWcsTask
29 from .display import displayAstrometry
30 from .createMatchMetadata import createMatchMetadata
31 
32 
33 class AstrometryConfig(RefMatchConfig):
34  wcsFitter = pexConfig.ConfigurableField(
35  target=FitTanSipWcsTask,
36  doc="WCS fitter",
37  )
38  forceKnownWcs = pexConfig.Field(
39  dtype=bool,
40  doc="If True then load reference objects and match sources but do not fit a WCS; " +
41  " this simply controls whether 'run' calls 'solve' or 'loadAndMatch'",
42  default=False,
43  )
44  maxIter = pexConfig.RangeField(
45  doc="maximum number of iterations of match sources and fit WCS" +
46  "ignored if not fitting a WCS",
47  dtype=int,
48  default=3,
49  min=1,
50  )
51  minMatchDistanceArcSec = pexConfig.RangeField(
52  doc="the match distance below which further iteration is pointless (arcsec); "
53  "ignored if not fitting a WCS",
54  dtype=float,
55  default=0.001,
56  min=0,
57  )
58 
59 # The following block adds links to this task from the Task Documentation page.
60 ## \addtogroup LSST_task_documentation
61 ## \{
62 ## \page measAstrom_astrometryTask
63 ## \ref AstrometryTask_ "AstrometryTask"
64 ## Match an input source catalog with objects from a reference catalog and solve for the WCS
65 ## \}
66 
67 
68 class AstrometryTask(RefMatchTask):
69  """!Match an input source catalog with objects from a reference catalog and solve for the WCS
70 
71  @anchor AstrometryTask_
72 
73  @section meas_astrom_astrometry_Contents Contents
74 
75  - @ref meas_astrom_astrometry_Purpose
76  - @ref meas_astrom_astrometry_Initialize
77  - @ref meas_astrom_astrometry_IO
78  - @ref meas_astrom_astrometry_Config
79  - @ref meas_astrom_astrometry_Example
80  - @ref meas_astrom_astrometry_Debug
81 
82  @section meas_astrom_astrometry_Purpose Description
83 
84  Match input sourceCat with a reference catalog and solve for the Wcs
85 
86  There are three steps, each performed by different subtasks:
87  - Find position reference stars that overlap the exposure
88  - Match sourceCat to position reference stars
89  - Fit a WCS based on the matches
90 
91  @section meas_astrom_astrometry_Initialize Task initialisation
92 
93  @copydoc \_\_init\_\_
94 
95  @section meas_astrom_astrometry_IO Invoking the Task
96 
97  @copydoc run
98 
99  @copydoc loadAndMatch
100 
101  @section meas_astrom_astrometry_Config Configuration parameters
102 
103  See @ref AstrometryConfig
104 
105  @section meas_astrom_astrometry_Example A complete example of using AstrometryTask
106 
107  See \ref meas_photocal_photocal_Example.
108 
109  @section meas_astrom_astrometry_Debug Debug variables
110 
111  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
112  flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about
113  @b debug.py files.
114 
115  The available variables in AstrometryTask are:
116  <DL>
117  <DT> @c display (bool)
118  <DD> If True display information at three stages: after finding reference objects,
119  after matching sources to reference objects, and after fitting the WCS; defaults to False
120  <DT> @c frame (int)
121  <DD> ds9 frame to use to display the reference objects; the next two frames are used
122  to display the match list and the results of the final WCS; defaults to 0
123  </DL>
124 
125  To investigate the @ref meas_astrom_astrometry_Debug, put something like
126  @code{.py}
127  import lsstDebug
128  def DebugInfo(name):
129  debug = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
130  if name == "lsst.meas.astrom.astrometry":
131  debug.display = True
132 
133  return debug
134 
135  lsstDebug.Info = DebugInfo
136  @endcode
137  into your debug.py file and run this task with the @c --debug flag.
138  """
139  ConfigClass = AstrometryConfig
140  _DefaultName = "astrometricSolver"
141 
142  def __init__(self, refObjLoader, schema=None, **kwargs):
143  """!Construct an AstrometryTask
144 
145  @param[in] refObjLoader A reference object loader object
146  @param[in] schema ignored; available for compatibility with an older astrometry task
147  @param[in] kwargs additional keyword arguments for pipe_base Task.\_\_init\_\_
148  """
149  RefMatchTask.__init__(self, refObjLoader, schema=schema, **kwargs)
150  self.makeSubtask("wcsFitter")
151 
152  @pipeBase.timeMethod
153  def run(self, sourceCat, exposure):
154  """!Load reference objects, match sources and optionally fit a WCS
155 
156  This is a thin layer around solve or loadAndMatch, depending on config.forceKnownWcs
157 
158  @param[in,out] exposure exposure whose WCS is to be fit
159  The following are read only:
160  - bbox
161  - calib (may be absent)
162  - filter (may be unset)
163  - detector (if wcs is pure tangent; may be absent)
164  The following are updated:
165  - wcs (the initial value is used as an initial guess, and is required)
166  @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
167  @return an lsst.pipe.base.Struct with these fields:
168  - refCat reference object catalog of objects that overlap the exposure (with some margin)
169  (an lsst::afw::table::SimpleCatalog)
170  - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
171  - scatterOnSky median on-sky separation between reference objects and sources in "matches"
172  (an lsst.afw.geom.Angle), or None if config.forceKnownWcs True
173  - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
174  """
175  if self.config.forceKnownWcs:
176  res = self.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
177  res.scatterOnSky = None
178  else:
179  res = self.solve(exposure=exposure, sourceCat=sourceCat)
180  return res
181 
182  @pipeBase.timeMethod
183  def solve(self, exposure, sourceCat):
184  """!Load reference objects overlapping an exposure, match to sources and fit a WCS
185 
186  @return an lsst.pipe.base.Struct with these fields:
187  - refCat reference object catalog of objects that overlap the exposure (with some margin)
188  (an lsst::afw::table::SimpleCatalog)
189  - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
190  - scatterOnSky median on-sky separation between reference objects and sources in "matches"
191  (an lsst.afw.geom.Angle)
192  - matchMeta metadata needed to unpersist matches (an lsst.daf.base.PropertyList)
193 
194  @note ignores config.forceKnownWcs
195  """
196  import lsstDebug
197  debug = lsstDebug.Info(__name__)
198 
199  matchMeta = createMatchMetadata(exposure, border=self.refObjLoader.config.pixelMargin)
200  expMd = self._getExposureMetadata(exposure)
201 
202  loadRes = self.refObjLoader.loadPixelBox(
203  bbox=expMd.bbox,
204  wcs=expMd.wcs,
205  filterName=expMd.filterName,
206  calib=expMd.calib,
207  )
208  if debug.display:
209  frame = int(debug.frame)
211  refCat=loadRes.refCat,
212  sourceCat=sourceCat,
213  exposure=exposure,
214  bbox=expMd.bbox,
215  frame=frame,
216  title="Reference catalog",
217  )
218 
219  res = None
220  wcs = expMd.wcs
221  maxMatchDist = None
222  for i in range(self.config.maxIter):
223  iterNum = i + 1
224  try:
225  tryRes = self._matchAndFitWcs( # refCat, sourceCat, refFluxField, bbox, wcs, exposure=None
226  refCat=loadRes.refCat,
227  sourceCat=sourceCat,
228  refFluxField=loadRes.fluxField,
229  bbox=expMd.bbox,
230  wcs=wcs,
231  exposure=exposure,
232  maxMatchDist=maxMatchDist,
233  )
234  except Exception as e:
235  # if we have had a succeessful iteration then use that; otherwise fail
236  if i > 0:
237  self.log.info("Fit WCS iter %d failed; using previous iteration: %s" % (iterNum, e))
238  iterNum -= 1
239  break
240  else:
241  raise
242 
243  tryMatchDist = self._computeMatchStatsOnSky(tryRes.matches)
244  self.log.debug(
245  "Match and fit WCS iteration %d: found %d matches with scatter = %0.3f +- %0.3f arcsec; "
246  "max match distance = %0.3f arcsec",
247  iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
248  tryMatchDist.distStdDev.asArcseconds(), tryMatchDist.maxMatchDist.asArcseconds())
249  if maxMatchDist is not None:
250  if tryMatchDist.maxMatchDist >= maxMatchDist:
251  self.log.debug(
252  "Iteration %d had no better maxMatchDist; using previous iteration", iterNum)
253  iterNum -= 1
254  break
255 
256  maxMatchDist = tryMatchDist.maxMatchDist
257  res = tryRes
258  wcs = res.wcs
259  if tryMatchDist.maxMatchDist.asArcseconds() < self.config.minMatchDistanceArcSec:
260  self.log.debug(
261  "Max match distance = %0.3f arcsec < %0.3f = config.minMatchDistanceArcSec; "
262  "that's good enough",
263  tryMatchDist.maxMatchDist.asArcseconds(), self.config.minMatchDistanceArcSec)
264  break
265 
266  self.log.info(
267  "Matched and fit WCS in %d iterations; "
268  "found %d matches with scatter = %0.3f +- %0.3f arcsec" %
269  (iterNum, len(tryRes.matches), tryMatchDist.distMean.asArcseconds(),
270  tryMatchDist.distStdDev.asArcseconds()))
271 
272  exposure.setWcs(res.wcs)
273 
274  return pipeBase.Struct(
275  refCat=loadRes.refCat,
276  matches=res.matches,
277  scatterOnSky=res.scatterOnSky,
278  matchMeta=matchMeta,
279  )
280 
281  @pipeBase.timeMethod
282  def _matchAndFitWcs(self, refCat, sourceCat, refFluxField, bbox, wcs, maxMatchDist=None,
283  exposure=None):
284  """!Match sources to reference objects and fit a WCS
285 
286  @param[in] refCat catalog of reference objects
287  @param[in] sourceCat catalog of sources detected on the exposure (an lsst.afw.table.SourceCatalog)
288  @param[in] refFluxField field of refCat to use for flux
289  @param[in] bbox bounding box of exposure (an lsst.afw.geom.Box2I)
290  @param[in] wcs initial guess for WCS of exposure (an lsst.afw.image.Wcs)
291  @param[in] maxMatchDist maximum on-sky distance between reference objects and sources
292  (an lsst.afw.geom.Angle); if None then use the matcher's default
293  @param[in] exposure exposure whose WCS is to be fit, or None; used only for the debug display
294 
295  @return an lsst.pipe.base.Struct with these fields:
296  - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
297  - wcs the fit WCS (an lsst.afw.image.Wcs)
298  - scatterOnSky median on-sky separation between reference objects and sources in "matches"
299  (an lsst.afw.geom.Angle)
300  """
301  import lsstDebug
302  debug = lsstDebug.Info(__name__)
303  matchRes = self.matcher.matchObjectsToSources(
304  refCat=refCat,
305  sourceCat=sourceCat,
306  wcs=wcs,
307  refFluxField=refFluxField,
308  maxMatchDist=maxMatchDist,
309  )
310  self.log.debug("Found %s matches", len(matchRes.matches))
311  if debug.display:
312  frame = int(debug.frame)
314  refCat=refCat,
315  sourceCat=matchRes.usableSourceCat,
316  matches=matchRes.matches,
317  exposure=exposure,
318  bbox=bbox,
319  frame=frame + 1,
320  title="Initial WCS",
321  )
322 
323  self.log.debug("Fitting WCS")
324  fitRes = self.wcsFitter.fitWcs(
325  matches=matchRes.matches,
326  initWcs=wcs,
327  bbox=bbox,
328  refCat=refCat,
329  sourceCat=sourceCat,
330  exposure=exposure,
331  )
332  fitWcs = fitRes.wcs
333  scatterOnSky = fitRes.scatterOnSky
334  if debug.display:
335  frame = int(debug.frame)
337  refCat=refCat,
338  sourceCat=matchRes.usableSourceCat,
339  matches=matchRes.matches,
340  exposure=exposure,
341  bbox=bbox,
342  frame=frame + 2,
343  title="Fit TAN-SIP WCS",
344  )
345 
346  return pipeBase.Struct(
347  matches=matchRes.matches,
348  wcs=fitWcs,
349  scatterOnSky=scatterOnSky,
350  )
def _matchAndFitWcs
Match sources to reference objects and fit a WCS.
Definition: astrometry.py:283
def __init__
Construct an AstrometryTask.
Definition: astrometry.py:142
def run
Load reference objects, match sources and optionally fit a WCS.
Definition: astrometry.py:153
def solve
Load reference objects overlapping an exposure, match to sources and fit a WCS.
Definition: astrometry.py:183
Match an input source catalog with objects from a reference catalog and solve for the WCS...
Definition: astrometry.py:68