LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
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 """Select sources that are useful for astrometry.
24 
25 Such sources have good signal-to-noise, are well centroided, not blended,
26 and not flagged with a handful of "bad" flags.
27 """
28 
29 __all__ = ["AstrometrySourceSelectorConfig", "AstrometrySourceSelectorTask"]
30 
31 import numpy as np
32 
33 import lsst.pex.config as pexConfig
34 from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry
35 from lsst.pipe.base import Struct
36 from functools import reduce
37 
38 
40  badFlags = pexConfig.ListField(
41  doc="List of flags which cause a source to be rejected as bad",
42  dtype=str,
43  default=[
44  "base_PixelFlags_flag_edge",
45  "base_PixelFlags_flag_interpolatedCenter",
46  "base_PixelFlags_flag_saturatedCenter",
47  "base_PixelFlags_flag_crCenter",
48  "base_PixelFlags_flag_bad",
49  ],
50  )
51  sourceFluxType = pexConfig.Field(
52  doc="Type of source flux; typically one of Ap or Psf",
53  dtype=str,
54  default="Ap",
55  )
56  minSnr = pexConfig.Field(
57  dtype=float,
58  doc="Minimum allowed signal-to-noise ratio for sources used for matching "
59  "(in the flux specified by sourceFluxType); <= 0 for no limit",
60  default=10,
61  )
62 
63 
64 @pexConfig.registerConfigurable("astrometry", sourceSelectorRegistry)
66  """Select sources that are useful for astrometry.
67 
68  Good astrometry sources have high signal/noise, are non-blended, and
69  did not have certain "bad" flags set during source extraction. They need not
70  be PSF sources, just have reliable centroids.
71  """
72  ConfigClass = AstrometrySourceSelectorConfig
73 
74  def __init__(self, *args, **kwargs):
75  BaseSourceSelectorTask.__init__(self, *args, **kwargs)
76 
77  def selectSources(self, sourceCat, matches=None, exposure=None):
78  """Return a selection of sources that are useful for astrometry.
79 
80  Parameters:
81  -----------
82  sourceCat : `lsst.afw.table.SourceCatalog`
83  Catalog of sources to select from.
84  This catalog must be contiguous in memory.
85  matches : `list` of `lsst.afw.table.ReferenceMatch` or None
86  Ignored in this SourceSelector.
87  exposure : `lsst.afw.image.Exposure` or None
88  The exposure the catalog was built from; used for debug display.
89 
90  Return
91  ------
92  struct : `lsst.pipe.base.Struct`
93  The struct contains the following data:
94 
95  - selected : `array` of `bool``
96  Boolean array of sources that were selected, same length as
97  sourceCat.
98  """
99  self._getSchemaKeys_getSchemaKeys(sourceCat.schema)
100 
101  bad = reduce(lambda x, y: np.logical_or(x, sourceCat.get(y)), self.config.badFlags, False)
102  good = self._isGood_isGood(sourceCat)
103  return Struct(selected=good & ~bad)
104 
105  def _getSchemaKeys(self, schema):
106  """Extract and save the necessary keys from schema with asKey.
107  """
108  self.parentKeyparentKey = schema["parent"].asKey()
109  self.nChildKeynChildKey = schema["deblend_nChild"].asKey()
110  self.centroidXKeycentroidXKey = schema["slot_Centroid_x"].asKey()
111  self.centroidYKeycentroidYKey = schema["slot_Centroid_y"].asKey()
112  self.centroidXErrKeycentroidXErrKey = schema["slot_Centroid_xErr"].asKey()
113  self.centroidYErrKeycentroidYErrKey = schema["slot_Centroid_yErr"].asKey()
114  self.centroidFlagKeycentroidFlagKey = schema["slot_Centroid_flag"].asKey()
115 
116  self.edgeKeyedgeKey = schema["base_PixelFlags_flag_edge"].asKey()
117  self.interpolatedCenterKeyinterpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
118  self.saturatedKeysaturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
119 
120  fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,)
121  self.instFluxKeyinstFluxKey = schema[fluxPrefix + "instFlux"].asKey()
122  self.fluxFlagKeyfluxFlagKey = schema[fluxPrefix + "flag"].asKey()
123  self.instFluxErrKeyinstFluxErrKey = schema[fluxPrefix + "instFluxErr"].asKey()
124 
125  def _isMultiple(self, sourceCat):
126  """Return True for each source that is likely multiple sources.
127  """
128  test = (sourceCat.get(self.parentKeyparentKey) != 0) | (sourceCat.get(self.nChildKeynChildKey) != 0)
129  # have to currently manage footprints on a source-by-source basis.
130  for i, cat in enumerate(sourceCat):
131  footprint = cat.getFootprint()
132  test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1)
133  return test
134 
135  def _hasCentroid(self, sourceCat):
136  """Return True for each source that has a valid centroid
137  """
138  def checkNonfiniteCentroid():
139  """Return True for sources with non-finite centroids.
140  """
141  return ~np.isfinite(sourceCat.get(self.centroidXKeycentroidXKey)) | \
142  ~np.isfinite(sourceCat.get(self.centroidYKeycentroidYKey))
143  assert ~checkNonfiniteCentroid().any(), \
144  "Centroids not finite for %d unflagged sources." % (checkNonfiniteCentroid().sum())
145  return np.isfinite(sourceCat.get(self.centroidXErrKeycentroidXErrKey)) \
146  & np.isfinite(sourceCat.get(self.centroidYErrKeycentroidYErrKey)) \
147  & ~sourceCat.get(self.centroidFlagKeycentroidFlagKey)
148 
149  def _goodSN(self, sourceCat):
150  """Return True for each source that has Signal/Noise > config.minSnr.
151  """
152  if self.config.minSnr <= 0:
153  return True
154  else:
155  with np.errstate(invalid="ignore"): # suppress NAN warnings
156  return sourceCat.get(self.instFluxKeyinstFluxKey)/sourceCat.get(self.instFluxErrKeyinstFluxErrKey) > self.config.minSnr
157 
158  def _isUsable(self, sourceCat):
159  """Return True for each source that is usable for matching, even if it may
160  have a poor centroid.
161 
162  For a source to be usable it must:
163  - have a valid centroid
164  - not be deblended
165  - have a valid flux (of the type specified in this object's constructor)
166  - have adequate signal-to-noise
167  """
168 
169  return self._hasCentroid_hasCentroid(sourceCat) \
170  & ~self._isMultiple_isMultiple(sourceCat) \
171  & self._goodSN_goodSN(sourceCat) \
172  & ~sourceCat.get(self.fluxFlagKeyfluxFlagKey)
173 
174  def _isGood(self, sourceCat):
175  """Return True for each source that is usable for matching and likely has a
176  good centroid.
177 
178  The additional tests for a good centroid, beyond isUsable, are:
179  - not interpolated in the center
180  - not saturated
181  - not near the edge
182  """
183 
184  return self._isUsable_isUsable(sourceCat) \
185  & ~sourceCat.get(self.saturatedKeysaturatedKey) \
186  & ~sourceCat.get(self.interpolatedCenterKeyinterpolatedCenterKey) \
187  & ~sourceCat.get(self.edgeKeyedgeKey)
188 
189  def _isBadFlagged(self, source):
190  """Return True if any of config.badFlags are set for this source.
191  """
192  return any(source.get(flag) for flag in self.config.badFlags)
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.