Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-39141: Add doRequirePrimary to source selectors. #331

Merged
merged 4 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions python/lsst/meas/algorithms/astrometrySourceSelector.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import numpy as np

import lsst.pex.config as pexConfig
from lsst.pex.exceptions import NotFoundError
from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry
from lsst.pipe.base import Struct
from functools import reduce
Expand Down Expand Up @@ -112,6 +113,10 @@ def _getSchemaKeys(self, schema):
self.centroidXErrKey = schema["slot_Centroid_xErr"].asKey()
self.centroidYErrKey = schema["slot_Centroid_yErr"].asKey()
self.centroidFlagKey = schema["slot_Centroid_flag"].asKey()
try:
self.primaryKey = schema["detect_isPrimary"].asKey()
except NotFoundError:
self.primaryKey = None

self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey()
self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey()
Expand Down Expand Up @@ -171,6 +176,14 @@ def _isUsable(self, sourceCat):
& self._goodSN(sourceCat) \
& ~sourceCat.get(self.fluxFlagKey)

def _isPrimary(self, sourceCat):
"""Return True if this is a primary source.
"""
if self.primaryKey:
return sourceCat.get(self.primaryKey)
else:
return np.ones(len(sourceCat), dtype=bool)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this, I'm wondering if the same should be added to ObjectSizeStarSelectorTask too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To the best of my knowledge, that selector is never run on catalogs that have had sky sources added; it's only run at the earliest time when we're looking for stars to measure the PSF.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was just thinking that, in principle, someone could, so it wouldn’t hurt…your call!

def _isGood(self, sourceCat):
"""Return True for each source that is usable for matching and likely has a
good centroid.
Expand All @@ -182,6 +195,7 @@ def _isGood(self, sourceCat):
"""

return self._isUsable(sourceCat) \
& self._isPrimary(sourceCat) \
& ~sourceCat.get(self.saturatedKey) \
& ~sourceCat.get(self.interpolatedCenterKey) \
& ~sourceCat.get(self.edgeKey)
Expand Down
63 changes: 51 additions & 12 deletions python/lsst/meas/algorithms/sourceSelector.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

__all__ = ["BaseSourceSelectorConfig", "BaseSourceSelectorTask", "sourceSelectorRegistry",
"ColorLimit", "MagnitudeLimit", "SignalToNoiseLimit", "MagnitudeErrorLimit",
"RequireFlags", "RequireUnresolved", "RequireFiniteRaDec",
"RequireFlags", "RequireUnresolved", "RequireFiniteRaDec", "RequirePrimary",
"ScienceSourceSelectorConfig", "ScienceSourceSelectorTask",
"ReferenceSourceSelectorConfig", "ReferenceSourceSelectorTask",
]
Expand Down Expand Up @@ -119,14 +119,8 @@ def run(self, sourceCat, sourceSelectedField=None, matches=None, exposure=None):
matches=matches)

if sourceSelectedField is not None:
if isinstance(sourceCat, (pandas.DataFrame, astropy.table.Table)):
sourceCat[sourceSelectedField] = result.selected
else:
source_selected_key = \
sourceCat.getSchema()[sourceSelectedField].asKey()
# TODO: Remove for loop when DM-6981 is completed.
for source, flag in zip(sourceCat, result.selected):
source.set(source_selected_key, bool(flag))
sourceCat[sourceSelectedField] = result.selected

return pipeBase.Struct(sourceCat=sourceCat[result.selected],
selected=result.selected)

Expand Down Expand Up @@ -494,18 +488,18 @@ class RequireFiniteRaDec(pexConfig.Config):
"""Select sources that have finite RA and Dec sky coordinate values

This object can be used as a `lsst.pex.config.Config` for configuring
the column names to check for "coore_ra" and "coord_dec" keys.
the column names to check for "coord_ra" and "coord_dec" keys.

This will select against objects for which either the RA or Dec coordinate
entries are not numpy.isfinite().
"""
raColName = pexConfig.Field(dtype=str, default="coord_ra", doc="Name of column for RA coordinate")
decColName = pexConfig.Field(dtype=str, default="coord_dec", doc="Name of column for Dec coordiante")
decColName = pexConfig.Field(dtype=str, default="coord_dec", doc="Name of column for Dec coordinate")

def apply(self, catalog):
"""Apply the sky coordinate requirements to a catalog

Returns whether the source is selected.
Returns whether the sources were selected.

Parameters
----------
Expand All @@ -524,6 +518,45 @@ def apply(self, catalog):
return selected


class RequirePrimary(pexConfig.Config):
"""Select sources that have the detect_isPrimary flag set.

