Coverage for tests / test_dynamicDetection.py: 26%

114 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-30 08:57 +0000

1import unittest 

2 

3import lsst.utils.tests 

4import numpy as np 

5from lsst.afw.geom import makeCdMatrix, makeSkyWcs 

6from lsst.afw.table import SourceTable 

7from lsst.geom import Box2I, Extent2I, Point2D, Point2I, SpherePoint, degrees 

8from lsst.meas.algorithms import DynamicDetectionTask, InsufficientSourcesError 

9from lsst.meas.algorithms.testUtils import plantSources 

10from lsst.pex.config import FieldValidationError 

11 

12 

13class DynamicDetectionTest(lsst.utils.tests.TestCase): 

14 def setUp(self): 

15 xy0 = Point2I(12345, 67890) # xy0 for image 

16 dims = Extent2I(2345, 2345) # Dimensions of image 

17 box = Box2I(xy0, dims) # Bounding box of image 

18 sigma = 3.21 # PSF sigma 

19 buffer = 4.0 # Buffer for star centers around edge 

20 nSigmaForKernel = 5.0 # Number of PSF sigmas for kernel 

21 sky = 12345.6 # Sky level 

22 numStars = 100 # Number of stars 

23 noise = np.sqrt(sky)*np.pi*sigma**2 # Poisson noise per PSF 

24 faint = 1.0*noise # Faintest level for star fluxes 

25 bright = 100.0*noise # Brightest level for star fluxes 

26 starBox = Box2I(box) # Area on image in which we can put star centers 

27 starBox.grow(-int(buffer*sigma)) 

28 scale = 1.0e-5*degrees # Pixel scale 

29 

30 rng = np.random.Generator(np.random.MT19937(12345)) 

31 stars = [(xx, yy, ff, sigma) for xx, yy, ff in 

32 zip(rng.uniform(starBox.getMinX(), starBox.getMaxX(), numStars), 

33 rng.uniform(starBox.getMinY(), starBox.getMaxY(), numStars), 

34 np.linspace(faint, bright, numStars))] 

35 self.exposure = plantSources(box, 2*int(nSigmaForKernel*sigma) + 1, sky, stars, True) 

36 self.exposure.setWcs(makeSkyWcs(crpix=Point2D(0, 0), 

37 crval=SpherePoint(0, 0, degrees), 

38 cdMatrix=makeCdMatrix(scale=scale))) 

39 

40 self.config = DynamicDetectionTask.ConfigClass() 

41 self.rtol = 0.1 

42 

43 def tearDown(self): 

44 del self.exposure 

45 

46 def check(self, expectFactor, config): 

47 schema = SourceTable.makeMinimalSchema() 

48 task = DynamicDetectionTask(config=config, schema=schema) 

49 table = SourceTable.make(schema) 

50 

51 results = task.run(table, self.exposure, expId=12345) 

52 self.assertFloatsAlmostEqual(results.factor, expectFactor, rtol=self.rtol) 

53 

54 def testVanilla(self): 

55 """Dynamic detection used as normal detection.""" 

56 self.check(1.0, self.config) 

57 

58 def testDynamic(self): 

59 """Modify the variance plane, and see if the task is able to determine 

60 what we did. 

61 """ 

62 factor = 2.0 

63 self.exposure.maskedImage.variance /= factor 

64 self.check(1.0/np.sqrt(factor), self.config) 

65 

66 def testNoWcs(self): 

67 """Check that dynamic detection runs when the exposure wcs is None.""" 

68 self.exposure.setWcs(None) 

69 self.check(1.0, self.config) 

70 

71 def testMinimalSkyObjects(self): 

72 """Check that dynamic detection runs when there are a relatively small 

73 number of sky objects. 

74 """ 

75 config = DynamicDetectionTask.ConfigClass() 

76 config.skyObjects.nSources = int(0.1 * self.config.skyObjects.nSources) 

77 self.check(1.0, config) 

78 

79 def testDynamicNoLimits(self): 

80 """Test setting the threshold scaling and background tweak limits to 

81 None (i.e. no limits imposed). 

82 """ 

83 config = DynamicDetectionTask.ConfigClass() 

84 config.minThresholdScaleFactor = None 

