Skip to content

Commit a594b0f

Browse files
committed
Add CullFromMaskedRegions and use in ref selector
1 parent 2179890 commit a594b0f

File tree

1 file changed

+85
-1
lines changed

1 file changed

+85
-1
lines changed

python/lsst/meas/algorithms/sourceSelector.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,85 @@ def apply(self, catalog):
562562
return selected
563563

564564

565+
class 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+
bbox = exposure.getBBox()
625+
# If reference object nominally lies outside the detector, consider
626+
# it to be at the edge (and thus obeys those mask planes).
627+
xMax = bbox.getMaxX()
628+
yMax = bbox.getMaxY()
629+
x0, y0 = exposure.getXY0()
630+
xRefList = [int(min(max(x0, xRef - x0), xMax)) for xRef in xRefList]
631+
yRefList = [int(min(max(y0, yRef - y0), yMax)) for yRef in yRefList]
632+
badMaskNames = []
633+
maskPlaneDict = exposure.getMask().getMaskPlaneDict()
634+
for badName in self.badMaskNames:
635+
if badName in maskPlaneDict:
636+
badMaskNames.append(badName)
637+
bitmask = exposure.mask.getPlaneBitMask(badMaskNames)
638+
toKeep = ((exposure.mask.array & bitmask) == 0)
639+
selected = toKeep[yRefList, xRefList] # x & y flipped for numpy arrays
640+
641+
return selected
642+
643+
565644
class ScienceSourceSelectorConfig(pexConfig.Config):
566645
"""Configuration for selecting science sources"""
567646
doFluxLimit = pexConfig.Field(dtype=bool, default=False, doc="Apply flux limit?")
@@ -660,6 +739,8 @@ class ReferenceSourceSelectorConfig(pexConfig.Config):
660739
doMagError = pexConfig.Field(dtype=bool, default=False, doc="Apply magnitude error limit?")
661740
doRequireFiniteRaDec = pexConfig.Field(dtype=bool, default=True,
662741
doc="Apply finite sky coordinate check?")
742+
doCullFromMaskedRegion = pexConfig.Field(dtype=bool, default=False,
743+
doc="Apply image masked region culling?")
663744
magLimit = pexConfig.ConfigField(dtype=MagnitudeLimit, doc="Magnitude limit to apply")
664745
flags = pexConfig.ConfigField(dtype=RequireFlags, doc="Flags to require")
665746
unresolved = pexConfig.ConfigField(dtype=RequireUnresolved, doc="Star/galaxy separation to apply")
@@ -669,6 +750,8 @@ class ReferenceSourceSelectorConfig(pexConfig.Config):
669750
magError = pexConfig.ConfigField(dtype=MagnitudeErrorLimit, doc="Magnitude error limit to apply")
670751
colorLimits = pexConfig.ConfigDictField(keytype=str, itemtype=ColorLimit, default={},
671752
doc="Color limits to apply; key is used as a label only")
753+
cullFromMaskedRegion = pexConfig.ConfigField(dtype=CullFromMaskedRegion,
754+
doc="Image mask plane criteria to apply")
672755

673756

674757
@pexConfig.registerConfigurable("references", sourceSelectorRegistry)
@@ -718,7 +801,8 @@ def selectSources(self, sourceCat, matches=None, exposure=None):
718801
selected &= self.config.requireFiniteRaDec.apply(sourceCat)
719802
for limit in self.config.colorLimits.values():
720803
selected &= limit.apply(sourceCat)
721-
804+
if self.config.doCullFromMaskedRegion:
805+
selected &= self.config.cullFromMaskedRegion.apply(sourceCat, exposure)
722806
self.log.info("Selected %d/%d references", selected.sum(), len(sourceCat))
723807

724808
return pipeBase.Struct(selected=selected)

0 commit comments

Comments
 (0)