This object can be used as a `lsst.pex.config.Config` for configuring
the column names to check for "detect_isPrimary". For single frame
catalogs this will be True when the source is not a sky object, and is
either an isolated parent that is un-modeled or deblended from a parent
with multiple children. For meas_deblender, this is equivalent to
deblend_nChild=0. For coadd catalogs there is an additional constraint
that the source is located on the interior of a patch and tract.
"""
primaryColName = pexConfig.Field(
dtype=str,
default="detect_isPrimary",
doc="Name of primary flag column",
)

def apply(self, catalog):
"""Apply the primary requirements to a catalog.

Returns whether the sources were selected.

Parameters
----------
catalog : lsst.afw.table.SourceCatalog` or `pandas.DataFrame`
or `astropy.table.Table`
Catalog of sources to which the requirement will be applied.

Returns
-------
selected : `numpy.ndarray`
Boolean array indicating for each source whether it is selected
(True means selected).
"""
selected = (_getFieldFromCatalog(catalog, self.primaryColName)).astype(bool)

return selected


class ScienceSourceSelectorConfig(pexConfig.Config):
"""Configuration for selecting science sources"""
doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
Expand All @@ -533,13 +566,17 @@ class ScienceSourceSelectorConfig(pexConfig.Config):
doIsolated = pexConfig.Field(dtype=bool, default=False, doc="Apply isolated limitation?")
doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=False,
doc="Apply finite sky coordinate check?")
doRequirePrimary = pexConfig.Field(dtype=bool, default=False,
doc="Apply source is primary check?")
fluxLimit = pexConfig.ConfigField(dtype=FluxLimit, doc="Flux limit to apply")
flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
signalToNoise = pexConfig.ConfigField(dtype=SignalToNoiseLimit, doc="Signal-to-noise limit to apply")
isolated = pexConfig.ConfigField(dtype=RequireIsolated, doc="Isolated criteria to apply")
requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec,
doc="Finite sky coordinate criteria to apply")
requirePrimary = pexConfig.ConfigField(dtype=RequirePrimary,
doc="Primary source criteria to apply")

def setDefaults(self):
pexConfig.Config.setDefaults(self)
Expand Down Expand Up @@ -596,6 +633,8 @@ def selectSources(self, sourceCat, matches=None, exposure=None):
selected &= self.config.isolated.apply(sourceCat)
if self.config.doRequireFiniteRaDec:
selected &= self.config.requireFiniteRaDec.apply(sourceCat)
if self.config.doRequirePrimary:
selected &= self.config.requirePrimary.apply(sourceCat)

self.log.info("Selected %d/%d sources", selected.sum(), len(sourceCat))

Expand Down
2 changes: 2 additions & 0 deletions tests/test_astrometrySourceSelector.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def add_good_source(src, num=0):
src['slot_Centroid_x_y_Cov'][-1] = 3. + num
src['slot_ApFlux_instFlux'][-1] = 100. + num
src['slot_ApFlux_instFluxErr'][-1] = 1.
src['detect_isPrimary'] = True


class TestAstrometrySourceSelector(lsst.utils.tests.TestCase):
Expand All @@ -71,6 +72,7 @@ def setUp(self):
schema = lsst.meas.base.tests.TestDataset.makeMinimalSchema()
schema.addField("slot_ApFlux_instFlux", type=np.float64)
schema.addField("slot_ApFlux_instFluxErr", type=np.float64)
schema.addField("detect_isPrimary", type="Flag")
for flag in badFlags + goodFlags:
schema.addField(flag, type="Flag")

Expand Down
11 changes: 11 additions & 0 deletions tests/test_sourceSelector.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def setUp(self):
schema.addField("badFlag", "Flag", "Flagged if bad")
schema.addField("starGalaxy", float, "0=star, 1=galaxy")
schema.addField("nChild", np.int32, "Number of children")
schema.addField("detect_isPrimary", "Flag", "Is primary detection?")
self.catalog = lsst.afw.table.SourceCatalog(schema)
self.catalog.reserve(10)
self.config = self.Task.ConfigClass()
Expand Down Expand Up @@ -204,6 +205,16 @@ def testRequireFiniteRaDec(self):
self.config.requireFiniteRaDec.decColName = "coord_dec"
self.check((np.isfinite(ra) & np.isfinite(dec)).tolist())

def testRequirePrimary(self):
num = 5
for _ in range(num):
self.catalog.addNew()
primary = np.array([True, True, False, True, False], dtype=bool)
self.catalog["detect_isPrimary"] = primary
self.config.doRequirePrimary = True
self.config.requirePrimary.primaryColName = "detect_isPrimary"
self.check(primary.tolist())


class ReferenceSourceSelectorTaskTest(SourceSelectorTester, lsst.utils.tests.TestCase):
Task = lsst.meas.algorithms.ReferenceSourceSelectorTask
Expand Down