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