Skip to content

Commit ff6d1b1

Browse files
committed
Move adaptive background step to a new subtask.
1 parent bc7a85a commit ff6d1b1

File tree

1 file changed

+6
-234
lines changed

1 file changed

+6
-234
lines changed

python/lsst/pipe/tasks/calibrateImage.py

Lines changed: 6 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import lsst.meas.algorithms.installGaussianPsf
3636
import lsst.meas.algorithms.measureApCorr
3737
import lsst.meas.algorithms.setPrimaryFlags
38-
from lsst.meas.algorithms.adaptive_thresholds import AdaptiveThresholdDetectionTask
38+
from lsst.meas.algorithms.adaptive_thresholds import (
39+
AdaptiveThresholdDetectionTask,
40+
AdaptiveThresholdBackgroundTask,
41+
)
3942
import lsst.meas.base
4043
import lsst.meas.astrom
4144
import lsst.meas.deblender
@@ -317,7 +320,7 @@ class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=Cali
317320

318321
# subtasks used during star measurement
319322
star_background = pexConfig.ConfigurableField(
320-
target=lsst.meas.algorithms.SubtractBackgroundTask,
323+
target=AdaptiveThresholdBackgroundTask,
321324
doc="Task to perform final background subtraction, just before photoCal.",
322325
)
323326
star_detection = pexConfig.ConfigurableField(
@@ -1625,95 +1628,6 @@ def _update_wcs_with_camera_model(self, exposure, cameraModel):
16251628
newWcs = createInitialSkyWcs(exposure.visitInfo, detector)
16261629
exposure.setWcs(newWcs)
16271630

1628-
def _compute_mask_fraction(self, mask, mask_planes, bad_mask_planes):
1629-
"""Evaluate the fraction of masked pixels in a (set of) mask plane(s).
1630-
1631-
Parameters
1632-
----------
1633-
mask : `lsst.afw.image.Mask`
1634-
The mask on which to evaluate the fraction.
1635-
mask_planes : `list`, `str`
1636-
The mask planes for which to evaluate the fraction.
1637-
bad_mask_planes : `list`, `str`
1638-
The mask planes to exclude from the computation.
1639-
1640-
Returns
1641-
-------
1642-
detected_fraction : `float`
1643-
The calculated fraction of masked pixels
1644-
"""
1645-
bad_pixel_mask = afwImage.Mask.getPlaneBitMask(bad_mask_planes)
1646-
n_good_pix = np.sum(mask.array & bad_pixel_mask == 0)
1647-
if n_good_pix == 0:
1648-
detected_fraction = float("nan")
1649-
return detected_fraction
1650-
detected_pixel_mask = afwImage.Mask.getPlaneBitMask(mask_planes)
1651-
n_detected_pix = np.sum((mask.array & detected_pixel_mask != 0)
1652-
& (mask.array & bad_pixel_mask == 0))
1653-
detected_fraction = n_detected_pix/n_good_pix
1654-
return detected_fraction
1655-
1656-
def _compute_per_amp_fraction(self, exposure, detected_fraction, mask_planes, bad_mask_planes):
1657-
"""Evaluate the maximum per-amplifier fraction of masked pixels.
1658-
1659-
Parameters
1660-
----------
1661-
exposure : `lsst.afw.image.ExposureF`
1662-
The exposure on which to compute the per-amp masked fraction.
1663-
detected_fraction : `float`
1664-
The current detected_fraction of the ``mask_planes`` for the
1665-
full detector.
1666-
mask_planes : `list`, `str`
1667-
The mask planes for which to evaluate the fraction.
1668-
bad_mask_planes : `list`, `str`
1669-
The mask planes to exclude from the computation.
1670-
1671-
Returns
1672-
-------
1673-
n_above_max_per_amp : `int`
1674-
The number of amplifiers with masked fractions above a maximum
1675-
value (set by the current full-detector ``detected_fraction``).
1676-
highest_detected_fraction_per_amp : `float`
1677-
The highest value of the per-amplifier fraction of masked pixels.
1678-
no_zero_det_amps : `bool`
1679-
A boolean representing whether any of the amplifiers has zero
1680-
masked pixels.
1681-
"""
1682-
highest_detected_fraction_per_amp = -9.99
1683-
n_above_max_per_amp = 0
1684-
n_no_zero_det_amps = 0
1685-
no_zero_det_amps = True
1686-
amps = exposure.detector.getAmplifiers()
1687-
if amps is not None:
1688-
for ia, amp in enumerate(amps):
1689-
amp_bbox = amp.getBBox()
1690-
exp_bbox = exposure.getBBox()
1691-
if not exp_bbox.contains(amp_bbox):
1692-
self.log.info("Bounding box of amplifier (%s) does not fit in exposure's "
1693-
"bounding box (%s). Skipping...", amp_bbox, exp_bbox)
1694-
continue
1695-
sub_image = exposure.subset(amp.getBBox())
1696-
detected_fraction_amp = self._compute_mask_fraction(sub_image.mask,
1697-
mask_planes,
1698-
bad_mask_planes)
1699-
self.log.debug("Current detected fraction for amplifier %s = %.3f",
1700-
amp.getName(), detected_fraction_amp)
1701-
if detected_fraction_amp < 0.002:
1702-
n_no_zero_det_amps += 1
1703-
if n_no_zero_det_amps > 2:
1704-
no_zero_det_amps = False
1705-
break
1706-
highest_detected_fraction_per_amp = max(detected_fraction_amp,
1707-
highest_detected_fraction_per_amp)
1708-
if highest_detected_fraction_per_amp > min(0.998, max(0.8, 3.0*detected_fraction)):
1709-
n_above_max_per_amp += 1
1710-
if n_above_max_per_amp > 2:
1711-
break
1712-
else:
1713-
self.log.info("No amplifier object for detector %d, so skipping per-amp "
1714-
"detection fraction checks.", exposure.detector.getId())
1715-
return n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps
1716-
17171631
def _remeasure_star_background(self, result):
17181632
"""Remeasure the exposure's background with iterative adaptive
17191633
threshold detection.
@@ -1729,142 +1643,7 @@ def _remeasure_star_background(self, result):
17291643
result : `lsst.pipe.base.Struct`
17301644
The modified result Struct with the new background subtracted.
17311645
"""
1732-
# Restore the previously measured backgroud and remeasure it
1733-
# using an adaptive threshold detection iteration to ensure a
1734-
# "Goldilocks Zone" for the fraction of detected pixels.
1735-
backgroundOrig = result.background.clone()
1736-
median_background = np.median(backgroundOrig.getImage().array)
1737-
self.log.warning("Original median_background = %.2f", median_background)
1738-
result.exposure.image.array += result.background.getImage().array
1739-
result.background = afwMath.BackgroundList()
1740-
1741-
origMask = result.exposure.mask.clone()
1742-
bad_mask_planes = ["BAD", "EDGE", "NO_DATA"]
1743-
detected_mask_planes = ["DETECTED", "DETECTED_NEGATIVE"]
1744-
nPixToDilate = 10
1745-
detected_fraction_orig = self._compute_mask_fraction(result.exposure.mask,
1746-
detected_mask_planes,
1747-
bad_mask_planes)
1748-
minDetFracForFinalBg = 0.02
1749-
maxDetFracForFinalBg = 0.93
1750-
doDilatedOrigDetectionMask = True
1751-
if doDilatedOrigDetectionMask:
1752-
# Dilate the current detected mask planes and don't clear
1753-
# them in the detection step.
1754-
inDilating = True
1755-
while inDilating:
1756-
dilatedMask = origMask.clone()
1757-
for maskName in detected_mask_planes:
1758-
# Compute the grown detection mask plane using SpanSet
1759-
detectedMaskBit = dilatedMask.getPlaneBitMask(maskName)
1760-
detectedMaskSpanSet = SpanSet.fromMask(dilatedMask, detectedMaskBit)
1761-
detectedMaskSpanSet = detectedMaskSpanSet.dilated(nPixToDilate)
1762-
detectedMaskSpanSet = detectedMaskSpanSet.clippedTo(dilatedMask.getBBox())
1763-
# Clear the detected mask plane
1764-
detectedMask = dilatedMask.getMaskPlane(maskName)
1765-
dilatedMask.clearMaskPlane(detectedMask)
1766-
# Set the mask plane to the dilated one
1767-
detectedMaskSpanSet.setMask(dilatedMask, detectedMaskBit)
1768-
1769-
detected_fraction_dilated = self._compute_mask_fraction(dilatedMask,
1770-
detected_mask_planes,
1771-
bad_mask_planes)
1772-
if detected_fraction_dilated < maxDetFracForFinalBg or nPixToDilate == 1:
1773-
inDilating = False
1774-
else:
1775-
nPixToDilate -= 1
1776-
result.exposure.mask = dilatedMask
1777-
self.log.warning("detected_fraction_orig = %.3f detected_fraction_dilated = %.3f",
1778-
detected_fraction_orig, detected_fraction_dilated)
1779-
n_above_max_per_amp = -99
1780-
highest_detected_fraction_per_amp = float("nan")
1781-
doCheckPerAmpDetFraction = True
1782-
if doCheckPerAmpDetFraction: # detected_fraction < maxDetFracForFinalBg:
1783-
n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1784-
self._compute_per_amp_fraction(result.exposure, detected_fraction_dilated,
1785-
detected_mask_planes, bad_mask_planes)
1786-
self.log.warning("Dilated mask: n_above_max_per_amp = %d, "
1787-
"highest_detected_fraction_per_amp = %.3f",
1788-
n_above_max_per_amp, highest_detected_fraction_per_amp)
1789-
1790-
detected_fraction = 1.0
1791-
inBackgroundDet = True
1792-
detected_fraction = 1.0 if inBackgroundDet else detected_fraction_dilated
1793-
maxIter = 40
1794-
nIter = 0
1795-
nFootprintTemp = 1e12
1796-
starBackgroundDetectionConfig = lsst.meas.algorithms.SourceDetectionConfig()
1797-
starBackgroundDetectionConfig.doTempLocalBackground = False
1798-
starBackgroundDetectionConfig.nSigmaToGrow = 70.0
1799-
starBackgroundDetectionConfig.reEstimateBackground = False
1800-
starBackgroundDetectionConfig.includeThresholdMultiplier = 1.0
1801-
starBackgroundDetectionConfig.thresholdValue = max(2.0, 0.2*median_background)
1802-
starBackgroundDetectionConfig.thresholdType = "pixel_stdev" # "stdev"
1803-
1804-
n_above_max_per_amp = -99
1805-
highest_detected_fraction_per_amp = float("nan")
1806-
doCheckPerAmpDetFraction = True
1807-
1808-
while inBackgroundDet:
1809-
currentThresh = starBackgroundDetectionConfig.thresholdValue
1810-
if detected_fraction > maxDetFracForFinalBg:
1811-
starBackgroundDetectionConfig.thresholdValue = 1.07*currentThresh
1812-
if nFootprintTemp < 3 and detected_fraction > 0.9*maxDetFracForFinalBg:
1813-
starBackgroundDetectionConfig.thresholdValue = 1.2*currentThresh
1814-
if n_above_max_per_amp > 1:
1815-
starBackgroundDetectionConfig.thresholdValue = 1.1*currentThresh
1816-
if detected_fraction < minDetFracForFinalBg:
1817-
starBackgroundDetectionConfig.thresholdValue = 0.8*currentThresh
1818-
starBackgroundDetectionTask = lsst.meas.algorithms.SourceDetectionTask(
1819-
config=starBackgroundDetectionConfig)
1820-
table = afwTable.SourceTable.make(self.initial_stars_schema.schema)
1821-
tempDetections = starBackgroundDetectionTask.run(
1822-
table=table, exposure=result.exposure, clearMask=True)
1823-
result.exposure.mask |= dilatedMask
1824-
nFootprintTemp = len(tempDetections.sources)
1825-
detected_fraction = self._compute_mask_fraction(result.exposure.mask, detected_mask_planes,
1826-
bad_mask_planes)
1827-
self.log.info("nIter = %d, thresh = %.2f: Fraction of pixels marked as DETECTED or "
1828-
"DETECTED_NEGATIVE in star_background_detection = %.3f "
1829-
"(max is %.3f; min is %.3f)",
1830-
nIter, starBackgroundDetectionConfig.thresholdValue,
1831-
detected_fraction, maxDetFracForFinalBg, minDetFracForFinalBg)
1832-
1833-
n_amp = len(result.exposure.detector.getAmplifiers())
1834-
if doCheckPerAmpDetFraction: # detected_fraction < maxDetFracForFinalBg:
1835-
n_above_max_per_amp, highest_detected_fraction_per_amp, no_zero_det_amps = \
1836-
self._compute_per_amp_fraction(result.exposure, detected_fraction,
1837-
detected_mask_planes, bad_mask_planes)
1838-
1839-
if not no_zero_det_amps:
1840-
starBackgroundDetectionConfig.thresholdValue = 0.95*currentThresh
1841-
nIter += 1
1842-
if nIter > maxIter:
1843-
inBackgroundDet = False
1844-
1845-
if (detected_fraction < maxDetFracForFinalBg and detected_fraction > minDetFracForFinalBg
1846-
and n_above_max_per_amp < int(0.75*n_amp)
1847-
and no_zero_det_amps):
1848-
if (n_above_max_per_amp < max(1, int(0.15*n_amp))
1849-
or detected_fraction < 0.85*maxDetFracForFinalBg):
1850-
inBackgroundDet = False
1851-
else:
1852-
self.log.warning("Making small tweak....")
1853-
starBackgroundDetectionConfig.thresholdValue = 1.05*currentThresh
1854-
self.log.warning("n_above_max_per_amp = %d (abs max is %d)", n_above_max_per_amp, int(0.75*n_amp))
1855-
1856-
self.log.info("Fraction of pixels marked as DETECTED or DETECTED_NEGATIVE is now %.5f "
1857-
"(highest per amp section = %.5f)",
1858-
detected_fraction, highest_detected_fraction_per_amp)
1859-
1860-
if detected_fraction > maxDetFracForFinalBg:
1861-
result.exposure.mask = dilatedMask
1862-
self.log.warning("Final fraction of pixels marked as DETECTED or DETECTED_NEGATIVE "
1863-
"was too large in star_background_detection = %.3f (max = %.3f). "
1864-
"Reverting to dilated mask from PSF detection...",
1865-
detected_fraction, maxDetFracForFinalBg)
1866-
star_background = self.star_background.run(exposure=result.exposure).background
1867-
result.background.append(star_background[0])
1646+
result.background = self.star_background.run(result.exposure, background=result.background).background
18681647

18691648
# Perform one more round of background subtraction that is just an
18701649
# overall pedestal (order = 0). This is intended to account for
@@ -1906,11 +1685,4 @@ def _remeasure_star_background(self, result):
19061685
pedestalBackgroundLevel = pedestalBackground.getImage().array[0, 0]
19071686
self.log.warning("Subtracted pedestalBackgroundLevel = %.4f", pedestalBackgroundLevel)
19081687

1909-
# Clear detected mask plane before final round of detection
1910-
mask = result.exposure.mask
1911-
for mp in detected_mask_planes:
1912-
if mp not in mask.getMaskPlaneDict():
1913-
mask.addMaskPlane(mp)
1914-
mask &= ~mask.getPlaneBitMask(detected_mask_planes)
1915-
19161688
return result

0 commit comments

Comments
 (0)