Coverage for tests/test_matchSourceInjected.py: 98%

82 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-09 09:42 +0000

1# 

2# This file is part of ap_pipe. 

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# 

23 

24import numpy as np 

25import unittest 

26 

27from astropy.table import Table 

28from lsst.afw.table import SourceCatalog, SourceTable 

29from lsst.afw.image import ExposureF 

30from lsst.afw.geom import makeSkyWcs 

31from lsst.geom import SpherePoint, degrees, Point2D, Extent2I 

32import lsst.utils.tests 

33 

34from lsst.pipe.tasks.matchDiffimSourceInjected import ( 

35 MatchInjectedToDiaSourceTask, 

36 MatchInjectedToAssocDiaSourceTask, 

37 MatchInjectedToDiaSourceConfig, 

38 MatchInjectedToAssocDiaSourceConfig, 

39) 

40 

41 

42class BaseTestMatchInjected(lsst.utils.tests.TestCase): 

43 def setUp(self): 

44 rng = np.random.RandomState(6) 

45 # 0.35 arcsec = 0.5/np.sqrt(2) arcsec (half of the diagonal of a 0.5 arcsec square) 

46 offsetFactor = 1./3600 * 0.35 

47 

48 # Create a mock injected catalog 

49 self.injectedCat = Table( 

50 { 

51 "injection_id": [1, 2, 3, 5, 6, 7, 12, 21, 31, 49], 

52 "injection_flag": np.repeat(0, 10), 

53 # random positions with 10 arcmin size 

54 # ra centered at 0, dec centered at -30 

55 "ra": (1/6.) * rng.random(size=10), 

56 "dec": -30 + (1/6.) * rng.random(size=10), 

57 "mag": 20.25 - 0.5 * rng.random(size=10), # random magnitudes 

58 "source_type": np.repeat("DeltaFunction", 10) 

59 } 

60 ) 

61 

62 # Create a mock diaSources catalog 

63 schema = SourceTable.makeMinimalSchema() 

64 self.diaSources = SourceCatalog(schema) 

65 for i in range(5): 

66 record = self.diaSources.addNew() 

67 record.setId(100 + i) 

68 record.setCoord( 

69 # Random posisions of diaSources 

70 SpherePoint((1/6.) * rng.random(), -30 + (1/6.) * rng.random(), degrees) 

71 ) 

72 

73 for i, (ra, dec) in enumerate(self.injectedCat[['ra', 'dec']][:8]): 

74 record = self.diaSources.addNew() 

75 sign = rng.choice([-1, 1], size=2) 

76 record.setId(i + 200) 

77 record.setCoord( 

78 SpherePoint( 

79 ra+sign[0]*rng.random()*offsetFactor, 

80 dec+sign[1]*rng.random()*offsetFactor, 

81 degrees 

82 ) 

83 ) 

84 

85 # Create a mock difference image 

86 self.diffIm = ExposureF(Extent2I(4096, 4096)) 

87 crpix = Point2D(0, 0) 

88 crval = SpherePoint(0, -30, degrees) 

89 cdMatrix = np.array([[5.19513851e-05, 2.81124812e-07], 

90 [3.25186974e-07, 5.19112119e-05]]) 

91 wcs = makeSkyWcs(crpix, crval, cdMatrix) 

92 self.diffIm.setWcs(wcs) 

93 

94 # add a fake source outside of the image 

95 self.injectedCatForTrimming = self.injectedCat.copy() 

96 self.injectedCatForTrimming.add_row( 

97 { 

98 'injection_id': 50, 

99 'injection_flag': 0, 

100 'ra': 360. - 0.1/6., 

101 'dec': -30 - 0.1/6., 

102 'mag': 20.0, 

103 'source_type': 'DeltaFunction' 

104 } 

105 ) 

106 

107 # only 4 injected sources are associated 