85 config.maxThresholdScaleFactor = None 

86 config.minBackgroundTweak = None 

87 config.maxBackgroundTweak = None 

88 self.check(1.0, config) 

89 

90 def testNoThresholdScaling(self): 

91 """Check that dynamic detection runs when doThresholdScaling is False. 

92 """ 

93 config = DynamicDetectionTask.ConfigClass() 

94 config.doThresholdScaling = False 

95 self.check(1.0, config) 

96 

97 def testNoBackgroundTweak(self): 

98 """Check that dynamic detection runs when doBackgroundTweak is False. 

99 """ 

100 config = DynamicDetectionTask.ConfigClass() 

101 config.doBackgroundTweak = False 

102 self.check(1.0, config) 

103 

104 def testThresholdScalingAndNoBackgroundTweak(self): 

105 """Check that dynamic detection runs when both doThresholdScaling and 

106 doBackgroundTweak are False. 

107 """ 

108 config = DynamicDetectionTask.ConfigClass() 

109 config.doThresholdScaling = False 

110 config.doBackgroundTweak = False 

111 self.check(1.0, config) 

112 

113 def testBrightDetectionPass(self): 

114 """Check that the maximum number of bright detection iterations is 

115 observed. 

116 

117 The bright detection loop is forced by setting config.brightMultiplier 

118 so low such that the entire image is marked as DETECTED. As such, the 

119 task is doomed to fail where it tries to lay down sky sources." 

120 """ 

121 config = DynamicDetectionTask.ConfigClass() 

122 config.brightMultiplier = 0.05 

123 config.brightDetectionIterMax = 2 

124 with self.assertRaisesRegex( 

125 InsufficientSourcesError, "Insufficient good sky source flux measurements"): 

126 self.check(1.0, config) 

127 

128 def testThresholdsOutsideBounds(self): 

129 """Check that dynamic detection properly sets threshold limits. 

130 """ 

131 schema = SourceTable.makeMinimalSchema() 

132 config = DynamicDetectionTask.ConfigClass() 

133 config.minThresholdScaleFactor = 1.05 

134 config.maxBackgroundTweak = -0.1 

135 table = SourceTable.make(schema) 

136 task = DynamicDetectionTask(config=config, schema=schema) 

137 task.run(table, self.exposure, expId=12345) 

138 

139 config = DynamicDetectionTask.ConfigClass() 

140 config.maxThresholdScaleFactor = 0.99 

141 config.minBackgroundTweak = 0.1 

142 table = SourceTable.make(schema) 

143 task = DynamicDetectionTask(config=config, schema=schema) 

144 task.run(table, self.exposure, expId=12345) 

145 

146 def testConfigValidation(self): 

147 """Check that the field validation is working correctly. 

148 """ 

149 schema = SourceTable.makeMinimalSchema() 

150 config = DynamicDetectionTask.ConfigClass() 

151 config.minThresholdScaleFactor = 1.05 

152 config.maxThresholdScaleFactor = 1.01 

153 with self.assertRaisesRegex( 

154 FieldValidationError, "minThresholdScaleFactor must be <= maxThresholdScaleFactor"): 

155 DynamicDetectionTask(config=config, schema=schema) 

156 

157 config = DynamicDetectionTask.ConfigClass() 

158 config.minBackgroundTweak = 2.0 

159 config.maxBackgroundTweak = 1.0 

160 with self.assertRaisesRegex( 

161 FieldValidationError, "minBackgroundTweak must be <= maxBackgroundTweak"): 

162 DynamicDetectionTask(config=config, schema=schema) 

163 

164 def testNoSkyObjects(self): 

165 """Check that dynamic detection runs when there are no sky objects. 

166 

167 Notes 

168 ----- 

169 This test, originally named 'testNoSources', was deactivated on 

170 DM-39809 because the dynamic detection task is not able to function 

171 without any sky objects. This test should be reactivated once DM-39826 

172 has been completed. 

173 """ 

174 pass 

175 

176 

177class TestMemory(lsst.utils.tests.MemoryTestCase): 

178 pass 

179 

180 

181def setup_module(module): 

182 lsst.utils.tests.init() 

183 

184 

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

186 import sys 

187 setup_module(sys.modules['__main__']) 

188 unittest.main()