Skip to content

Commit

Permalink
Add edgeCenter and edgeCenterAll flags with tests
Browse files Browse the repository at this point in the history
These flags will help distinguish sources that are near an edge vs.
those whose center is fully contained in the edge region.
Cleanup handling of NO_DATA using the EDGE flag key map.
  • Loading branch information
parejkoj committed Feb 15, 2024
1 parent c19132b commit 04a9dd8
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 14 deletions.
21 changes: 11 additions & 10 deletions src/PixelFlags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ PixelFlagsAlgorithm::PixelFlagsAlgorithm(Control const& ctrl, std::string const&
// Set all the flags that correspond to mask planes anywhere in the footprint
_anyKeys["EDGE"] = schema.addField<afw::table::Flag>(
name + "_flag_edge",
"Source is outside usable exposure region (masked EDGE or NO_DATA, or centroid off image)");
"Source is outside usable exposure region (masked EDGE or NO_DATA, or centroid off image).");
_anyKeys["NO_DATA"] = _anyKeys.at("EDGE"); // Also set edge flag for NO_DATA.
_anyKeys["INTRP"] = schema.addField<afw::table::Flag>(name + "_flag_interpolated",
"Interpolated pixel in the Source footprint");
_anyKeys["SAT"] = schema.addField<afw::table::Flag>(name + "_flag_saturated",
Expand All @@ -119,6 +120,9 @@ PixelFlagsAlgorithm::PixelFlagsAlgorithm(Control const& ctrl, std::string const&
_anyKeys["SUSPECT"] = schema.addField<afw::table::Flag>(name + "_flag_suspect",
"Source's footprint includes suspect pixels");
// Flags that correspond to mask bits which are set anywhere in the 3x3 central region of the object.
_centerKeys["EDGE"] = schema.addField<afw::table::Flag>(
name + "_flag_edgeCenter", "EDGE or NO_DATA Pixel in the 3x3 region around the centroid.");
_centerKeys["NO_DATA"] = _centerKeys.at("EDGE"); // Also set edge flag for NO_DATA.
_centerKeys["INTRP"] = schema.addField<afw::table::Flag>(
name + "_flag_interpolatedCenter", "Interpolated pixel in the 3x3 region around the centroid.");
_centerKeys["SAT"] = schema.addField<afw::table::Flag>(
Expand All @@ -131,6 +135,10 @@ PixelFlagsAlgorithm::PixelFlagsAlgorithm(Control const& ctrl, std::string const&
name + "_flag_suspectCenter", "Suspect pixel in the 3x3 region around the centroid.");

// Flags that correspond to mask bits which are set on all of the 3x3 central pixels of the object.
_centerAllKeys["EDGE"] = schema.addField<afw::table::Flag>(
name + "_flag_edgeCenterAll",
"All pixels in the 3x3 region around the centroid are marked EDGE or NO_DATA.");
_centerAllKeys["NO_DATA"] = _centerAllKeys.at("EDGE"); // Also set edge flag for NO_DATA.
_centerAllKeys["INTRP"] = schema.addField<afw::table::Flag>(
name + "_flag_interpolatedCenterAll",
"All pixels in the 3x3 region around the centroid are interpolated.");
Expand Down Expand Up @@ -203,6 +211,8 @@ void PixelFlagsAlgorithm::measure(afw::table::SourceRecord& measRecord,
if (!bbox.contains(center)) {
measRecord.set(_offImageKey, true);
measRecord.set(_anyKeys.at("EDGE"), true);
measRecord.set(_centerKeys.at("EDGE"), true);
measRecord.set(_centerAllKeys.at("EDGE"), true);
}

// Check for bits set in the source's Footprint
Expand All @@ -219,15 +229,6 @@ void PixelFlagsAlgorithm::measure(afw::table::SourceRecord& measRecord,
}
fullSpans->clippedTo(mimage.getBBox())->applyFunctor(func, *(mimage.getMask()));

// Set the EDGE flag if the bitmask has NO_DATA set
try {
if (func.getAnyBits() & MaskedImageF::Mask::getPlaneBitMask("NO_DATA")) {
measRecord.set(_anyKeys.at("EDGE"), true);
}
} catch (pex::exceptions::InvalidParameterError& err) {
throw LSST_EXCEPT(FatalAlgorithmError, err.what());
}

// update the source record for the any keys
updateFlags(_anyKeys, func, measRecord);

Expand Down
35 changes: 31 additions & 4 deletions tests/test_PixelFlags.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def testNoFlags(self):
record = catalog[0]
self.assertFalse(record.get("base_PixelFlags_flag"))
self.assertFalse(record.get("base_PixelFlags_flag_edge"))
self.assertFalse(record.get("base_PixelFlags_flag_edgeCenter"))
self.assertFalse(record.get("base_PixelFlags_flag_edgeCenterAll"))
self.assertFalse(record.get("base_PixelFlags_flag_interpolated"))
self.assertFalse(record.get("base_PixelFlags_flag_interpolatedCenter"))
self.assertFalse(record.get("base_PixelFlags_flag_interpolatedCenterAll"))
Expand Down Expand Up @@ -74,6 +76,9 @@ def testSomeFlags(self):
task.run(catalog, exposure)
record = catalog[0]

self.assertFalse(record.get("base_PixelFlags_flag_edge"))
self.assertFalse(record.get("base_PixelFlags_flag_edgeCenter"))
self.assertFalse(record.get("base_PixelFlags_flag_edgeCenterAll"))
self.assertTrue(record.get("base_PixelFlags_flag_cr"))
self.assertFalse(record.get("base_PixelFlags_flag_crCenter"))
self.assertFalse(record.get("base_PixelFlags_flag_crCenterAll"))
Expand Down Expand Up @@ -103,28 +108,50 @@ def testOffimageNonfinite(self):
np.array([False, True, True, True]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edge"],
np.array([False, True, True, True]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edgeCenter"],
np.array([False, True, True, True]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edgeCenterAll"],
np.array([False, True, True, True]))

def testOffimage(self):
"""Test that sources at the boundary of the image get flag_offimage
and flag_edge set appropriately.
and flag_edge[Center[All]] set appropriately.
"""
# These four will be explicitly set to values at the boundary; we
# cannot add sources off the image in TestDataset.
self.dataset.addSource(100000, lsst.geom.Point2D(20, 20))
self.dataset.addSource(100000, lsst.geom.Point2D(20, 100))
self.dataset.addSource(100000, lsst.geom.Point2D(80, 100))
self.dataset.addSource(100000, lsst.geom.Point2D(40, 100))
self.dataset.addSource(100000, lsst.geom.Point2D(80, 30))
# Half on the edge; no offimage or edgeCenterAll, but edge and edgeCenter.
self.dataset.addSource(100000, lsst.geom.Point2D(115, 30))
# Fully on the edge; no offimage but all edge flags.
self.dataset.addSource(100000, lsst.geom.Point2D(10, 127))
task = self.makeSingleFrameMeasurementTask("base_PixelFlags")
exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=0)
# All of these should get edgeCenter and edgeCenterAll.
catalog[1]["slot_Centroid_x"] = -20.5 # on image
catalog[2]["slot_Centroid_x"] = -20.51 # off image
catalog[3]["slot_Centroid_y"] = 129.4 # on image
catalog[4]["slot_Centroid_y"] = 129.50 # off image
# Mask 4 pixels at the x-edges
edgeBit = exposure.mask.getPlaneBitMask("EDGE")
exposure.mask.array[:4, :] |= edgeBit
exposure.mask.array[-4:, ] |= edgeBit
# Mask 4 pixels at the y-edges
nodataBit = exposure.mask.getPlaneBitMask("NO_DATA")
exposure.mask.array[:, :4] |= nodataBit
exposure.mask.array[:, -4:] |= nodataBit
task.run(catalog, exposure)

np.testing.assert_array_equal(catalog["base_PixelFlags_flag_offimage"],
np.array([False, False, True, False, True]))
np.array([False, False, True, False, True, False, False]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edge"],
np.array([False, False, True, False, True]))
np.array([False, False, True, False, True, True, True]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edgeCenter"],
np.array([False, True, True, True, True, True, True]))
np.testing.assert_array_equal(catalog["base_PixelFlags_flag_edgeCenterAll"],
np.array([False, True, True, True, True, False, True]))


class TestMemory(lsst.utils.tests.MemoryTestCase):
Expand Down

0 comments on commit 04a9dd8

Please sign in to comment.