Coverage for tests/test_convolved.py: 17%
148 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:10 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:10 +0000
1#
2# LSST Data Management System
3# Copyright 2017 LSST/AURA.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22from __future__ import absolute_import, division, print_function
24import math
25import unittest
26import lsst.utils.tests
27import lsst.daf.base as dafBase
28import lsst.afw.detection as afwDetection
29import lsst.afw.geom as afwGeom
30import lsst.afw.geom.ellipses as afwEll
31import lsst.afw.table as afwTable
32import lsst.afw.image as afwImage
33import lsst.geom as geom
34import lsst.meas.base as measBase
35import lsst.meas.extensions.convolved # Load flux.convolved algorithm
37import lsst.afw.display as afwDisplay
39try:
40 type(display)
41except NameError:
42 display = False
43 frame = 1
45SIGMA_TO_FWHM = 2.0*math.sqrt(2.0*math.log(2.0))
48def makeExposure(bbox, scale, psfFwhm, flux):
49 """Make a fake exposure
51 Parameters
52 ----------
54 bbox : `lsst.geom.Box2I`
55 Bounding box for image.
56 scale : `lsst.geom.Angle`
57 Pixel scale.
58 psfFwhm : `float`
59 PSF FWHM (arcseconds)
60 flux : `float`
61 PSF flux (ADU)
63 Returns
64 -------
65 exposure : `lsst.afw.image.ExposureF`
66 Fake exposure.
67 center : `lsst.geom.Point2D`
68 Position of fake source.
69 """
70 image = afwImage.ImageF(bbox)
71 image.set(0)
72 center = geom.Box2D(bbox).getCenter()
73 psfSigma = psfFwhm/SIGMA_TO_FWHM/scale.asArcseconds()
74 psfWidth = 2*int(4.0*psfSigma) + 1
75 psf = afwDetection.GaussianPsf(psfWidth, psfWidth, psfSigma)
76 psfImage = psf.computeImage(center).convertF()
77 psfFlux = psfImage.getArray().sum()
78 psfImage *= flux/psfFlux
80 subImage = afwImage.ImageF(image, psfImage.getBBox(afwImage.PARENT), afwImage.PARENT)
81 subImage += psfImage
83 exp = afwImage.makeExposure(afwImage.makeMaskedImage(image))
84 exp.setPsf(psf)
85 exp.getMaskedImage().getVariance().set(1.0)
86 exp.getMaskedImage().getMask().set(0)
88 cdMatrix = afwGeom.makeCdMatrix(scale=scale)
89 exp.setWcs(afwGeom.makeSkyWcs(crpix=center,
90 crval=geom.SpherePoint(0.0, 0.0, geom.degrees),
91 cdMatrix=cdMatrix))
92 return exp, center
95class ConvolvedFluxTestCase(lsst.utils.tests.TestCase):
96 """A test case for measuring convolved fluxes"""
98 def checkSchema(self, schema, names):
99 """Check that the schema includes flux, fluxErr and flag elements for each measurement
101 Also checks for the presence of the corresponding undeblended measurements.
103 Parameters
104 ----------
105 schema : `lsst.afw.table.Schema`
106 Schema to check.
107 names : `list` of `str`
108 List of measurement algorithm names
109 """
110 for name in names:
111 self.assertIn(name + "_instFlux", schema)
112 self.assertIn(name + "_instFluxErr", schema)
113 self.assertIn(name + "_flag", schema)
114 self.assertIn("undeblended_" + name + "_instFlux", schema)
115 self.assertIn("undeblended_" + name + "_instFluxErr", schema)
116 self.assertIn("undeblended_" + name + "_flag", schema)
118 def check(self, psfFwhm=0.5, flux=1000.0, forced=False):
119 """Check that we can measure convolved fluxes
121 We create an image with a Gaussian PSF and a single point source.
122 Measurements of the point source should match expectations for a
123 Gaussian of the known sigma and known aperture radius.
125 Parameters
126 ----------
127 psfFwhm : `float`
128 PSF FWHM (arcsec)
129 flux : `float`
130 Source flux (ADU)
131 forced : `bool`
132 Forced measurement?
133 """
134 bbox = geom.Box2I(geom.Point2I(12345, 6789), geom.Extent2I(200, 300))
136 # We'll only achieve the target accuracy if the pixel scale is rather smaller than Gaussians
137 # involved. Otherwise it's important to consider the convolution with the pixel grid, and we're
138 # not doing that here.
139 scale = 0.1*geom.arcseconds
141 TaskClass = measBase.ForcedMeasurementTask if forced else measBase.SingleFrameMeasurementTask
143 exposure, center = makeExposure(bbox, scale, psfFwhm, flux)
144 measConfig = TaskClass.ConfigClass()
145 algName = "ext_convolved_ConvolvedFlux"
146 measConfig.plugins.names.add(algName)
147 if not forced:
148 measConfig.plugins.names.add("ext_photometryKron_KronFlux")
149 else:
150 measConfig.copyColumns = {"id": "objectId", "parent": "parentObjectId"}
151 values = [ii/scale.asArcseconds() for ii in (0.6, 0.8, 1.0, 1.2)]
152 algConfig = measConfig.plugins[algName]
153 algConfig.seeing = values
154 algConfig.aperture.radii = values
155 algConfig.aperture.maxSincRadius = max(values) + 1 # Get as exact as we can
157 if forced:
158 offset = geom.Extent2D(-12.3, 45.6)
159 kronRadiusName = "my_Kron_Radius"
160 kronRadius = 12.345
161 refWcs = exposure.getWcs().copyAtShiftedPixelOrigin(offset)
162 measConfig.plugins[algName].kronRadiusName = kronRadiusName
163 refSchema = afwTable.SourceTable.makeMinimalSchema()
164 centroidKey = afwTable.Point2DKey.addFields(refSchema, "my_centroid", doc="centroid",
165 unit="pixel")
166 shapeKey = afwTable.QuadrupoleKey.addFields(refSchema, "my_shape", "shape")
167 refSchema.getAliasMap().set("slot_Centroid", "my_centroid")
168 refSchema.getAliasMap().set("slot_Shape", "my_shape")
169 refSchema.addField("my_centroid_flag", type="Flag", doc="centroid flag")
170 refSchema.addField("my_shape_flag", type="Flag", doc="shape flag")
171 refSchema.addField(kronRadiusName, type=float, doc="my custom kron radius", units="pixel")
172 refCat = afwTable.SourceCatalog(refSchema)
173 refSource = refCat.addNew()
174 refSource.set(centroidKey, center + offset)
175 refSource.set(shapeKey, afwEll.Quadrupole(afwEll.Axes(kronRadius, kronRadius, 0)))
176 refSource.set(kronRadiusName, kronRadius)
177 refSource.setCoord(refWcs.pixelToSky(refSource.get(centroidKey)))
178 taskInitArgs = (refSchema,)
179 taskRunArgs = (refCat, refWcs)
180 else:
181 taskInitArgs = (afwTable.SourceTable.makeMinimalSchema(),)
182 taskRunArgs = ()
184 # Activate undeblended measurement with the same configuration
185 measConfig.undeblended.names.add(algName)
186 measConfig.undeblended[algName] = measConfig.plugins[algName]
188 algMetadata = dafBase.PropertyList()
189 task = TaskClass(*taskInitArgs, config=measConfig, algMetadata=algMetadata)
191 schema = task.schema
192 measCat = afwTable.SourceCatalog(schema)
193 source = measCat.addNew()
194 source.getTable().setMetadata(algMetadata)
195 ss = afwDetection.FootprintSet(exposure.getMaskedImage(), afwDetection.Threshold(0.1))
196 fp = ss.getFootprints()[0]
197 source.setFootprint(fp)
199 task.run(measCat, exposure, *taskRunArgs)
201 disp = afwDisplay.Display(frame)
202 disp.mtv(exposure)
203 disp.dot("x", *center, origin=afwImage.PARENT, title="psfFwhm=%f" % (psfFwhm,))
205 self.checkSchema(schema, algConfig.getAllApertureResultNames())
206 self.checkSchema(schema, algConfig.getAllKronResultNames())
207 self.checkSchema(schema, algConfig.getAllResultNames())
209 if not forced:
210 kronRadius = source.get("ext_photometryKron_KronFlux_radius")
212 self.assertFalse(source.get(algName + "_flag")) # algorithm succeeded
213 originalSeeing = psfFwhm/scale.asArcseconds()
214 for ii, targetSeeing in enumerate(algConfig.seeing):
215 deconvolve = targetSeeing < originalSeeing
216 seeing = originalSeeing if deconvolve else targetSeeing
218 def expected(radius, sigma=seeing/SIGMA_TO_FWHM):
219 """Return expected flux for 2D Gaussian with nominated sigma"""
220 return flux*(1.0 - math.exp(-0.5*(radius/sigma)**2))
222 for prefix in ("", "undeblended_"):
223 self.assertEqual(source.get(prefix + algName + "_%d_deconv" % ii), deconvolve)
225 # Kron succeeded and match expectation
226 if not forced:
227 kronName = algConfig.getKronResultName(targetSeeing)
228 kronApRadius = algConfig.kronRadiusForFlux*kronRadius
229 self.assertFloatsAlmostEqual(source.get(prefix + kronName + "_instFlux"),
230 expected(kronApRadius), rtol=1.0e-3)
231 self.assertGreater(source.get(prefix + kronName + "_instFluxErr"), 0)
232 self.assertFalse(source.get(prefix + kronName + "_flag"))
234 # Aperture measurements succeeded and match expectation
235 for jj, radius in enumerate(measConfig.algorithms[algName].aperture.radii):
236 name = algConfig.getApertureResultName(targetSeeing, radius)
237 self.assertFloatsAlmostEqual(source.get(prefix + name + "_instFlux"), expected(radius),
238 rtol=1.0e-3)
239 self.assertFalse(source.get(prefix + name + "_flag"))
240 self.assertGreater(source.get(prefix + name + "_instFluxErr"), 0)
242 def testConvolvedFlux(self):
243 for forced in (True, False):
244 for psfFwhm in (0.5, # Smaller than all target seeings
245 0.9, # Larger than half the target seeings
246 1.3, # Larger than all the target seeings
247 ):
248 self.check(psfFwhm=psfFwhm, forced=forced)
251class TestMemory(lsst.utils.tests.MemoryTestCase):
252 pass
255def setup_module(module, backend="virtualDevice"):
256 lsst.utils.tests.init()
257 try:
258 afwDisplay.setDefaultBackend(backend)
259 except Exception:
260 print("Unable to configure display backend: %s" % backend)
263if __name__ == "__main__": 263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true
264 import sys
266 from argparse import ArgumentParser
267 parser = ArgumentParser()
268 parser.add_argument('--backend', type=str, default="virtualDevice",
269 help="The backend to use, e.g. 'ds9'. Be sure to 'setup display_<backend>'")
270 args = parser.parse_args()
272 setup_module(sys.modules[__name__], backend=args.backend)
273 unittest.main()