3535import lsst .meas .algorithms .installGaussianPsf
3636import lsst .meas .algorithms .measureApCorr
3737import 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+ )
3942import lsst .meas .base
4043import lsst .meas .astrom
4144import 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