lsst.meas.algorithms  15.0-4-g20968767
astrometrySourceSelector.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 from __future__ import absolute_import, division, print_function
24 
25 import numpy as np
26 
27 from lsst.afw import table
28 import lsst.pex.config as pexConfig
29 from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry
30 from lsst.pipe.base import Struct
31 from functools import reduce
32 
33 
35  sourceFluxType = pexConfig.Field(
36  doc="Type of source flux; typically one of Ap or Psf",
37  dtype=str,
38  default="Ap",
39  )
40  minSnr = pexConfig.Field(
41  dtype=float,
42  doc="Minimum allowed signal-to-noise ratio for sources used for matching "
43  "(in the flux specified by sourceFluxType); <= 0 for no limit",
44  default=10,
45  )
46 
47 
49  """
50  !Select sources that are useful for astrometry.
51 
52  Good astrometry sources have high signal/noise, are non-blended, and
53  did not have certain "bad" flags set during source extraction. They need not
54  be PSF sources, just have reliable centroids.
55  """
56  ConfigClass = AstrometrySourceSelectorConfig
57 
58  def __init__(self, *args, **kwargs):
59  BaseSourceSelectorTask.__init__(self, *args, **kwargs)
60 
61  def selectSources(self, sourceCat, matches=None):
62  """
63  !Return a catalog of sources: a subset of sourceCat.
64 
65  If sourceCat is cotiguous in memory, will use vectorized tests for ~100x
66  execution speed advantage over non-contiguous catalogs. This would be
67  even faster if we didn't have to check footprints for multiple peaks.
68 
69  @param[in] sourceCat catalog of sources that may be sources
70  (an lsst.afw.table.SourceCatalog)
71 
72  @return a pipeBase.Struct containing:
73  - sourceCat a catalog of sources
74  """
75  self._getSchemaKeys(sourceCat.schema)
76 
77  if sourceCat.isContiguous():
78  bad = reduce(lambda x, y: np.logical_or(x, sourceCat.get(y)), self.config.badFlags, False)
79  good = self._isGood_vector(sourceCat)
80  result = sourceCat[good & ~bad]
81  else:
82  result = table.SourceCatalog(sourceCat.table)
83  for i, source in enumerate(sourceCat):
84  if self._isGood(source) and not self._isBad(source):
85  result.append(source)
86  return Struct(sourceCat=result)
87 
88  def _getSchemaKeys(self, schema):
89  """Extract and save the necessary keys from schema with asKey."""
90  self.parentKey = schema["parent"].asKey()
91  self.nChildKey = schema["deblend_nChild"].asKey()
92  self.centroidXKey = schema["slot_Centroid_x"].asKey()
93  self.centroidYKey = schema["slot_Centroid_y"].asKey()
94  self.centroidXSigmaKey = schema["slot_Centroid_xSigma"].asKey()
95  self.centroidYSigmaKey = schema["slot_Centroid_ySigma"].asKey()
96  self.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
97 
98  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
99  self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
100  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
101 
102  fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,)
103  self.fluxKey = schema[fluxPrefix + "flux"].asKey()
104  self.fluxFlagKey = schema[fluxPrefix + "flag"].asKey()
105  self.fluxSigmaKey = schema[fluxPrefix + "fluxSigma"].asKey()
106 
107  def _isMultiple_vector(self, sourceCat):
108  """Return True for each source that is likely multiple sources."""
109  test = (sourceCat.get(self.parentKey) != 0) | (sourceCat.get(self.nChildKey) != 0)
110  # have to currently manage footprints on a source-by-source basis.
111  for i, cat in enumerate(sourceCat):
112  footprint = cat.getFootprint()
113  test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1)
114  return test
115 
116  def _isMultiple(self, source):
117  """Return True if source is likely multiple sources."""
118  if (source.get(self.parentKey) != 0) or (source.get(self.nChildKey) != 0):
119  return True
120  footprint = source.getFootprint()
121  return footprint is not None and len(footprint.getPeaks()) > 1
122 
123  def _hasCentroid_vector(self, sourceCat):
124  """Return True for each source that has a valid centroid"""
125  def checkNonfiniteCentroid():
126  """Return True for sources with non-finite centroids."""
127  return ~np.isfinite(sourceCat.get(self.centroidXKey)) | ~np.isfinite(sourceCat.get(self.centroidYKey))
128  assert ~checkNonfiniteCentroid().any(), \
129  "Centroids not finite for %d unflagged sources." % (checkNonfiniteCentroid().sum())
130  return np.isfinite(sourceCat.get(self.centroidXSigmaKey)) \
131  & np.isfinite(sourceCat.get(self.centroidYSigmaKey)) \
132  & ~sourceCat.get(self.centroidFlagKey)
133 
134  def _hasCentroid(self, source):
135  """Return True if the source has a valid centroid"""
136  assert np.all(np.isfinite(source.getCentroid())), 'Centroid not finite for source: %s' % source
137  return np.all(np.isfinite(source.getCentroidErr())) and not source.getCentroidFlag()
138 
139  def _goodSN_vector(self, sourceCat):
140  """Return True for each source that has Signal/Noise > config.minSnr."""
141  if self.config.minSnr <= 0:
142  return True
143  else:
144  with np.errstate(invalid="ignore"): # suppress NAN warnings
145  return sourceCat.get(self.fluxKey)/sourceCat.get(self.fluxSigmaKey) > self.config.minSnr
146 
147  def _goodSN(self, source):
148  """Return True if source has Signal/Noise > config.minSnr."""
149  return (self.config.minSnr <= 0 or
150  (source.get(self.fluxKey)/source.get(self.fluxSigmaKey) > self.config.minSnr))
151 
152  def _isUsable_vector(self, sourceCat):
153  """
154  Return True for each source that is usable for matching, even if it may
155  have a poor centroid.
156 
157  For a source to be usable it must:
158  - have a valid centroid
159  - not be deblended
160  - have a valid flux (of the type specified in this object's constructor)
161  - have adequate signal-to-noise
162  """
163 
164  return self._hasCentroid_vector(sourceCat) \
165  & ~self._isMultiple_vector(sourceCat) \
166  & self._goodSN_vector(sourceCat) \
167  & ~sourceCat.get(self.fluxFlagKey)
168 
169  def _isUsable(self, source):
170  """
171  Return True if the source is usable for matching, even if it may have a
172  poor centroid.
173 
174  For a source to be usable it must:
175  - have a valid centroid
176  - not be deblended
177  - have a valid flux (of the type specified in this object's constructor)
178  - have adequate signal-to-noise
179  """
180  return self._hasCentroid(source) \
181  and not self._isMultiple(source) \
182  and not source.get(self.fluxFlagKey) \
183  and self._goodSN(source)
184 
185  def _isGood_vector(self, sourceCat):
186  """
187  Return True for each source that is usable for matching and likely has a
188  good centroid.
189 
190  The additional tests for a good centroid, beyond isUsable, are:
191  - not interpolated in the center
192  - not saturated
193  - not near the edge
194  """
195 
196  return self._isUsable_vector(sourceCat) \
197  & ~sourceCat.get(self.saturatedKey) \
198  & ~sourceCat.get(self.interpolatedCenterKey) \
199  & ~sourceCat.get(self.edgeKey)
200 
201  def _isGood(self, source):
202  """
203  Return True if source is usable for matching and likely has a good centroid.
204 
205  The additional tests for a good centroid, beyond isUsable, are:
206  - not interpolated in the center
207  - not saturated
208  - not near the edge
209  """
210  return self._isUsable(source) \
211  and not source.get(self.saturatedKey) \
212  and not source.get(self.interpolatedCenterKey) \
213  and not source.get(self.edgeKey)
214 
215 
216 sourceSelectorRegistry.register("astrometry", AstrometrySourceSelectorTask)