108 self.assocDiaSources = Table( 

109 { 

110 "diaSourceId": [101, 102, 103, 201, 202, 205, 207], 

111 "band": np.repeat("r", 7), 

112 "visit": np.repeat(410001, 7), 

113 "detector": np.repeat(0, 7), 

114 "diaObjectId": np.arange(7), 

115 } 

116 ) 

117 

118 

119class TestMatchInjectedToDiaSourceTask(BaseTestMatchInjected): 

120 

121 def test_run(self): 

122 config = MatchInjectedToDiaSourceConfig() 

123 config.matchDistanceArcseconds = 0.5 

124 config.doMatchVisit = False 

125 config.doForcedMeasurement = False 

126 

127 task = MatchInjectedToDiaSourceTask(config=config) 

128 

129 result = task.run(self.injectedCat, self.diffIm, self.diaSources) 

130 self.assertEqual(len(result.matchDiaSources), len(self.injectedCat)) 

131 self.assertEqual(np.sum(result.matchDiaSources['diaSourceId'] > 0), 8) 

132 self.assertEqual(np.sum(result.matchDiaSources['dist_diaSrc'] > 0), 8) 

133 self.assertEqual( 

134 np.sum(np.abs(result.matchDiaSources['dist_diaSrc']) < config.matchDistanceArcseconds), 8 

135 ) 

136 

137 def test_run_trimming(self): 

138 config = MatchInjectedToDiaSourceConfig() 

139 config.matchDistanceArcseconds = 0.5 

140 config.doMatchVisit = True 

141 config.doForcedMeasurement = False 

142 

143 task = MatchInjectedToDiaSourceTask(config=config) 

144 result = task.run(self.injectedCatForTrimming, self.diffIm, self.diaSources) 

145 

146 self.assertEqual(len(result.matchDiaSources), len(self.injectedCatForTrimming) - 1) 

147 self.assertEqual(np.sum(result.matchDiaSources['diaSourceId'] > 0), 8) 

148 self.assertEqual(np.sum(result.matchDiaSources['dist_diaSrc'] > 0), 8) 

149 self.assertEqual( 

150 np.sum(np.abs(result.matchDiaSources['dist_diaSrc']) < config.matchDistanceArcseconds), 8 

151 ) 

152 

153 def test_getVectors(self): 

154 config = MatchInjectedToDiaSourceConfig() 

155 config.matchDistanceArcseconds = 0.5 

156 config.doMatchVisit = False 

157 config.doForcedMeasurement = False 

158 

159 task = MatchInjectedToDiaSourceTask(config=config) 

160 

161 ras = np.radians(self.injectedCat['ra']) 

162 decs = np.radians(self.injectedCat['dec']) 

163 vectors = task._getVectors(ras, decs) 

164 self.assertEqual(vectors.shape, (10, 3)) 

165 

166 

167class TestMatchInjectedToAssocDiaSourceTask(BaseTestMatchInjected): 

168 

169 def test_run(self): 

170 config = MatchInjectedToDiaSourceConfig() 

171 config.matchDistanceArcseconds = 0.5 

172 config.doMatchVisit = False 

173 config.doForcedMeasurement = False 

174 

175 task = MatchInjectedToDiaSourceTask(config=config) 

176 

177 result = task.run(self.injectedCat, self.diffIm, self.diaSources) 

178 

179 configAssoc = MatchInjectedToAssocDiaSourceConfig() 

180 taskAssoc = MatchInjectedToAssocDiaSourceTask(config=configAssoc) 

181 resultAssoc = taskAssoc.run(self.assocDiaSources, result.matchDiaSources) 

182 self.assertEqual(len(resultAssoc.matchAssocDiaSources), len(self.injectedCat)) 

183 self.assertEqual(np.sum(resultAssoc.matchAssocDiaSources['isAssocDiaSource']), 4) 

184 

185 

186if __name__ == "__main__": 186 ↛ 187line 186 didn't jump to line 187 because the condition on line 186 was never true

187 unittest.main()