Coverage for tests/test_undeblended.py: 19%
110 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-14 16:22 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-14 16:22 -0700
1# This file is part of meas_base.
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/>.
22"""Tests for measuring sources on undeblended images.
23"""
25import sys
26import unittest
28import numpy as np
30import lsst.geom
31import lsst.afw.image as afwImage
32import lsst.afw.table as afwTable
33import lsst.afw.geom as afwGeom
34import lsst.afw.detection as afwDetection
35import lsst.afw.math as afwMath
36import lsst.meas.base as measBase
37import lsst.utils.tests
40class UndeblendedTestCase(lsst.utils.tests.TestCase):
41 def testUndeblendedMeasurement(self):
42 """Check undeblended measurement and aperture correction.
43 """
44 width, height = 100, 100 # Dimensions of image
45 x0, y0 = 1234, 5678 # Offset of image
46 radius = 3.0 # Aperture radius
48 # Position of first source; integer values, for convenience
49 xCenter, yCenter = width//2, height//2
50 xOffset, yOffset = 1, 1 # Offset from first source to second source
51 instFlux1, instFlux2 = 1000, 1 # Flux of sources
52 apCorrValue = 3.21 # Aperture correction value to apply
54 image = afwImage.MaskedImageF(lsst.geom.ExtentI(width, height))
55 image.setXY0(x0, y0)
56 image.getVariance().set(1.0)
58 schema = afwTable.SourceTable.makeMinimalSchema()
59 schema.addField("centroid_x", type=np.float64)
60 schema.addField("centroid_y", type=np.float64)
61 schema.addField("centroid_flag", type='Flag')
62 schema.getAliasMap().set("slot_Centroid", "centroid")
64 sfmConfig = measBase.SingleFrameMeasurementConfig()
65 algName = "base_CircularApertureFlux"
67 for subConfig in (sfmConfig.plugins, sfmConfig.undeblended):
68 subConfig.names = [algName]
69 subConfig[algName].radii = [radius]
70 # Disable sinc photometry because we're undersampled
71 subConfig[algName].maxSincRadius = 0
72 slots = sfmConfig.slots
73 slots.centroid = "centroid"
74 slots.shape = None
75 slots.psfShape = None
76 slots.apFlux = None
77 slots.modelFlux = None
78 slots.psfFlux = None
79 slots.gaussianFlux = None
80 slots.calibFlux = None
82 fieldName = lsst.meas.base.CircularApertureFluxAlgorithm.makeFieldPrefix(algName, radius)
83 measBase.addApCorrName(fieldName)
85 apCorrConfig = measBase.ApplyApCorrConfig()
86 apCorrConfig.proxies = {"undeblended_" + fieldName: fieldName}
88 sfm = measBase.SingleFrameMeasurementTask(config=sfmConfig, schema=schema)
89 apCorr = measBase.ApplyApCorrTask(config=apCorrConfig, schema=schema)
91 cat = afwTable.SourceCatalog(schema)
92 parent = cat.addNew()
93 parent.set("centroid_x", x0 + xCenter)
94 parent.set("centroid_y", y0 + yCenter)
95 spanSetParent = afwGeom.SpanSet.fromShape(int(radius))
96 spanSetParent = spanSetParent.shiftedBy(x0 + xCenter, y0 + yCenter)
97 parent.setFootprint(afwDetection.Footprint(spanSetParent))
99 # First child is bright, dominating the blend
100 child1 = cat.addNew()
101 child1.set("centroid_x", parent.get("centroid_x"))
102 child1.set("centroid_y", parent.get("centroid_y"))
103 child1.setParent(parent.getId())
104 image[xCenter, yCenter, afwImage.LOCAL] = (instFlux1, 0, 0)
105 spanSetChild1 = afwGeom.SpanSet.fromShape(1)
106 spanSetChild1 = spanSetChild1.shiftedBy(x0 + xCenter, y0 + yCenter)
107 foot1 = afwDetection.Footprint(spanSetChild1)
108 child1.setFootprint(afwDetection.HeavyFootprintF(foot1, image))
110 # Second child is fainter, but we want to be able to measure it!
111 child2 = cat.addNew()
112 child2.set("centroid_x", parent.get("centroid_x") + xOffset)
113 child2.set("centroid_y", parent.get("centroid_y") + yOffset)
114 child2.setParent(parent.getId())
115 image[xCenter + xOffset, yCenter + yOffset, afwImage.LOCAL] = (instFlux2, 0, 0)
116 spanSetChild2 = afwGeom.SpanSet.fromShape(1)
117 tmpPoint = (x0 + xCenter + xOffset, y0 + yCenter + yOffset)
118 spanSetChild2 = spanSetChild2.shiftedBy(*tmpPoint)
119 foot2 = afwDetection.Footprint(spanSetChild2)
120 child2.setFootprint(afwDetection.HeavyFootprintF(foot2, image))
122 spans = foot1.spans.union(foot2.spans)
123 bbox = lsst.geom.Box2I()
124 bbox.include(foot1.getBBox())
125 bbox.include(foot2.getBBox())
126 parent.setFootprint(afwDetection.Footprint(spans, bbox))
128 exposure = afwImage.makeExposure(image)
130 sfm.run(cat, exposure)
132 def checkSource(source, baseName, expectedFlux):
133 """Check that we get the expected results.
134 """
135 self.assertEqual(source.get(baseName + "_instFlux"), expectedFlux)
136 self.assertGreater(source.get(baseName + "_instFluxErr"), 0)
137 self.assertFalse(source.get(baseName + "_flag"))
139 # Deblended
140 checkSource(child1, fieldName, instFlux1)
141 checkSource(child2, fieldName, instFlux2)
143 # Undeblended
144 checkSource(child1, "undeblended_" + fieldName, instFlux1 + instFlux2)
145 checkSource(child2, "undeblended_" + fieldName, instFlux1 + instFlux2)
147 # Apply aperture correction
148 apCorrMap = afwImage.ApCorrMap()
149 apCorrMap[fieldName + "_instFlux"] = afwMath.ChebyshevBoundedField(
150 image.getBBox(),
151 apCorrValue*np.ones((1, 1), dtype=np.float64)
152 )
153 apCorrMap[fieldName + "_instFluxErr"] = afwMath.ChebyshevBoundedField(
154 image.getBBox(),
155 apCorrValue*np.zeros((1, 1), dtype=np.float64)
156 )
158 apCorr.run(cat, apCorrMap)
160 # Deblended
161 checkSource(child1, fieldName, instFlux1*apCorrValue)
162 checkSource(child2, fieldName, instFlux2*apCorrValue)
164 # Undeblended
165 checkSource(child1, "undeblended_" + fieldName, (instFlux1 + instFlux2)*apCorrValue)
166 checkSource(child2, "undeblended_" + fieldName, (instFlux1 + instFlux2)*apCorrValue)
168 self.assertIn(fieldName + "_apCorr", schema)
169 self.assertIn(fieldName + "_apCorrErr", schema)
170 self.assertIn("undeblended_" + fieldName + "_apCorr", schema)
171 self.assertIn("undeblended_" + fieldName + "_apCorrErr", schema)
174class TestMemory(lsst.utils.tests.MemoryTestCase):
175 pass
178def setup_module(module):
179 lsst.utils.tests.init()
182if __name__ == "__main__": 182 ↛ 183line 182 didn't jump to line 183, because the condition on line 182 was never true
183 setup_module(sys.modules[__name__])
184 unittest.main()