Coverage for tests/test_ticket2707.py: 13%
83 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-22 02:38 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-22 02:38 -0700
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
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#
23import unittest
25import lsst.geom
26import lsst.afw.table as afwTable
27import lsst.utils.tests
30class MatchXyTest(unittest.TestCase):
31 """Test that matching sources by centroid works as expected,
32 even when some of the centroids contain NaN.
33 """
35 def setUp(self):
36 nan = float('nan')
37 self.schema = afwTable.SourceTable.makeMinimalSchema()
38 centroidKey = afwTable.Point2DKey.addFields(
39 self.schema, "cen", "center", "pixels")
40 self.table = afwTable.SourceTable.make(self.schema)
41 self.table.defineCentroid("cen")
42 idKey = self.table.getIdKey()
43 self.cat1 = afwTable.SourceCatalog(self.table)
44 self.cat2 = afwTable.SourceCatalog(self.table)
45 self.nobj = 10
46 self.nUniqueMatch = 0
47 self.matchRadius = 0.1 # Matching radius to use in tests (pixels)
48 for i in range(self.nobj):
49 j = self.nobj - i - 1
50 r1, r2 = self.cat1.addNew(), self.cat2.addNew()
51 r1.set(idKey, i)
52 r2.set(idKey, self.nobj + j)
53 if i % 3 != 0:
54 # These will provide the exact matches, though the two records we're setting right now won't
55 # match each other (because cat2 counts down in reverse).
56 r1.set(centroidKey, lsst.geom.Point2D(i, i))
57 r2.set(centroidKey, lsst.geom.Point2D(j, j))
58 self.nUniqueMatch += 1
59 elif i == 3:
60 # Deliberately offset position in cat2 by 2 pixels and a bit so that it will match a
61 # different source. This gives us an extra match when we're not just getting the closest.
62 # The "2 pixels" makes it line up with a different source.
63 # The "a bit" is half a match radius, so that it's still within the matching radius, but it
64 # doesn't match another source exactly. If it matches another source exactly, then it's not
65 # clear which one will be taken as the match (in fact, it
66 # appears to depend on the compiler).
67 offset = 2 + 0.5*self.matchRadius
68 r1.set(centroidKey, lsst.geom.Point2D(i, i))
69 r2.set(centroidKey, lsst.geom.Point2D(j + offset, j + offset))
70 else:
71 # Test that we can handle NANs
72 r1.set(centroidKey, lsst.geom.Point2D(nan, nan))
73 r2.set(centroidKey, lsst.geom.Point2D(nan, nan))
75 def tearDown(self):
76 del self.cat2
77 del self.cat1
78 del self.table
79 del self.schema
81 def testMatchXy(self):
82 matches = afwTable.matchXy(self.cat1, self.cat2, self.matchRadius)
83 self.assertEqual(len(matches), self.nUniqueMatch)
85 for m in matches:
86 self.assertEqual(m.first.getId() + self.nobj, m.second.getId())
87 self.assertEqual(m.distance, 0.0)
89 def testMatchXyMatchControl(self):
90 """Test using MatchControl to return all matches
92 Also tests closest==False at the same time
93 """
94 for closest in (True, False):
95 for includeMismatches in (True, False):
96 mc = afwTable.MatchControl()
97 mc.findOnlyClosest = closest
98 mc.includeMismatches = includeMismatches
99 matches = afwTable.matchXy(
100 self.cat1, self.cat2, self.matchRadius, mc)
102 if False:
103 for m in matches:
104 print(closest, m.first.getId(),
105 m.second.getId(), m.distance)
107 if includeMismatches:
108 catMatches = afwTable.SourceCatalog(self.table)
109 catMismatches = afwTable.SourceCatalog(self.table)
110 for m in matches:
111 if m[1] is not None:
112 if not any(x == m[0] for x in catMatches):
113 catMatches.append(m[0])
114 else:
115 catMismatches.append(m[0])
116 matches = afwTable.matchXy(
117 catMatches, self.cat2, self.matchRadius, mc)
118 mc.includeMismatches = False
119 noMatches = afwTable.matchXy(
120 catMismatches, self.cat2, self.matchRadius, mc)
121 self.assertEqual(len(noMatches), 0)
123 # If we're not getting only the closest match, then we get an extra match due to the
124 # source we offset by 2 pixels and a bit. Everything else
125 # should match exactly.
126 self.assertEqual(
127 len(matches),
128 self.nUniqueMatch if closest else self.nUniqueMatch + 1)
129 self.assertEqual(
130 sum(1 for m in matches if m.distance == 0.0),
131 self.nUniqueMatch)
132 for m in matches:
133 if closest:
134 self.assertEqual(m.first.getId() + self.nobj, m.second.getId())
135 else:
136 self.assertLessEqual(m.distance, self.matchRadius)
138 def testSelfMatchXy(self):
139 """Test doing a self-matches"""
140 for symmetric in (True, False):
141 mc = afwTable.MatchControl()
142 mc.symmetricMatch = symmetric
143 matches = afwTable.matchXy(self.cat2, self.matchRadius, mc)
145 if False:
146 for m in matches:
147 print(m.first.getId(), m.second.getId(), m.distance)
149 # There is only one source that matches another source when we do a self-match: the one
150 # offset by 2 pixels and a bit.
151 # If we're getting symmetric matches, that multiplies the expected number by 2 because it
152 # produces s1,s2 and s2,s1.
153 self.assertEqual(len(matches), 2 if symmetric else 1)
156class MemoryTester(lsst.utils.tests.MemoryTestCase):
157 pass
160def setup_module(module):
161 lsst.utils.tests.init()
164if __name__ == "__main__": 164 ↛ 165line 164 didn't jump to line 165, because the condition on line 164 was never true
165 lsst.utils.tests.init()
166 unittest.main()