Coverage for tests/test_trailedSources.py: 30%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# This file is part of meas_extensions_trailedSources.
3#
4# Developed for the LSST Data Management System.
5# This product includes software developed by the LSST Project
6# (http://www.lsst.org).
7# See the COPYRIGHT file at the top-level directory of this distribution
8# for details of code ownership.
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22#
24import numpy as np
25import unittest
26import lsst.utils.tests
27import lsst.meas.extensions.trailedSources
28from lsst.meas.base.tests import AlgorithmTestCase
29from lsst.utils.tests import classParameters
31import lsst.log
33# Trailed-source length, angle, and centroid.
34rng = np.random.default_rng(432)
35nTrails = 50
36Ls = rng.uniform(2, 20, nTrails)
37thetas = rng.uniform(0, 2*np.pi, nTrails)
38xcs = rng.uniform(0, 100, nTrails)
39ycs = rng.uniform(0, 100, nTrails)
42class TrailedSource:
43 """Holds a set of true trail parameters.
44 """
46 def __init__(self, instFlux, length, angle, xc, yc):
47 self.instFlux = instFlux
48 self.length = length
49 self.angle = angle
50 self.center = lsst.geom.Point2D(xc, yc)
51 self.x0 = xc - length/2 * np.cos(angle)
52 self.y0 = yc - length/2 * np.sin(angle)
53 self.x1 = xc + length/2 * np.cos(angle)
54 self.y1 = yc + length/2 * np.sin(angle)
57# "Extend" meas.base.tests.TestDataset
58class TrailedTestDataset(lsst.meas.base.tests.TestDataset):
59 """A dataset for testing trailed source measurements.
60 Given a `TrailedSource`, construct a record of the true values and an
61 Exposure.
62 """
64 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
65 super().__init__(bbox, threshold, exposure, **kwds)
67 def addTrailedSource(self, trail):
68 """Add a trailed source to the simulation.
69 'Re-implemented' version of
70 `lsst.meas.base.tests.TestDataset.addSource`. Numerically integrates a
71 Gaussian PSF over a line to obtain am image of a trailed source.
72 """
74 record = self.catalog.addNew()
75 record.set(self.keys["centroid"], trail.center)
76 rng = np.random.default_rng(32)
77 covariance = rng.normal(0, 0.1, 4).reshape(2, 2)
78 covariance[0, 1] = covariance[1, 0]
79 record.set(self.keys["centroid_sigma"], covariance.astype(np.float32))
80 record.set(self.keys["shape"], self.psfShape)
81 record.set(self.keys["isStar"], False)
83 # Sum the psf at each
84 numIter = int(10*trail.length)
85 xp = np.linspace(trail.x0, trail.x1, num=numIter)
86 yp = np.linspace(trail.y0, trail.y1, num=numIter)
87 for (x, y) in zip(xp, yp):
88 pt = lsst.geom.Point2D(x, y)
89 im = self.drawGaussian(self.exposure.getBBox(), trail.instFlux,
90 lsst.afw.geom.Ellipse(self.psfShape, pt))
91 self.exposure.getMaskedImage().getImage().getArray()[:, :] += im.getArray()
93 totFlux = self.exposure.image.array.sum()
94 self.exposure.image.array /= totFlux
95 self.exposure.image.array *= trail.instFlux
97 record.set(self.keys["instFlux"], trail.instFlux)
98 self._installFootprint(record, self.exposure.getImage())
100 return record, self.exposure.getImage()
103# Following from meas_base/test_NaiveCentroid.py
104# Taken from NaiveCentroidTestCase
105@classParameters(length=Ls, theta=thetas, xc=xcs, yc=ycs)
106class TrailedSourcesTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase):
108 def setUp(self):
109 self.center = lsst.geom.Point2D(50.1, 49.8)
110 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-20, -30),
111 lsst.geom.Extent2I(140, 160))
112 self.dataset = TrailedTestDataset(self.bbox)
114 self.trail = TrailedSource(100000.0, self.length, self.theta, self.xc, self.yc)
115 self.dataset.addTrailedSource(self.trail)
117 def tearDown(self):
118 del self.center
119 del self.bbox
120 del self.trail
121 del self.dataset
123 def makeTrailedSourceMeasurementTask(self, plugin=None, dependencies=(),
124 config=None, schema=None, algMetadata=None):
125 """Set up a measurement task for a trailed source plugin.
126 """
128 config = self.makeSingleFrameMeasurementConfig(plugin=plugin,
129 dependencies=dependencies)
131 # Make sure the shape slot is base_SdssShape
132 config.slots.shape = "base_SdssShape"
133 return self.makeSingleFrameMeasurementTask(plugin=plugin,
134 dependencies=dependencies,
135 config=config, schema=schema,
136 algMetadata=algMetadata)
138 def testNaivePlugin(self):
139 """Test the NaivePlugin measurements.
140 Given a `TrailedTestDataset`, run the NaivePlugin measurement and
141 compare the measured parameters to the true values.
142 """
144 # Set up and run Naive measurement.
145 task = self.makeTrailedSourceMeasurementTask(
146 plugin="ext_trailedSources_Naive",
147 dependencies=("base_SdssCentroid", "base_SdssShape")
148 )
149 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=0)
150 task.run(catalog, exposure)
151 record = catalog[0]
153 # Check the RA and Dec measurements
154 wcs = exposure.getWcs()
155 spt = wcs.pixelToSky(self.center)
156 ra_true = spt.getRa().asDegrees()
157 dec_true = spt.getDec().asDegrees()
158 ra_meas = record.get("ext_trailedSources_Naive_ra")
159 dec_meas = record.get("ext_trailedSources_Naive_dec")
160 self.assertFloatsAlmostEqual(ra_true, ra_meas, atol=None, rtol=0.01)
161 self.assertFloatsAlmostEqual(dec_true, dec_meas, atol=None, rtol=0.01)
163 # Check that root finder converged
164 converged = record.get("ext_trailedSources_Naive_flag_noConverge")
165 self.assertFalse(converged)
167 # Compare true with measured length, angle, and flux.
168 # Accuracy is dependent on the second-moments measurements, so the
169 # rtol values are simply rough upper bounds.
170 length = record.get("ext_trailedSources_Naive_length")
171 theta = record.get("ext_trailedSources_Naive_angle")
172 flux = record.get("ext_trailedSources_Naive_flux")
173 self.assertFloatsAlmostEqual(length, self.trail.length, atol=None, rtol=0.1)
174 self.assertFloatsAlmostEqual(theta % np.pi, self.trail.angle % np.pi,
175 atol=np.arctan(1/length), rtol=None)
176 self.assertFloatsAlmostEqual(flux, self.trail.instFlux, atol=None, rtol=0.1)
178 # Check test setup
179 self.assertNotEqual(length, self.trail.length)
180 self.assertNotEqual(theta, self.trail.angle)
182 # Make sure measurement flag is False
183 self.assertFalse(record.get("ext_trailedSources_Naive_flag"))
185 def testVeresPlugin(self):
186 """Test the VeresPlugin measurements.
187 Given a `TrailedTestDataset`, run the VeresPlugin measurement and
188 compare the measured parameters to the true values.
189 """
191 # Set up and run Veres measurement.
192 task = self.makeTrailedSourceMeasurementTask(
193 plugin="ext_trailedSources_Veres",
194 dependencies=(
195 "base_SdssCentroid",
196 "base_SdssShape",
197 "ext_trailedSources_Naive")
198 )
199 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=0)
200 task.run(catalog, exposure)
201 record = catalog[0]
203 # Make sure optmizer converged
204 converged = record.get("ext_trailedSources_Veres_flag_nonConvergence")
205 self.assertFalse(converged)
207 # Compare measured trail length, angle, and flux to true values
208 # These measurements should perform at least as well as NaivePlugin
209 length = record.get("ext_trailedSources_Veres_length")
210 theta = record.get("ext_trailedSources_Veres_angle")
211 flux = record.get("ext_trailedSources_Veres_flux")
212 self.assertFloatsAlmostEqual(length, self.trail.length, atol=None, rtol=0.1)
213 self.assertFloatsAlmostEqual(theta % np.pi, self.trail.angle % np.pi,
214 atol=np.arctan(1/length), rtol=None)
215 self.assertFloatsAlmostEqual(flux, self.trail.instFlux, atol=None, rtol=0.1)
217 # Make sure test setup is working as expected
218 self.assertNotEqual(length, self.trail.length)
219 self.assertNotEqual(theta, self.trail.angle)
221 # Test that reduced chi-squared is reasonable
222 rChiSq = record.get("ext_trailedSources_Veres_rChiSq")
223 self.assertGreater(rChiSq, 0.8)
224 self.assertLess(rChiSq, 1.3)
226 # Make sure measurement flag is False
227 self.assertFalse(record.get("ext_trailedSources_Veres_flag"))
230class TestMemory(lsst.utils.tests.MemoryTestCase):
231 pass
234def setup_module(module):
235 lsst.utils.tests.init()
238if __name__ == "__main__": 238 ↛ 239line 238 didn't jump to line 239, because the condition on line 238 was never true
239 lsst.utils.tests.init()
240 unittest.main()