LSST Applications g180d380827+78227d2bc4,g2079a07aa2+86d27d4dc4,g2305ad1205+bdd7851fe3,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3d1719c13e+260d7c3927,g3ddfee87b4+723a6db5f3,g487adcacf7+29e55ea757,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+9443c4b912,g62aa8f1a4b+7e2ea9cd42,g858d7b2824+260d7c3927,g864b0138d7+8498d97249,g95921f966b+dffe86973d,g991b906543+260d7c3927,g99cad8db69+4809d78dd9,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+260d7c3927,gba4ed39666+c2a2e4ac27,gbb8dafda3b+e22341fd87,gbd998247f1+585e252eca,gc120e1dc64+713f94b854,gc28159a63d+c6a8a0fb72,gc3e9b769f7+385ea95214,gcf0d15dbbd+723a6db5f3,gdaeeff99f8+f9a426f77a,ge6526c86ff+fde82a80b9,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,w.2024.18
LSST Data Management Base Package
Loading...
Searching...
No Matches
astrometrySourceSelector.py
Go to the documentation of this file.
1# This file is part of meas_algorithms.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
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 GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22"""Select sources that are useful for astrometry.
23
24Such sources have good signal-to-noise, are well centroided, not blended,
25and not flagged with a handful of "bad" flags.
26"""
27
28__all__ = ["AstrometrySourceSelectorConfig", "AstrometrySourceSelectorTask"]
29
30from deprecated.sphinx import deprecated
31
32import numpy as np
33
34import lsst.pex.config as pexConfig
35from lsst.pex.exceptions import NotFoundError
36from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry
37from lsst.pipe.base import Struct
38from functools import reduce
39
40
42 badFlags = pexConfig.ListField(
43 doc="List of flags which cause a source to be rejected as bad",
44 dtype=str,
45 default=[
46 "base_PixelFlags_flag_edge",
47 "base_PixelFlags_flag_interpolatedCenter",
48 "base_PixelFlags_flag_saturatedCenter",
49 "base_PixelFlags_flag_crCenter",
50 "base_PixelFlags_flag_bad",
51 ],
52 )
53 sourceFluxType = pexConfig.Field(
54 doc="Type of source flux; typically one of Ap or Psf",
55 dtype=str,
56 default="Ap",
57 )
58 minSnr = pexConfig.Field(
59 dtype=float,
60 doc="Minimum allowed signal-to-noise ratio for sources used for matching "
61 "(in the flux specified by sourceFluxType); <= 0 for no limit",
62 default=10,
63 )
64
65
66# remove this file on DM-41146
67@deprecated(reason=("This Task has been replaced by an appropriately configured ScienceSourceSelector."
68 " See `AstrometryConfig.setDefaults` in meas_astrom for an example config that was "
69 "made to closely match this Task. Will be removed after v27."),
70 version="v27.0", category=FutureWarning)
71@pexConfig.registerConfigurable("astrometry", sourceSelectorRegistry)
73 """Select sources that are useful for astrometry.
74
75 Good astrometry sources have high signal/noise, are non-blended, and
76 did not have certain "bad" flags set during source extraction. They need not
77 be PSF sources, just have reliable centroids.
78 """
79 ConfigClass = AstrometrySourceSelectorConfig
80
81 def __init__(self, *args, **kwargs):
82 BaseSourceSelectorTask.__init__(self, *args, **kwargs)
83
84 def selectSources(self, sourceCat, matches=None, exposure=None):
85 """Return a selection of sources that are useful for astrometry.
86
87 Parameters
88 ----------
89 sourceCat : `lsst.afw.table.SourceCatalog`
90 Catalog of sources to select from.
91 This catalog must be contiguous in memory.
92 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
93 Ignored in this SourceSelector.
94 exposure : `lsst.afw.image.Exposure` or None
95 The exposure the catalog was built from; used for debug display.
96
97 Returns
98 -------
99 struct : `lsst.pipe.base.Struct`
100 The struct contains the following data:
101
102 ``selected``
103 Boolean array of sources that were selected, same length as
104 sourceCat. (`numpy.ndarray` of `bool`)
105 """
106 self._getSchemaKeys(sourceCat.schema)
107
108 bad = reduce(lambda x, y: np.logical_or(x, sourceCat[y]), self.config.badFlags, False)
109 good = self._isGood(sourceCat)
110 return Struct(selected=good & ~bad)
111
112 def _getSchemaKeys(self, schema):
113 """Extract and save the necessary keys from schema with asKey.
114 """
115 self.parentKey = schema["parent"].asKey()
116 self.nChildKey = schema["deblend_nChild"].asKey()
117 self.centroidXKey = schema["slot_Centroid_x"].asKey()
118 self.centroidYKey = schema["slot_Centroid_y"].asKey()
119 self.centroidXErrKey = schema["slot_Centroid_xErr"].asKey()
120 self.centroidYErrKey = schema["slot_Centroid_yErr"].asKey()
121 self.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
122 try:
123 self.primaryKey = schema["detect_isPrimary"].asKey()
124 except NotFoundError:
125 self.primaryKey = None
126
127 self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
128 self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
129 self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey()
130
131 fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,)
132 self.instFluxKey = schema[fluxPrefix + "instFlux"].asKey()
133 self.fluxFlagKey = schema[fluxPrefix + "flag"].asKey()
134 self.instFluxErrKey = schema[fluxPrefix + "instFluxErr"].asKey()
135
136 def _isMultiple(self, sourceCat):
137 """Return True for each source that is likely multiple sources.
138 """
139 test = (sourceCat[self.parentKey] != 0) | (sourceCat[self.nChildKey] != 0)
140 # have to currently manage footprints on a source-by-source basis.
141 for i, cat in enumerate(sourceCat):
142 footprint = cat.getFootprint()
143 test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1)
144 return test
145
146 def _hasCentroid(self, sourceCat):
147 """Return True for each source that has a valid centroid
148 """
149 def checkNonfiniteCentroid():
150 """Return True for sources with non-finite centroids.
151 """
152 return ~np.isfinite(sourceCat[self.centroidXKey]) | \
153 ~np.isfinite(sourceCat[self.centroidYKey])
154 assert ~checkNonfiniteCentroid().any(), \
155 "Centroids not finite for %d unflagged sources." % (checkNonfiniteCentroid().sum())
156 return np.isfinite(sourceCat[self.centroidXErrKey]) \
157 & np.isfinite(sourceCat[self.centroidYErrKey]) \
158 & ~sourceCat[self.centroidFlagKey]
159
160 def _goodSN(self, sourceCat):
161 """Return True for each source that has Signal/Noise > config.minSnr.
162 """
163 if self.config.minSnr <= 0:
164 return True
165 else:
166 with np.errstate(invalid="ignore"): # suppress NAN warnings
167 return sourceCat[self.instFluxKey]/sourceCat[self.instFluxErrKey] > self.config.minSnr
168
169 def _isUsable(self, sourceCat):
170 """Return True for each source that is usable for matching, even if it may
171 have a poor centroid.
172
173 For a source to be usable it must:
174 - have a valid centroid
175 - not be deblended
176 - have a valid flux (of the type specified in this object's constructor)
177 - have adequate signal-to-noise
178 """
179
180 return self._hasCentroid(sourceCat) \
181 & ~self._isMultiple(sourceCat) \
182 & self._goodSN(sourceCat) \
183 & ~sourceCat[self.fluxFlagKey]
184
185 def _isPrimary(self, sourceCat):
186 """Return True if this is a primary source.
187 """
188 if self.primaryKey:
189 return sourceCat[self.primaryKey]
190 else:
191 return np.ones(len(sourceCat), dtype=bool)
192
193 def _isGood(self, sourceCat):
194 """Return True for each source that is usable for matching and likely has a
195 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
203 return self._isUsable(sourceCat) \
204 & self._isPrimary(sourceCat) \
205 & ~sourceCat[self.saturatedKey] \
206 & ~sourceCat[self.interpolatedCenterKey] \
207 & ~sourceCat[self.edgeKey]
208
209 def _isBadFlagged(self, source):
210 """Return True if any of config.badFlags are set for this source.
211 """
212 return any(source[flag] for flag in self.config.badFlags)