Coverage for tests / test_findGlintTrails.py: 15%
107 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:57 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:57 +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 unittest
24import numpy as np
26from lsst.meas.algorithms import findGlintTrails
27import lsst.geom
28import lsst.meas.base.tests
29import lsst.utils.tests
31# Set this and have display_ds9 setup to see the fit on the image.
32display = False
33if display: 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true
34 import lsst.afw.display
35 display = lsst.afw.display.Display()
38class TestFindGlintTrails(lsst.utils.tests.TestCase):
39 """Generate two glint trails and test that they are both found, while
40 outlier points are not included.
41 """
43 def _make_image_3_trails(self):
44 """Make an image with 3 trails on it, one of them negative.
45 """
46 rng = np.random.default_rng(10)
47 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -4), lsst.geom.Point2I(1005, 1084))
48 dataset = lsst.meas.base.tests.TestDataset(bbox)
49 flux = 10000
51 x0 = 200
52 y0 = 300
53 scale = 50
54 # one outlier source near the first glint trail.
55 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(x0 - 31, y0 - 27))
56 # two glint trails
57 for i in range(8):
58 dataset.addSource(instFlux=flux,
59 centroid=lsst.geom.Point2D(i*scale + x0 + rng.random(),
60 i*scale + y0 + rng.random()))
62 # A negative trail (e.g. came from the template) that shouldn't be
63 # included in any of the fits.
64 x0 = 170
65 y0 = 690
66 step = 70
67 for i in range(6):
68 dataset.addSource(instFlux=-flux,
69 centroid=lsst.geom.Point2D(i*step + x0 + rng.random(),
70 -i*step + y0 + rng.random()),
71 negative=True)
73 # a source that shouldn't appear in the found trails
74 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(500, 200))
76 # Perpendicular trail that has a point lying on the same line as the
77 # first trail.
78 x0 = 380
79 y0 = 760
80 step = 70
81 for i in range(5):
82 dataset.addSource(instFlux=flux,
83 centroid=lsst.geom.Point2D(i*step + x0 + rng.random(),
84 -i*step + y0 + rng.random()))
86 schema = dataset.makeMinimalSchema()
87 schema.addField("ip_diffim_DipoleFit_classification", type="Flag")
88 exposure, catalog = dataset.realize(10.0, schema=schema)
89 # So that the catalog ids don't look like simple indices when debugging.
90 catalog["id"] += 100
91 return exposure, catalog
93 def test_simple(self):
94 """Test the basic operation of the glint finder on an image with two
95 glint trails.
96 """
97 exposure, catalog = self._make_image_3_trails()
99 config = findGlintTrails.FindGlintTrailsTask.ConfigClass()
100 # Limit the search radius so that we can test the line-extension
101 # part of the fitter.
102 config.radius = 300
103 # Use a tighter threshold for this simulated data, which has better
104 # controlled centroids than real data.
105 config.threshold = 1.5
106 task = findGlintTrails.FindGlintTrailsTask(config=config)
107 result = task.run(catalog)
109 if display:
110 display.frame = 1
111 display.image(exposure, title="something")
112 for i, trail in enumerate(result.trails):
113 display.centroids(trail, size=5, ctype="cyan", symbol=i)
115 self.assertEqual(len(result.trails), 2)
116 # Note that if you add sources to the simulated catalog, the ids in
117 # the fitted trails may change.
118 self.assertSetEqual(set(result.trails[0]["id"]),
119 {102, 103, 104, 105, 106, 107, 108, 109, 119})
120 self.assertSetEqual(set(result.trails[1]["id"]),
121 {117, 118, 119, 120, 121})
122 self.assertSetEqual(result.trailed_ids, {102, 103, 104, 105, 106, 107, 108,
123 109, 117, 118, 119, 120, 121})
124 # Expected lengths from the step size of the simulated trails.
125 self.assertFloatsAlmostEqual(result.parameters[0].length,
126 np.sqrt((7*50)**2 + (7*50)**2),
127 rtol=1e-3)
128 self.assertFloatsAlmostEqual(result.parameters[1].length,
129 np.sqrt((4*70)**2 + (4*70)**2),
130 rtol=1e-3)
131 self.assertFloatsAlmostEqual(np.degrees(result.parameters[0].angle), 45.0, rtol=2e-3)
132 self.assertFloatsAlmostEqual(np.degrees(result.parameters[1].angle), -45.0, rtol=2e-3)
134 def test_empty_image(self):
135 """Test that no trails are found on an empty image."""
136 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -4), lsst.geom.Point2I(1005, 1084))
137 dataset = lsst.meas.base.tests.TestDataset(bbox)
138 schema = dataset.makeMinimalSchema()
139 schema.addField("ip_diffim_DipoleFit_classification", type="Flag")
140 exposure, catalog = dataset.realize(10.0, schema=schema)
142 task = findGlintTrails.FindGlintTrailsTask()
143 result = task.run(catalog)
145 self.assertEqual(len(result.trails), 0)
146 self.assertEqual(result.trailed_ids, set())
148 def test_many_points_no_trail(self):
149 """Test that no trails are found on an image with many points, but no
150 sets of three that could lie on the same line.
151 """
152 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -4), lsst.geom.Point2I(1005, 1084))
153 dataset = lsst.meas.base.tests.TestDataset(bbox)
154 flux = 10000
155 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(500, 500))
156 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(480, 610))
157 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(520, 680))
158 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(570, 500))
159 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(640, 630))
160 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(600, 700))
161 schema = dataset.makeMinimalSchema()
162 schema.addField("ip_diffim_DipoleFit_classification", type="Flag")
163 exposure, catalog = dataset.realize(10.0, schema=schema)
165 task = findGlintTrails.FindGlintTrailsTask()
166 result = task.run(catalog)
168 self.assertEqual(len(result.trails), 0)
169 self.assertEqual(result.trailed_ids, set())
171 def test_rejected_trail_min_points(self):
172 """Test that no trails are found on an image with an initially
173 reasonable trail that is rejected during fitting for having too many
174 outliers.
175 """
176 bbox = lsst.geom.Box2I(lsst.geom.Point2I(-5, -4), lsst.geom.Point2I(1005, 1084))
177 dataset = lsst.meas.base.tests.TestDataset(bbox)
178 flux = 10000
179 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(100, 100))
180 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(210, 200))
181 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(300, 310))
182 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(400, 400))
183 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(500, 500))
184 dataset.addSource(instFlux=flux, centroid=lsst.geom.Point2D(600, 600))
185 schema = dataset.makeMinimalSchema()
186 schema.addField("ip_diffim_DipoleFit_classification", type="Flag")
187 exposure, catalog = dataset.realize(10.0, schema=schema)
189 task = findGlintTrails.FindGlintTrailsTask()
190 result = task.run(catalog)
192 self.assertEqual(len(result.trails), 0)
193 self.assertEqual(result.trailed_ids, set())
196def setup_module(module):
197 lsst.utils.tests.init()
200if __name__ == "__main__": 200 ↛ 201line 200 didn't jump to line 201 because the condition on line 200 was never true
201 lsst.utils.tests.init()
202 unittest.main()