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
astrometrySourceSelector.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2015 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 
24 import numpy as np
25 
26 from lsst.afw import table
27 import lsst.pex.config as pexConfig
28 from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry
29 from lsst.pipe.base import Struct
30 from functools import reduce
31 
32 
33 class AstrometrySourceSelectorConfig(BaseSourceSelectorConfig):
34  sourceFluxType = pexConfig.Field(
35  doc="Type of source flux; typically one of Ap or Psf",
36  dtype=str,
37  default="Ap",
38  )
39  minSnr = pexConfig.Field(
40  dtype=float,
41  doc="Minimum allowed signal-to-noise ratio for sources used for matching "
42  "(in the flux specified by sourceFluxType); <= 0 for no limit",
43  default=10,
44  )
45 
46 
47 class AstrometrySourceSelectorTask(BaseSourceSelectorTask):
48  """
49  !Select sources that are useful for astrometry.
50 
51  Good astrometry sources have high signal/noise, are non-blended, and
52  did not have certain "bad" flags set during source extraction. They need not
53  be PSF sources, just have reliable centroids.
54  """
55  ConfigClass = AstrometrySourceSelectorConfig
56 
57  def __init__(self, *args, **kwargs):
58  BaseSourceSelectorTask.__init__(self, *args, **kwargs)
59 
60  def selectSources(self, sourceCat, matches=None):
61  """
62  !Return a catalog of sources: a subset of sourceCat.
63 
64  If sourceCat is cotiguous in memory, will use vectorized tests for ~100x
65  execution speed advantage over non-contiguous catalogs. This would be
66  even faster if we didn't have to check footprints for multiple peaks.
67 
68  @param[in] sourceCat catalog of sources that may be sources
69  (an lsst.afw.table.SourceCatalog)
70 
71  @return a pipeBase.Struct containing:
72  - sourceCat a catalog of sources
73  """
74  self._getSchemaKeys(sourceCat.schema)
75 
76  if sourceCat.isContiguous():
77  bad = reduce(lambda x, y: np.logical_or(x, sourceCat.get(y)), self.config.badFlags, False)
78  good = self._isGood_vector(sourceCat)
79  result = sourceCat[good & ~bad]
80  else:
81  result = table.SourceCatalog(sourceCat.table)
82  for i, source in enumerate(sourceCat):
83  if self._isGood(source) and not self._isBad(source):
84  result.append(source)
85  return Struct(sourceCat=result)
86 
87  def _getSchemaKeys(self, schema):
88  """Extract and save the necessary keys from schema with asKey."""
89  self.parentKey = schema["parent"].asKey()
90  self.nChildKey = schema["deblend_nChild"].asKey()
91  self.centroidXKey = schema["slot_Centroid_x"].asKey()
92  self.centroidYKey = schema["slot_Centroid_y"].asKey()
93  self.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
94 
95  self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
96  self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
97  self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
98 
99  fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,)
100  self.fluxKey = schema[fluxPrefix + "flux"].asKey()
101  self.fluxFlagKey = schema[fluxPrefix + "flag"].asKey()
102  self.fluxSigmaKey = schema[fluxPrefix + "fluxSigma"].asKey()
103 
104  def _isMultiple_vector(self, sourceCat):
105  """Return True for each source that is likely multiple sources."""
106  test = (sourceCat.get(self.parentKey) != 0) | (sourceCat.get(self.nChildKey) != 0)
107  # have to currently manage footprints on a source-by-source basis.
108  for i, cat in enumerate(sourceCat):
109  footprint = cat.getFootprint()
110  test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1)
111  return test
112 
113  def _isMultiple(self, source):
114  """Return True if source is likely multiple sources."""
115  if (source.get(self.parentKey) != 0) or (source.get(self.nChildKey) != 0):
116  return True
117  footprint = source.getFootprint()
118  return footprint is not None and len(footprint.getPeaks()) > 1
119 
120  def _hasCentroid_vector(self, sourceCat):
121  """Return True for each source that has a valid centroid"""
122  return np.isfinite(sourceCat.get(self.centroidXKey)) \
123  & np.isfinite(sourceCat.get(self.centroidYKey)) \
124  & ~sourceCat.get(self.centroidFlagKey)
125 
126  def _hasCentroid(self, source):
127  """Return True if the source has a valid centroid"""
128  centroid = source.getCentroid()
129  return np.all(np.isfinite(centroid)) and not source.getCentroidFlag()
130 
131  def _goodSN_vector(self, sourceCat):
132  """Return True for each source that has Signal/Noise > config.minSnr."""
133  if self.config.minSnr <= 0:
134  return True
135  else:
136  return sourceCat.get(self.fluxKey)/sourceCat.get(self.fluxSigmaKey) > self.config.minSnr
137 
138  def _goodSN(self, source):
139  """Return True if source has Signal/Noise > config.minSnr."""
140  return (self.config.minSnr <= 0 or
141  (source.get(self.fluxKey)/source.get(self.fluxSigmaKey) > self.config.minSnr))
142 
143  def _isUsable_vector(self, sourceCat):
144  """
145  Return True for each source that is usable for matching, even if it may
146  have a poor centroid.
147 
148  For a source to be usable it must:
149  - have a valid centroid
150  - not be deblended
151  - have a valid flux (of the type specified in this object's constructor)
152  - have adequate signal-to-noise
153  """
154 
155  return self._hasCentroid_vector(sourceCat) \
156  & ~self._isMultiple_vector(sourceCat) \
157  & self._goodSN_vector(sourceCat) \
158  & ~sourceCat.get(self.fluxFlagKey)
159 
160  def _isUsable(self, source):
161  """
162  Return True if the source is usable for matching, even if it may have a
163  poor centroid.
164 
165  For a source to be usable it must:
166  - have a valid centroid
167  - not be deblended
168  - have a valid flux (of the type specified in this object's constructor)
169  - have adequate signal-to-noise
170  """
171  return self._hasCentroid(source) \
172  and not self._isMultiple(source) \
173  and not source.get(self.fluxFlagKey) \
174  and self._goodSN(source)
175 
176  def _isGood_vector(self, sourceCat):
177  """
178  Return True for each source that is usable for matching and likely has a
179  good centroid.
180 
181  The additional tests for a good centroid, beyond isUsable, are:
182  - not interpolated in the center
183  - not saturated
184  - not near the edge
185  """
186 
187  return self._isUsable_vector(sourceCat) \
188  & ~sourceCat.get(self.saturatedKey) \
189  & ~sourceCat.get(self.interpolatedCenterKey) \
190  & ~sourceCat.get(self.edgeKey)
191 
192  def _isGood(self, source):
193  """
194  Return True if source is usable for matching and likely has a good centroid.
195 
196  The additional tests for a good centroid, beyond isUsable, are:
197  - not interpolated in the center
198  - not saturated
199  - not near the edge
200  """
201  return self._isUsable(source) \
202  and not source.get(self.saturatedKey) \
203  and not source.get(self.interpolatedCenterKey) \
204  and not source.get(self.edgeKey)
205 
206 sourceSelectorRegistry.register("astrometry", AstrometrySourceSelectorTask)