lsst.meas.algorithms  13.0-24-g22030a45+12
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.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
95 
96  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
97  self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
98  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
99 
100  fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,)
101  self.fluxKey = schema[fluxPrefix + "flux"].asKey()
102  self.fluxFlagKey = schema[fluxPrefix + "flag"].asKey()
103  self.fluxSigmaKey = schema[fluxPrefix + "fluxSigma"].asKey()
104 
105  def _isMultiple_vector(self, sourceCat):
106  """Return True for each source that is likely multiple sources."""
107  test = (sourceCat.get(self.parentKey) != 0) | (sourceCat.get(self.nChildKey) != 0)
108  # have to currently manage footprints on a source-by-source basis.
109  for i, cat in enumerate(sourceCat):
110  footprint = cat.getFootprint()
111  test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1)
112  return test
113 
114  def _isMultiple(self, source):
115  """Return True if source is likely multiple sources."""
116  if (source.get(self.parentKey) != 0) or (source.get(self.nChildKey) != 0):
117  return True
118  footprint = source.getFootprint()
119  return footprint is not None and len(footprint.getPeaks()) > 1
120 
121  def _hasCentroid_vector(self, sourceCat):
122  """Return True for each source that has a valid centroid"""
123  return np.isfinite(sourceCat.get(self.centroidXKey)) \
124  & np.isfinite(sourceCat.get(self.centroidYKey)) \
125  & ~sourceCat.get(self.centroidFlagKey)
126 
127  def _hasCentroid(self, source):
128  """Return True if the source has a valid centroid"""
129  centroid = source.getCentroid()
130  return np.all(np.isfinite(centroid)) and not source.getCentroidFlag()
131 
132  def _goodSN_vector(self, sourceCat):
133  """Return True for each source that has Signal/Noise > config.minSnr."""
134  if self.config.minSnr <= 0:
135  return True
136  else:
137  return sourceCat.get(self.fluxKey)/sourceCat.get(self.fluxSigmaKey) > self.config.minSnr
138 
139  def _goodSN(self, source):
140  """Return True if source has Signal/Noise > config.minSnr."""
141  return (self.config.minSnr <= 0 or
142  (source.get(self.fluxKey)/source.get(self.fluxSigmaKey) > self.config.minSnr))
143 
144  def _isUsable_vector(self, sourceCat):
145  """
146  Return True for each source that is usable for matching, even if it may
147  have a poor centroid.
148 
149  For a source to be usable it must:
150  - have a valid centroid
151  - not be deblended
152  - have a valid flux (of the type specified in this object's constructor)
153  - have adequate signal-to-noise
154  """
155 
156  return self._hasCentroid_vector(sourceCat) \
157  & ~self._isMultiple_vector(sourceCat) \
158  & self._goodSN_vector(sourceCat) \
159  & ~sourceCat.get(self.fluxFlagKey)
160 
161  def _isUsable(self, source):
162  """
163  Return True if the source is usable for matching, even if it may have a
164  poor centroid.
165 
166  For a source to be usable it must:
167  - have a valid centroid
168  - not be deblended
169  - have a valid flux (of the type specified in this object's constructor)
170  - have adequate signal-to-noise
171  """
172  return self._hasCentroid(source) \
173  and not self._isMultiple(source) \
174  and not source.get(self.fluxFlagKey) \
175  and self._goodSN(source)
176 
177  def _isGood_vector(self, sourceCat):
178  """
179  Return True for each source that is usable for matching and likely has a
180  good centroid.
181 
182  The additional tests for a good centroid, beyond isUsable, are:
183  - not interpolated in the center
184  - not saturated
185  - not near the edge
186  """
187 
188  return self._isUsable_vector(sourceCat) \
189  & ~sourceCat.get(self.saturatedKey) \
190  & ~sourceCat.get(self.interpolatedCenterKey) \
191  & ~sourceCat.get(self.edgeKey)
192 
193  def _isGood(self, source):
194  """
195  Return True if source is usable for matching and likely has a good centroid.
196 
197  The additional tests for a good centroid, beyond isUsable, are:
198  - not interpolated in the center
199  - not saturated
200  - not near the edge
201  """
202  return self._isUsable(source) \
203  and not source.get(self.saturatedKey) \
204  and not source.get(self.interpolatedCenterKey) \
205  and not source.get(self.edgeKey)
206 
207 
208 sourceSelectorRegistry.register("astrometry", AstrometrySourceSelectorTask)