Coverage for tests/test_measure.py: 24%
162 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-24 10:23 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-24 10:23 +0000
1# This file is part of meas_algorithms.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import os
23import unittest
24import math
25import warnings
27import lsst.geom
28import lsst.afw.detection as afwDetection
29import lsst.afw.image as afwImage
30import lsst.afw.math as afwMath
31import lsst.afw.table as afwTable
32from lsst.log import Log
33import lsst.meas.base as measBase
34import lsst.meas.algorithms as algorithms
35import lsst.meas.algorithms.testUtils as testUtils
36import lsst.pex.config as pexConfig
37import lsst.utils.tests
39# Change the level to Log.DEBUG or Log.TRACE to see debug messages
40Log.getLogger("lsst.measurement").setLevel(Log.INFO)
42try:
43 type(display)
44except NameError:
45 display = False
46else:
47 import lsst.afw.display as afwDisplay
48 afwDisplay.setDefaultMaskTransparency(75)
50# Determine if we have afwdata
51try:
52 afwdataDir = lsst.utils.getPackageDir('afwdata')
53except Exception:
54 afwdataDir = None
57def toString(*args):
58 """toString written in python"""
59 if len(args) == 1:
60 args = args[0]
62 y, x0, x1 = args
63 return "%d: %d..%d" % (y, x0, x1)
66class FindAndMeasureTestCase(lsst.utils.tests.TestCase):
67 """A test case detecting and measuring objects."""
69 def setUp(self):
70 self.mi = afwImage.MaskedImageF(os.path.join(afwdataDir,
71 "CFHT", "D4", "cal-53535-i-797722_1.fits"))
73 self.FWHM = 5
74 self.psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*math.sqrt(2*math.log(2))))
76 if False: # use full image, trimmed to data section
77 self.XY0 = lsst.geom.PointI(32, 2)
78 self.mi = self.mi.Factory(self.mi, lsst.geom.BoxI(self.XY0, lsst.geom.PointI(2079, 4609)),
79 afwImage.LOCAL)
80 self.mi.setXY0(lsst.geom.PointI(0, 0))
81 else: # use sub-image
82 self.XY0 = lsst.geom.PointI(824, 140)
83 self.mi = self.mi.Factory(self.mi, lsst.geom.BoxI(self.XY0, lsst.geom.ExtentI(256, 256)),
84 afwImage.LOCAL)
86 self.mi.getMask().addMaskPlane("DETECTED")
87 self.exposure = afwImage.makeExposure(self.mi)
89 def tearDown(self):
90 del self.mi
91 del self.psf
92 del self.exposure
94 @unittest.skipUnless(afwdataDir, "afwdata not available")
95 def testDetection(self):
96 """Test object detection"""
97 #
98 # Fix defects
99 #
100 # Mask known bad pixels
101 #
102 badPixels = testUtils.makeDefectList()
103 algorithms.interpolateOverDefects(self.mi, self.psf, badPixels)
105 #
106 # Subtract background
107 #
108 bgGridSize = 64 # was 256 ... but that gives only one region and the spline breaks
109 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.NATURAL_SPLINE)
110 bctrl.setNxSample(int(self.mi.getWidth()/bgGridSize) + 1)
111 bctrl.setNySample(int(self.mi.getHeight()/bgGridSize) + 1)
112 backobj = afwMath.makeBackground(self.mi.getImage(), bctrl)
114 self.mi.getImage()[:] -= backobj.getImageF()
115 #
116 # Remove CRs
117 #
118 crConfig = algorithms.FindCosmicRaysConfig()
119 algorithms.findCosmicRays(self.mi, self.psf, 0, pexConfig.makePropertySet(crConfig))
120 #
121 # We do a pretty good job of interpolating, so don't propagagate the convolved CR/INTRP bits
122 # (we'll keep them for the original CR/INTRP pixels)
123 #
124 savedMask = self.mi.getMask().Factory(self.mi.getMask(), True)
125 saveBits = savedMask.getPlaneBitMask("CR") | \
126 savedMask.getPlaneBitMask("BAD") | \
127 savedMask.getPlaneBitMask("INTRP") # Bits to not convolve
128 savedMask &= saveBits
130 msk = self.mi.getMask()
131 msk &= ~saveBits # Clear the saved bits
132 del msk
133 #
134 # Smooth image
135 #
136 psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*math.sqrt(2*math.log(2))))
138 cnvImage = self.mi.Factory(self.mi.getBBox())
139 kernel = psf.getKernel()
140 afwMath.convolve(cnvImage, self.mi, kernel, afwMath.ConvolutionControl())
142 msk = cnvImage.getMask()
143 msk |= savedMask # restore the saved bits
144 del msk
146 threshold = afwDetection.Threshold(3, afwDetection.Threshold.STDEV)
147 #
148 # Only search the part of the frame that was PSF-smoothed
149 #
150 llc = lsst.geom.PointI(psf.getKernel().getWidth()//2, psf.getKernel().getHeight()//2)
151 urc = lsst.geom.PointI(cnvImage.getWidth() - llc[0] - 1, cnvImage.getHeight() - llc[1] - 1)
152 middle = cnvImage.Factory(cnvImage, lsst.geom.BoxI(llc, urc), afwImage.LOCAL)
153 ds = afwDetection.FootprintSet(middle, threshold, "DETECTED")
154 del middle
155 #
156 # Reinstate the saved (e.g. BAD) (and also the DETECTED | EDGE) bits in the unsmoothed image
157 #
158 savedMask[:] = cnvImage.getMask()
159 msk = self.mi.getMask()
160 msk |= savedMask
161 del msk
162 del savedMask
164 if display:
165 disp = afwDisplay.Display(frame=2)
166 disp.mtv(self.mi, title=self._testMethodName + ": image")
167 afwDisplay.Display(frame=3).mtv(cnvImage, title=self._testMethodName + ": cnvImage")
169 #
170 # Time to actually measure
171 #
172 schema = afwTable.SourceTable.makeMinimalSchema()
173 sfm_config = measBase.SingleFrameMeasurementConfig()
174 sfm_config.plugins = ["base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux",
175 "base_SdssShape", "base_GaussianFlux",
176 "base_PixelFlags"]
177 sfm_config.slots.centroid = "base_SdssCentroid"
178 sfm_config.slots.shape = "base_SdssShape"
179 sfm_config.slots.psfFlux = "base_PsfFlux"
180 sfm_config.slots.gaussianFlux = None
181 sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0"
182 sfm_config.slots.modelFlux = "base_GaussianFlux"
183 sfm_config.slots.calibFlux = None
184 sfm_config.plugins["base_SdssShape"].maxShift = 10.0
185 sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0]
186 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config)
187 measCat = afwTable.SourceCatalog(schema)
188 # detect the sources and run with the measurement task
189 ds.makeSources(measCat)
190 self.exposure.setPsf(self.psf)
191 task.run(measCat, self.exposure)
193 self.assertGreater(len(measCat), 0)
194 for source in measCat:
195 if source.get("base_PixelFlags_flag_edge"):
196 continue
198 if display:
199 disp.dot("+", source.getX(), source.getY())
202class GaussianPsfTestCase(lsst.utils.tests.TestCase):
203 """A test case detecting and measuring Gaussian PSFs."""
205 def setUp(self):
206 FWHM = 5
207 psf = algorithms.DoubleGaussianPsf(15, 15, FWHM/(2*math.sqrt(2*math.log(2))))
208 mi = afwImage.MaskedImageF(lsst.geom.ExtentI(100, 100))
210 self.xc, self.yc, self.instFlux = 45, 55, 1000.0
211 mi.image[self.xc, self.yc, afwImage.LOCAL] = self.instFlux
213 cnvImage = mi.Factory(mi.getDimensions())
214 afwMath.convolve(cnvImage, mi, psf.getKernel(), afwMath.ConvolutionControl())
216 self.exp = afwImage.makeExposure(cnvImage)
217 self.exp.setPsf(psf)
219 if display and False:
220 afwDisplay.Display(frame=0).mtv(self.exp, title=self._testMethodName + ": image")
222 def tearDown(self):
223 del self.exp
225 def testPsfFlux(self):
226 """Test that fluxes are measured correctly."""
227 #
228 # Total flux in image
229 #
230 flux = afwMath.makeStatistics(self.exp.getMaskedImage(), afwMath.SUM).getValue()
231 self.assertAlmostEqual(flux/self.instFlux, 1.0)
233 #
234 # Various algorithms
235 #
236 rad = 10.0
238 schema = afwTable.SourceTable.makeMinimalSchema()
239 schema.addField("centroid_x", type=float)
240 schema.addField("centroid_y", type=float)
241 schema.addField("centroid_flag", type='Flag')
242 with warnings.catch_warnings():
243 warnings.filterwarnings("ignore", message="ignoreSlotPluginChecks", category=FutureWarning)
244 sfm_config = measBase.SingleFrameMeasurementConfig(ignoreSlotPluginChecks=True)
245 sfm_config.doReplaceWithNoise = False
246 sfm_config.plugins = ["base_CircularApertureFlux", "base_PsfFlux"]
247 sfm_config.slots.centroid = "centroid"
248 sfm_config.slots.shape = None
249 sfm_config.slots.psfFlux = None
250 sfm_config.slots.gaussianFlux = None
251 sfm_config.slots.apFlux = None
252 sfm_config.slots.modelFlux = None
253 sfm_config.slots.calibFlux = None
254 sfm_config.plugins["base_SdssShape"].maxShift = 10.0
255 sfm_config.plugins["base_CircularApertureFlux"].radii = [rad]
256 task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config)
257 measCat = afwTable.SourceCatalog(schema)
258 source = measCat.addNew()
259 source.set("centroid_x", self.xc)
260 source.set("centroid_y", self.yc)
261 task.run(measCat, self.exp)
262 for algName in ["base_CircularApertureFlux_10_0", "base_PsfFlux"]:
263 instFlux = source.get(algName + "_instFlux")
264 flag = source.get(algName + "_flag")
265 self.assertEqual(flag, False)
266 self.assertAlmostEqual(instFlux/self.instFlux, 1.0, 4, "Measuring with %s: %g v. %g" %
267 (algName, instFlux, self.instFlux))
270class TestMemory(lsst.utils.tests.MemoryTestCase):
271 pass
274def setup_module(module):
275 lsst.utils.tests.init()
278if __name__ == "__main__": 278 ↛ 279line 278 didn't jump to line 279, because the condition on line 278 was never true
279 lsst.utils.tests.init()
280 unittest.main()