LSST Applications g04a91732dc+146a938ab0,g07dc498a13+80b84b0d75,g0fba68d861+4c4f3dcb5c,g1409bbee79+80b84b0d75,g1a7e361dbc+80b84b0d75,g1fd858c14a+f6e422e056,g20f46db602+333b6c0f32,g35bb328faa+fcb1d3bbc8,g42c1b31a95+a1301e4c20,g4d2262a081+f1facf12e5,g4d39ba7253+9b833be27e,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+9b833be27e,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8852436030+790117df0f,g89139ef638+80b84b0d75,g8d6b6b353c+9b833be27e,g9125e01d80+fcb1d3bbc8,g989de1cb63+80b84b0d75,g9f33ca652e+9c6b68d7f3,ga9baa6287d+9b833be27e,gaaedd4e678+80b84b0d75,gabe3b4be73+1e0a283bba,gb1101e3267+9f3571abad,gb58c049af0+f03b321e39,gb90eeb9370+691e4ab549,gc741bbaa4f+2bcd3860df,gcf25f946ba+790117df0f,gd315a588df+5b65d88fe4,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+ee6a3faa19,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,gee8db133a9+2a6ae0040b,w.2025.10
LSST Data Management Base Package
Loading...
Searching...
No Matches
sourceSelector.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__all__ = ["BaseSourceSelectorConfig", "BaseSourceSelectorTask", "sourceSelectorRegistry",
23 "ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit",
24 "RequireFlags", "RequireUnresolved", "RequireFiniteRaDec", "RequirePrimary",
25 "ScienceSourceSelectorConfig", "ScienceSourceSelectorTask",
26 "ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask",
27 "NullSourceSelectorTask"
28 ]
29
30import abc
31import numpy as np
32import astropy.units as u
33import pandas
34import astropy.table
35import warnings
36
37import lsst.pex.config as pexConfig
38import lsst.pipe.base as pipeBase
39
40
41class BaseSourceSelectorConfig(pexConfig.Config):
42 pass
43
44
45class BaseSourceSelectorTask(pipeBase.Task, metaclass=abc.ABCMeta):
46 """Base class for source selectors
47
48 Source selectors are classes that perform a selection on a catalog
49 object given a set of criteria or cuts. They return the selected catalog
50 and can optionally set a specified Flag field in the input catalog to
51 identify if the source was selected.
52
53 Register all source selectors with the sourceSelectorRegistry using:
54 sourceSelectorRegistry.register(name, class)
55
56 Attributes
57 ----------
58 usesMatches : `bool`
59 A boolean variable specify if the inherited source selector uses
60 matches to an external catalog, and thus requires the ``matches``
61 argument to ``run()``.
62 """
63
64 ConfigClass = BaseSourceSelectorConfig
65 _DefaultName = "sourceSelector"
66 usesMatches = False
67
68 def __init__(self, **kwargs):
69 pipeBase.Task.__init__(self, **kwargs)
70
71 def run(self, sourceCat, sourceSelectedField=None, matches=None, exposure=None):
72 """Select sources and return them.
73
74 The input catalog must be contiguous in memory.
75
76 Parameters
77 ----------
78 sourceCat : Various table formats
79 Catalog of sources to select from. Can be
80 `lsst.afw.table.SourceCatalog` or `pandas.DataFrame` or
81 `astropy.table.Table`,
82 sourceSelectedField : `str` or None
83 Name of flag field in sourceCat to set for selected sources.
84 If set, will modify sourceCat in-place.
85 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
86 List of matches to use for source selection.
87 If usesMatches is set in source selector this field is required.
88 If not, it is ignored.
89 exposure : `lsst.afw.image.Exposure` or None
90 The exposure the catalog was built from; used for debug display.
91
92 Returns
93 -------
94 struct : `lsst.pipe.base.Struct`
95 The struct contains the following data:
96
97 ``sourceCat``
98 The catalog of sources that were selected.
99 (may not be memory-contiguous)
100 (`lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
101 or `astropy.table.Table`)
102 ``selected``
103 Boolean array of sources that were selected, same length as
104 sourceCat.
105 (`numpy.ndarray` of `bool`)
106
107 Raises
108 ------
109 RuntimeError
110 Raised if ``sourceCat`` is not contiguous.
111 """
112 if hasattr(sourceCat, 'isContiguous'):
113 # Check for continuity on afwTable catalogs
114 if not sourceCat.isContiguous():
115 raise RuntimeError("Input catalogs for source selection must be contiguous.")
116
117 result = self.selectSources(sourceCat=sourceCat,
118 exposure=exposure,
119 matches=matches)
120
121 if sourceSelectedField is not None:
122 sourceCat[sourceSelectedField] = result.selected
123
124 return pipeBase.Struct(sourceCat=sourceCat[result.selected],
125 selected=result.selected)
126
127 @abc.abstractmethod
128 def selectSources(self, sourceCat, matches=None, exposure=None):
129 """Return a selection of sources selected by some criteria.
130
131 Parameters
132 ----------
133 sourceCat : Various table formats
134 Catalog of sources to select from. Supports
135 `lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
136 or `astropy.table.Table`
137 This catalog must be contiguous in memory.
138 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
139 A list of lsst.afw.table.ReferenceMatch objects
140 exposure : `lsst.afw.image.Exposure` or None
141 The exposure the catalog was built from; used for debug display.
142
143 Returns
144 -------
145 struct : `lsst.pipe.base.Struct`
146 The struct contains the following data:
147
148 ``selected``
149 Boolean array of sources that were selected, same length as
150 sourceCat.
151 (`numpy.ndarray` of `bool`)
152 """
153 raise NotImplementedError("BaseSourceSelectorTask is abstract")
154
155
156sourceSelectorRegistry = pexConfig.makeRegistry(
157 doc="A registry of source selectors (subclasses of "
158 "BaseSourceSelectorTask)",
159)
160
161
162class BaseLimit(pexConfig.Config):
163 """Base class for selecting sources by applying a limit
164
165 This object can be used as a `lsst.pex.config.Config` for configuring
166 the limit, and then the `apply` method can be used to identify sources
167 in the catalog that match the configured limit.
168
169 This provides the `maximum` and `minimum` fields in the Config, and
170 a method to apply the limits to an array of values calculated by the
171 subclass.
172 """
173 minimum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value greater than this")
174 maximum = pexConfig.Field(dtype=float, optional=True, doc="Select objects with value less than this")
175
176 def apply(self, values):
177 """Apply the limits to an array of values
178
179 Subclasses should calculate the array of values and then
180 return the result of calling this method.
181
182 Parameters
183 ----------
184 values : `numpy.ndarray`
185 Array of values to which to apply limits.
186
187 Returns
188 -------
189 selected : `numpy.ndarray`
190 Boolean array indicating for each source whether it is selected
191 (True means selected).
192 """
193 selected = np.ones(len(values), dtype=bool)
194 with np.errstate(invalid="ignore"): # suppress NAN warnings
195 if self.minimum is not None:
196 selected &= values > self.minimum
197 if self.maximum is not None:
198 selected &= values < self.maximum
199 return selected
200
201
203 """Select sources using a color limit
204
205 This object can be used as a `lsst.pex.config.Config` for configuring
206 the limit, and then the `apply` method can be used to identify sources
207 in the catalog that match the configured limit.
208
209 We refer to 'primary' and 'secondary' flux measurements; these are the
210 two components of the color, which is:
211
212 instFluxToMag(cat[primary]) - instFluxToMag(cat[secondary])
213 """
214 primary = pexConfig.Field(dtype=str, doc="Name of column with primary flux measurement")
215 secondary = pexConfig.Field(dtype=str, doc="Name of column with secondary flux measurement")
216
217 def apply(self, catalog):
218 """Apply the color limit to a catalog
219
220 Parameters
221 ----------
222 catalog : Various table formats
223 Catalog of sources to which the limit will be applied.
224 Supports `lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
225 or `astropy.table.Table`
226
227 Returns
228 -------
229 selected : `numpy.ndarray`
230 Boolean array indicating for each source whether it is selected
231 (True means selected).
232 """
233 primary = _getFieldFromCatalog(catalog, self.primary)
234 secondary = _getFieldFromCatalog(catalog, self.secondary)
235
236 primary = (primary*u.nJy).to_value(u.ABmag)
237 secondary = (secondary*u.nJy).to_value(u.ABmag)
238 color = primary - secondary
239 return BaseLimit.apply(self, color)
240
241
243 """Select sources using a flux limit
244
245 This object can be used as a `lsst.pex.config.Config` for configuring
246 the limit, and then the `apply` method can be used to identify sources
247 in the catalog that match the configured limit.
248 """
249 fluxField = pexConfig.Field(dtype=str, default="slot_CalibFlux_instFlux",
250 doc="Name of the source flux field to use.")
251
252 def apply(self, catalog):
253 """Apply the flux limits to a catalog
254
255 Parameters
256 ----------
257 catalog : `lsst.afw.table.SourceCatalog`
258 Catalog of sources to which the limit will be applied.
259
260 Returns
261 -------
262 selected : `numpy.ndarray`
263 Boolean array indicating for each source whether it is selected
264 (True means selected).
265 """
266 flagField = self.fluxField + "_flag"
267 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True))
268 flux = _getFieldFromCatalog(catalog, self.fluxField)
269
270 selected &= BaseLimit.apply(self, flux)
271 return selected
272
273
275 """Select sources using a magnitude limit
276
277 Note that this assumes that a zero-point has already been applied and
278 the fluxes are in AB fluxes in Jansky. It is therefore principally
279 intended for reference catalogs rather than catalogs extracted from
280 science images.
281
282 This object can be used as a `lsst.pex.config.Config` for configuring
283 the limit, and then the `apply` method can be used to identify sources
284 in the catalog that match the configured limit.
285 """
286 fluxField = pexConfig.Field(dtype=str, default="flux",
287 doc="Name of the source flux field to use.")
288
289 def apply(self, catalog):
290 """Apply the magnitude limits to a catalog
291
292 Parameters
293 ----------
294 catalog : `lsst.afw.table.SourceCatalog`
295 Catalog of sources to which the limit will be applied.
296
297 Returns
298 -------
299 selected : `numpy.ndarray`
300 Boolean array indicating for each source whether it is selected
301 (True means selected).
302 """
303 flagField = self.fluxField + "_flag"
304 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True))
305 flux = _getFieldFromCatalog(catalog, self.fluxField)
306
307 magnitude = (flux*u.nJy).to_value(u.ABmag)
308 selected &= BaseLimit.apply(self, magnitude)
309 return selected
310
311
313 """Select sources using a flux signal-to-noise limit
314
315 This object can be used as a `lsst.pex.config.Config` for configuring
316 the limit, and then the `apply` method can be used to identify sources
317 in the catalog that match the configured limit.
318 """
319 fluxField = pexConfig.Field(dtype=str, default="flux",
320 doc="Name of the source flux field to use.")
321 errField = pexConfig.Field(dtype=str, default="flux_err",
322 doc="Name of the source flux error field to use.")
323
324 def apply(self, catalog):
325 """Apply the signal-to-noise limits to a catalog
326
327 Parameters
328 ----------
329 catalog : `lsst.afw.table.SourceCatalog`
330 Catalog of sources to which the limit will be applied.
331
332 Returns
333 -------
334 selected : `numpy.ndarray`
335 Boolean array indicating for each source whether it is selected
336 (True means selected).
337 """
338 flagField = self.fluxField + "_flag"
339 selected = np.logical_not(_getFieldFromCatalog(catalog, flagField, isFlag=True))
340 flux = _getFieldFromCatalog(catalog, self.fluxField)
341 err = _getFieldFromCatalog(catalog, self.errField)
342
343 with warnings.catch_warnings():
344 # Suppress NaN warnings; these will be filtered below.
345 warnings.simplefilter("ignore")
346 signalToNoise = flux/err
347
348 selected &= ~np.isnan(signalToNoise)
349 selected &= BaseLimit.apply(self, signalToNoise)
350 return selected
351
352
354 """Select sources using a magnitude error limit
355
356 Because the magnitude error is the inverse of the signal-to-noise
357 ratio, this also works to select sources by signal-to-noise when
358 you only have a magnitude.
359
360 This object can be used as a `lsst.pex.config.Config` for configuring
361 the limit, and then the `apply` method can be used to identify sources
362 in the catalog that match the configured limit.
363 """
364 magErrField = pexConfig.Field(dtype=str, default="mag_err",
365 doc="Name of the source flux error field to use.")
366
367 def apply(self, catalog):
368 """Apply the magnitude error limits to a catalog
369
370 Parameters
371 ----------
372 catalog : `lsst.afw.table.SourceCatalog`
373 Catalog of sources to which the limit will be applied.
374
375 Returns
376 -------
377 selected : `numpy.ndarray`
378 Boolean array indicating for each source whether it is selected
379 (True means selected).
380 """
381 return BaseLimit.apply(self, catalog[self.magErrField])
382
383
384class RequireFlags(pexConfig.Config):
385 """Select sources using flags
386
387 This object can be used as a `lsst.pex.config.Config` for configuring
388 the limit, and then the `apply` method can be used to identify sources
389 in the catalog that match the configured limit.
390 """
391 good = pexConfig.ListField(dtype=str, default=[],
392 doc="List of source flag fields that must be set for a source to be used.")
393 bad = pexConfig.ListField(dtype=str, default=[],
394 doc="List of source flag fields that must NOT be set for a source to be used.")
395
396 def apply(self, catalog):
397 """Apply the flag requirements to a catalog
398
399 Returns whether the source is selected.
400
401 Parameters
402 ----------
403 catalog : `lsst.afw.table.SourceCatalog`
404 Catalog of sources to which the requirements will be applied.
405
406 Returns
407 -------
408 selected : `numpy.ndarray`
409 Boolean array indicating for each source whether it is selected
410 (True means selected).
411 """
412 selected = np.ones(len(catalog), dtype=bool)
413 for flag in self.good:
414 selected &= catalog[flag]
415 for flag in self.bad:
416 selected &= ~catalog[flag]
417 return selected
418
419
421 """Select sources using star/galaxy separation
422
423 This object can be used as a `lsst.pex.config.Config` for configuring
424 the limit, and then the `apply` method can be used to identify sources
425 in the catalog that match the configured limit.
426 """
427 name = pexConfig.Field(dtype=str, default="base_ClassificationSizeExtendedness_value",
428 doc="Name of column for star/galaxy separation")
429
430 def setDefaults(self):
431 """Set default
432
433 Values below the threshold are unresolved.
434 """
435 self.maximum = 0.1
436
437 def apply(self, catalog):
438 """Apply the flag requirements to a catalog
439
440 Returns whether the source is selected.
441
442 Parameters
443 ----------
444 catalog : `lsst.afw.table.SourceCatalog`
445 Catalog of sources to which the requirements will be applied.
446
447 Returns
448 -------
449 selected : `numpy.ndarray`
450 Boolean array indicating for each source whether it is selected
451 (True means selected).
452 """
453 value = catalog[self.name]
454 return BaseLimit.apply(self, value)
455
456
457class RequireIsolated(pexConfig.Config):
458 """Select sources based on whether they are isolated
459
460 This object can be used as a `lsst.pex.config.Config` for configuring
461 the column names to check for "parent" and "nChild" keys.
462
463 Note that this should only be run on a catalog that has had the
464 deblender already run (or else deblend_nChild does not exist).
465 """
466 parentName = pexConfig.Field(dtype=str, default="parent",
467 doc="Name of column for parent")
468 nChildName = pexConfig.Field(dtype=str, default="deblend_nChild",
469 doc="Name of column for nChild")
470
471 def apply(self, catalog):
472 """Apply the isolation requirements to a catalog
473
474 Returns whether the source is selected.
475
476 Parameters
477 ----------
478 catalog : `lsst.afw.table.SourceCatalog`
479 Catalog of sources to which the requirements will be applied.
480
481 Returns
482 -------
483 selected : `numpy.ndarray`
484 Boolean array indicating for each source whether it is selected
485 (True means selected).
486 """
487 selected = ((catalog[self.parentName] == 0)
488 & (catalog[self.nChildName] == 0))
489 return selected
490
491
492class RequireFiniteRaDec(pexConfig.Config):
493 """Select sources that have finite RA and Dec sky coordinate values
494
495 This object can be used as a `lsst.pex.config.Config` for configuring
496 the column names to check for "coord_ra" and "coord_dec" keys.
497
498 This will select against objects for which either the RA or Dec coordinate
499 entries are not numpy.isfinite().
500 """
501 raColName = pexConfig.Field(dtype=str, default="coord_ra", doc="Name of column for RA coordinate")
502 decColName = pexConfig.Field(dtype=str, default="coord_dec", doc="Name of column for Dec coordinate")
503
504 def apply(self, catalog):
505 """Apply the sky coordinate requirements to a catalog
506
507 Returns whether the sources were selected.
508
509 Parameters
510 ----------
511 catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
512 or `astropy.table.Table`
513 Catalog of sources to which the requirements will be applied.
514
515 Returns
516 -------
517 selected : `numpy.ndarray`
518 Boolean array indicating for each source whether it is selected
519 (True means selected).
520 """
521 selected = (np.isfinite(_getFieldFromCatalog(catalog, self.raColName))
522 & np.isfinite(_getFieldFromCatalog(catalog, self.decColName)))
523 return selected
524
525
526class RequirePrimary(pexConfig.Config):
527 """Select sources that have the detect_isPrimary flag set.
528
529 This object can be used as a `lsst.pex.config.Config` for configuring
530 the column names to check for "detect_isPrimary". For single frame
531 catalogs this will be True when the source is not a sky object, and is
532 either an isolated parent that is un-modeled or deblended from a parent
533 with multiple children. For meas_deblender, this is equivalent to
534 deblend_nChild=0. For coadd catalogs there is an additional constraint
535 that the source is located on the interior of a patch and tract.
536 """
537 primaryColName = pexConfig.Field(
538 dtype=str,
539 default="detect_isPrimary",
540 doc="Name of primary flag column",
541 )
542
543 def apply(self, catalog):
544 """Apply the primary requirements to a catalog.
545
546 Returns whether the sources were selected.
547
548 Parameters
549 ----------
550 catalog : lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
551 or `astropy.table.Table`
552 Catalog of sources to which the requirement will be applied.
553
554 Returns
555 -------
556 selected : `numpy.ndarray`
557 Boolean array indicating for each source whether it is selected
558 (True means selected).
559 """
560 selected = (_getFieldFromCatalog(catalog, self.primaryColName)).astype(bool)
561
562 return selected
563
564
565class CullFromMaskedRegion(pexConfig.Config):
566 """Deselect sources that lie in a "bad" mask plane.
567
568 This will select against objects whose image coordinates lie in a region
569 with any of the mask bits in the `badMaskNames` list set. Namely used for
570 a reference catalog for which the flag columns we would get from the
571 measurement plugins do not exist.
572
573 NOTE: In the context of reference objects, it is recommended NOT to include
574 EDGE in the `badMaskNames` list as that will remove all the reference objects
575 outside the detector but within the pixelMargin (thus nulling the pixelMargin
576 padding all together!)
577 """
578 badMaskNames = pexConfig.ListField(
579 dtype=str,
580 default=["NO_DATA", "NOT_DEBLENDED"],
581 doc="List of mask planes for which sources should be removed if a bit is set.",
582 )
583 xColName = pexConfig.Field(
584 dtype=str,
585 default="centroid_x",
586 doc="Name of column for image x coordinate."
587 )
588 yColName = pexConfig.Field(
589 dtype=str,
590 default="centroid_y",
591 doc="Name of column for image y coordinate."
592 )
593
594 def apply(self, catalog, exposure):
595 """Apply the mask plane requirements to a catalog.
596
597 Returns whether the sources were selected.
598
599 Parameters
600 ----------
601 catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
602 or `astropy.table.Table`
603 Catalog of sources to which the requirements will be applied.
604 exposure : `lsst.afw.image.Exposure` or None
605 The exposure whose mask plane is to be respected.
606
607
608 Returns
609 -------
610 selected : `numpy.ndarray`
611 Boolean array indicating for each source whether it is selected
612 (True means selected).
613
614 Raises
615 ------
616 RuntimeError
617 Raised if exposure passed is `None`.
618 """
619 if exposure is None:
620 raise RuntimeError("Must provide an exposure to CullFromMaskedRegion selection.")
621 xRefList = catalog[self.xColName]
622 yRefList = catalog[self.yColName]
623 # Convert x, y coords to integers to map to indices in mask plane.
624 # If reference object nominally lies outside the exposure, consider
625 # it to be at the edge (and thus obeys those mask planes).
626 x0, y0 = exposure.getXY0()
627 xMax, yMax = exposure.getDimensions()
628 xRefList = [int(min(max(0, xRef - x0), xMax - 1)) for xRef in xRefList]
629 yRefList = [int(min(max(0, yRef - y0), yMax - 1)) for yRef in yRefList]
630 badMaskNames = []
631 maskPlaneDict = exposure.getMask().getMaskPlaneDict()
632 for badName in self.badMaskNames:
633 if badName in maskPlaneDict:
634 badMaskNames.append(badName)
635 bitmask = exposure.mask.getPlaneBitMask(badMaskNames)
636 toKeep = ((exposure.mask.array & bitmask) == 0)
637 selected = toKeep[yRefList, xRefList] # x & y flipped for numpy arrays
638
639 return selected
640
641
642class ScienceSourceSelectorConfig(pexConfig.Config):
643 """Configuration for selecting science sources"""
644 doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
645 doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
646 doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
647 doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
648 doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?")
649 doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=False,
650 doc="Apply finite sky coordinate check?")
651 doRequirePrimary = pexConfig.Field(dtype=bool, default=False,
652 doc="Apply source is primary check?")
653 doSkySources = pexConfig.Field(dtype=bool, default=False,
654 doc="Include sky sources, unioned with all other criteria?")
655 fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
656 flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
657 unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
658 signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
659 isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply")
660 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec,
661 doc="Finite sky coordinate criteria to apply")
662 requirePrimary = pexConfig.ConfigField(dtype=RequirePrimary,
663 doc="Primary source criteria to apply")
664 skyFlag = pexConfig.ConfigField(dtype=RequireFlags, doc="Sky source flag to include")
665
666 def setDefaults(self):
667 pexConfig.Config.setDefaults(self)
668 self.flags.bad = ["base_PixelFlags_flag_edge", "base_PixelFlags_flag_nodata",
669 "base_PixelFlags_flag_saturated", "base_PsfFlux_flag"]
670 self.signalToNoise.fluxField = "base_PsfFlux_instFlux"
671 self.signalToNoise.errField = "base_PsfFlux_instFluxErr"
672 self.skyFlag.good = ["sky_source"]
673
674
675@pexConfig.registerConfigurable("science", sourceSelectorRegistry)
677 """Science source selector
678
679 By "science" sources, we mean sources that are on images that we
680 are processing, as opposed to sources from reference catalogs.
681
682 This selects (science) sources by (optionally) applying each of a
683 magnitude limit, flag requirements and star/galaxy separation.
684 """
685 ConfigClass = ScienceSourceSelectorConfig
686
687 def selectSources(self, sourceCat, matches=None, exposure=None):
688 """Return a selection of sources selected by specified criteria.
689
690 Parameters
691 ----------
692 sourceCat : `lsst.afw.table.SourceCatalog`
693 Catalog of sources to select from.
694 This catalog must be contiguous in memory.
695 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
696 Ignored in this SourceSelector.
697 exposure : `lsst.afw.image.Exposure` or None
698 The exposure the catalog was built from; used for debug display.
699
700 Returns
701 -------
702 struct : `lsst.pipe.base.Struct`
703 The struct contains the following data:
704
705 ``selected``
706 Boolean array of sources that were selected, same length as
707 sourceCat.
708 (`numpy.ndarray` of `bool`)
709 """
710 selected = np.ones(len(sourceCat), dtype=bool)
711 if self.config.doFluxLimit:
712 selected &= self.config.fluxLimit.apply(sourceCat)
713 if self.config.doFlags:
714 selected &= self.config.flags.apply(sourceCat)
715 if self.config.doUnresolved:
716 selected &= self.config.unresolved.apply(sourceCat)
717 if self.config.doSignalToNoise:
718 selected &= self.config.signalToNoise.apply(sourceCat)
719 if self.config.doIsolated:
720 selected &= self.config.isolated.apply(sourceCat)
721 if self.config.doRequireFiniteRaDec:
722 selected &= self.config.requireFiniteRaDec.apply(sourceCat)
723 if self.config.doRequirePrimary:
724 selected &= self.config.requirePrimary.apply(sourceCat)
725 if self.config.doSkySources:
726 selected |= self.config.skyFlag.apply(sourceCat)
727
728 self.log.info("Selected %d/%d sources", selected.sum(), len(sourceCat))
729
730 return pipeBase.Struct(selected=selected)
731
732
733class ReferenceSourceSelectorConfig(pexConfig.Config):
734 doMagLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude limit?")
735 doFlags = pexConfig.Field(dtype=bool, default=False, doc="Apply flag limitation?")
736 doUnresolved = pexConfig.Field(dtype=bool, default=False, doc="Apply unresolved limitation?")
737 doSignalToNoise = pexConfig.Field(dtype=bool, default=False, doc="Apply signal-to-noise limit?")
738 doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
739 doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=True,
740 doc="Apply finite sky coordinate check?")
741 doCullFromMaskedRegion = pexConfig.Field(dtype=bool, default=False,
742 doc="Apply image masked region culling?")
743 magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
744 flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
745 unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
746 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec,
747 doc="Finite sky coordinate criteria to apply")
748 signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
749 magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
750 colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
751 doc="Color limits to apply; key is used as a label only")
752 cullFromMaskedRegion = pexConfig.ConfigField(dtype=CullFromMaskedRegion,
753 doc="Image mask plane criteria to apply")
754
755
756@pexConfig.registerConfigurable("references", sourceSelectorRegistry)
758 """Reference source selector
759
760 This selects reference sources by (optionally) applying each of a
761 magnitude limit, flag requirements and color limits.
762 """
763 ConfigClass = ReferenceSourceSelectorConfig
764
765 def selectSources(self, sourceCat, matches=None, exposure=None):
766 """Return a selection of reference sources selected by some criteria.
767
768 Parameters
769 ----------
770 sourceCat : `lsst.afw.table.SourceCatalog`
771 Catalog of sources to select from.
772 This catalog must be contiguous in memory.
773 matches : `list` of `lsst.afw.table.ReferenceMatch` or None
774 Ignored in this SourceSelector.
775 exposure : `lsst.afw.image.Exposure` or None
776 The exposure the catalog was built from; used for debug display.
777
778 Returns
779 -------
780 struct : `lsst.pipe.base.Struct`
781 The struct contains the following data:
782
783 ``selected``
784 Boolean array of sources that were selected, same length as
785 sourceCat.
786 (`numpy.ndarray` of `bool`)
787 """
788 selected = np.ones(len(sourceCat), dtype=bool)
789 if self.config.doMagLimit:
790 selected &= self.config.magLimit.apply(sourceCat)
791 if self.config.doFlags:
792 selected &= self.config.flags.apply(sourceCat)
793 if self.config.doUnresolved:
794 selected &= self.config.unresolved.apply(sourceCat)
795 if self.config.doSignalToNoise:
796 selected &= self.config.signalToNoise.apply(sourceCat)
797 if self.config.doMagError:
798 selected &= self.config.magError.apply(sourceCat)
799 if self.config.doRequireFiniteRaDec:
800 selected &= self.config.requireFiniteRaDec.apply(sourceCat)
801 for limit in self.config.colorLimits.values():
802 selected &= limit.apply(sourceCat)
803 if self.config.doCullFromMaskedRegion:
804 selected &= self.config.cullFromMaskedRegion.apply(sourceCat, exposure)
805 self.log.info("Selected %d/%d references", selected.sum(), len(sourceCat))
806
807 return pipeBase.Struct(selected=selected)
808
809
810@pexConfig.registerConfigurable("null", sourceSelectorRegistry)
812 """Source selector that returns true for all sources.
813
814 Use this when you do not want any sub-selection on your inputs.
815 """
816 ConfigClass = BaseSourceSelectorConfig
817
818 def selectSources(self, sourceCat, **kwargs):
819 # docstring inherited
820 return pipeBase.Struct(selected=np.ones(len(sourceCat), dtype=bool))
821
822
823def _getFieldFromCatalog(catalog, field, isFlag=False):
824 """
825 Get a field from a catalog, for `lsst.afw.table` catalogs or
826 `pandas.DataFrame` or `astropy.table.Table` catalogs.
827
828 Parameters
829 ----------
830 catalog : `lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
831 or `astropy.table.Table`
832 Catalog of sources to extract field array
833 field : `str`
834 Name of field
835 isFlag : `bool`, optional
836 Is this a flag column? If it does not exist, return array
837 of False.
838
839 Returns
840 -------
841 array : `np.ndarray`
842 Array of field values from the catalog.
843 """
844 found = False
845 if isinstance(catalog, (pandas.DataFrame, astropy.table.Table)):
846 if field in catalog.columns:
847 found = True
848 # Sequences must be converted to numpy arrays
849 arr = np.array(catalog[field])
850 else:
851 if field in catalog.schema:
852 found = True
853 arr = catalog[field]
854
855 if isFlag and not found:
856 arr = np.zeros(len(catalog), dtype=bool)
857 elif not found:
858 raise KeyError(f"Could not find field {field} in catalog.")
859
860 return arr
run(self, sourceCat, sourceSelectedField=None, matches=None, exposure=None)
selectSources(self, sourceCat, matches=None, exposure=None)
selectSources(self, sourceCat, matches=None, exposure=None)
selectSources(self, sourceCat, matches=None, exposure=None)
_getFieldFromCatalog(catalog, field, isFlag=False)