@@ -562,6 +562,85 @@ def apply(self, catalog):
562
562
return selected
563
563
564
564
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
+
565
644
class ScienceSourceSelectorConfig (pexConfig .Config ):
566
645
"""Configuration for selecting science sources"""
567
646
doFluxLimit = pexConfig .Field (dtype = bool , default = False , doc = "Apply flux limit?" )
@@ -660,6 +739,8 @@ class ReferenceSourceSelectorConfig(pexConfig.Config):
660
739
doMagError = pexConfig .Field (dtype = bool , default = False , doc = "Apply magnitude error limit?" )
661
740
doRequireFiniteRaDec = pexConfig .Field (dtype = bool , default = True ,
662
741
doc = "Apply finite sky coordinate check?" )
742
+ doCullFromMaskedRegion = pexConfig .Field (dtype = bool , default = False ,
743
+ doc = "Apply image masked region culling?" )
663
744
magLimit = pexConfig .ConfigField (dtype = MagnitudeLimit , doc = "Magnitude limit to apply" )
664
745
flags = pexConfig .ConfigField (dtype = RequireFlags , doc = "Flags to require" )
665
746
unresolved = pexConfig .ConfigField (dtype = RequireUnresolved , doc = "Star/galaxy separation to apply" )
@@ -669,6 +750,8 @@ class ReferenceSourceSelectorConfig(pexConfig.Config):
669
750
magError = pexConfig .ConfigField (dtype = MagnitudeErrorLimit , doc = "Magnitude error limit to apply" )
670
751
colorLimits = pexConfig .ConfigDictField (keytype = str , itemtype = ColorLimit , default = {},
671
752
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" )
672
755
673
756
674
757
@pexConfig .registerConfigurable ("references" , sourceSelectorRegistry )
@@ -718,7 +801,8 @@ def selectSources(self, sourceCat, matches=None, exposure=None):
718
801
selected &= self .config .requireFiniteRaDec .apply (sourceCat )
719
802
for limit in self .config .colorLimits .values ():
720
803
selected &= limit .apply (sourceCat )
721
-
804
+ if self .config .doCullFromMaskedRegion :
805
+ selected &= self .config .cullFromMaskedRegion .apply (sourceCat , exposure )
722
806
self .log .info ("Selected %d/%d references" , selected .sum (), len (sourceCat ))
723
807
724
808
return pipeBase .Struct (selected = selected )
0 commit comments