Coverage for tests/test_accumulator_mean_stack.py: 17%

117 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-01 01:39 -0700

1import unittest 

2 

3import numpy as np 

4import numpy.testing as testing 

5 

6import lsst.utils.tests 

7import lsst.afw.image as afwImage 

8import lsst.afw.math as afwMath 

9from lsst.meas.algorithms import AccumulatorMeanStack 

10 

11 

12class AccumulatorMeanStackTestCase(lsst.utils.tests.TestCase): 

13 def make_test_images_to_coadd(self): 

14 """Make test images to coadd.""" 

15 np.random.seed(12345) 

16 

17 # Choose an arbitrary number of images and image size. 

18 # Below we set bad pixels for either 1 or half these images, 

19 # at arbitrary locations. 

20 n_image = 10 

21 image_size = (200, 200) 

22 

23 # Create noise field images each with constant variance. 

24 # Create some fake pixel-sized sources at fixed positions as well. 

25 # Create some bad pixels at random positions, and some others at 

26 # fixed positions (but not in all images). 

27 exposures = [] 

28 variances = np.random.uniform(size=n_image, low=5.0, high=10.0) 

29 

30 for i in range(n_image): 

31 exposure = afwImage.ExposureF(width=image_size[0], height=image_size[1]) 

32 exposure.variance.array[:, :] = variances[i] 

33 exposure.image.array[:, :] = np.random.normal(loc=0.0, 

34 scale=np.sqrt(variances[i]), 

35 size=image_size) 

36 exposure.image.array[50, 50] = np.random.normal(loc=100.0, 

37 scale=np.sqrt(100.0)) 

38 exposure.image.array[100, 100] = np.random.normal(loc=200.0, 

39 scale=np.sqrt(200.0)) 

40 if (i == 2): 

41 # Create one outlier pixel 

42 exposure.image.array[150, 125] = 1000.0 

43 

44 exposure.image.array[150, 150] = np.nan 

45 exposure.mask.array[150, 150] = afwImage.Mask.getPlaneBitMask(['NO_DATA']) 

46 

47 # Create two saturated pixels, one above and one below the threshold. 

48 if (i < 5): 

49 exposure.mask.array[50, 100] = afwImage.Mask.getPlaneBitMask(['SAT']) 

50 if i == 1: 

51 exposure.mask.array[51, 100] = afwImage.Mask.getPlaneBitMask(['SAT']) 

52 

53 exposure.mask.array[0: 2, :] |= afwImage.Mask.getPlaneBitMask(['EDGE']) 

54 

55 exposures.append(exposure) 

56 

57 weights = np.random.uniform(size=n_image, low=0.9, high=1.1) 

58 

59 return exposures, weights 

60 

61 def make_coadd_exposure(self, exposure_ref): 

62 """Make a coadd exposure with mask planes.""" 

63 coadd_exposure = afwImage.ExposureF(width=exposure_ref.getWidth(), 

64 height=exposure_ref.getHeight()) 

65 coadd_exposure.mask.addMaskPlane("REJECTED") 

66 coadd_exposure.mask.addMaskPlane("CLIPPED") 

67 coadd_exposure.mask.addMaskPlane("SENSOR_EDGE") 

68 

69 return coadd_exposure 

70 

71 def make_stats_ctrl(self): 

72 """Make the reference stats_ctrl.""" 

73 stats_ctrl = afwMath.StatisticsControl() 

74 stats_ctrl.setAndMask(afwImage.Mask.getPlaneBitMask(["NO_DATA", 

75 "BAD", 

76 "SAT", 

77 "SUSPECT"])) 

78 stats_ctrl.setNanSafe(True) 

79 stats_ctrl.setWeighted(True) 

80 stats_ctrl.setCalcErrorFromInputVariance(True) 

81 bit = afwImage.Mask.getMaskPlane("SAT") 

82 stats_ctrl.setMaskPropagationThreshold(bit, 0.2) 

83 

84 return stats_ctrl 

85 

86 def make_mask_map(self, stats_ctrl): 

87 """Make the mask_map.""" 

88 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED") 

89 edge = afwImage.Mask.getPlaneBitMask("EDGE") 

90 no_data = afwImage.Mask.getPlaneBitMask("NO_DATA") 

91 to_reject = stats_ctrl.getAndMask() & (~no_data) & (~edge) & (~clipped) 

92 mask_map = [(to_reject, afwImage.Mask.getPlaneBitMask("REJECTED")), 

93 (edge, afwImage.Mask.getPlaneBitMask("SENSOR_EDGE")), 

94 (clipped, clipped)] 

95 

96 return mask_map 

97 

98 def test_online_coadd_input_variance_true(self): 

99 """Test online coaddition with calcErrorFromInputVariance=True.""" 

100 exposures, weights = self.make_test_images_to_coadd() 

101 coadd_exposure = self.make_coadd_exposure(exposures[0]) 

102 stats_ctrl = self.make_stats_ctrl() 

103 stats_ctrl.setCalcErrorFromInputVariance(True) 

104 mask_map = self.make_mask_map(stats_ctrl) 

105 

106 stats_flags = afwMath.stringToStatisticsProperty("MEAN") 

107 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED") 

108 

109 masked_image_list = [exp.maskedImage for exp in exposures] 

110 

111 afw_masked_image = afwMath.statisticsStack(masked_image_list, 

112 stats_flags, 

113 stats_ctrl, 

114 weights, 

115 clipped, 

116 mask_map) 

117 

118 mask_threshold_dict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(stats_ctrl) 

119 

120 # Make the stack with the online accumulator 

121 stacker = AccumulatorMeanStack( 

122 coadd_exposure.image.array.shape, 

123 stats_ctrl.getAndMask(), 

124 mask_threshold_dict=mask_threshold_dict, 

125 mask_map=mask_map, 

126 no_good_pixels_mask=stats_ctrl.getNoGoodPixelsMask(), 

127 calc_error_from_input_variance=stats_ctrl.getCalcErrorFromInputVariance(), 

128 compute_n_image=True) 

129 

130 for exposure, weight in zip(exposures, weights): 

131 stacker.add_masked_image(exposure.maskedImage, weight=weight) 

132 

133 stacker.fill_stacked_masked_image(coadd_exposure.maskedImage) 

134 

135 online_masked_image = coadd_exposure.maskedImage 

136 

137 # The coadds match at the <1e-5 level. 

138 testing.assert_array_almost_equal(online_masked_image.image.array, 

139 afw_masked_image.image.array, decimal=5) 

140 testing.assert_array_almost_equal(online_masked_image.variance.array, 

141 afw_masked_image.variance.array) 

142 testing.assert_array_equal(online_masked_image.mask.array, 

143 afw_masked_image.mask.array) 

144 

145 def test_online_coadd_input_variance_false(self): 

146 """Test online coaddition with calcErrorFromInputVariance=False.""" 

147 exposures, weights = self.make_test_images_to_coadd() 

148 coadd_exposure = self.make_coadd_exposure(exposures[0]) 

149 stats_ctrl = self.make_stats_ctrl() 

150 stats_ctrl.setCalcErrorFromInputVariance(False) 

151 mask_map = self.make_mask_map(stats_ctrl) 

152 

153 stats_flags = afwMath.stringToStatisticsProperty("MEAN") 

154 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED") 

155 

156 masked_image_list = [exp.maskedImage for exp in exposures] 

157 

158 afw_masked_image = afwMath.statisticsStack(masked_image_list, 

159 stats_flags, 

160 stats_ctrl, 

161 weights, 

162 clipped, 

163 mask_map) 

164 

165 mask_threshold_dict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(stats_ctrl) 

166 

167 # Make the stack with the online accumulator 

168 

169 # By setting no_good_pixels=None we have the same behavior 

170 # as the default from stats_ctrl.getNoGoodPixelsMask(), but 

171 # covers the alternate code path. 

172 stacker = AccumulatorMeanStack( 

173 coadd_exposure.image.array.shape, 

174 stats_ctrl.getAndMask(), 

175 mask_threshold_dict=mask_threshold_dict, 

176 mask_map=mask_map, 

177 no_good_pixels_mask=None, 

178 calc_error_from_input_variance=stats_ctrl.getCalcErrorFromInputVariance(), 

179 compute_n_image=True) 

180 

181 for exposure, weight in zip(exposures, weights): 

182 stacker.add_masked_image(exposure.maskedImage, weight=weight) 

183 

184 stacker.fill_stacked_masked_image(coadd_exposure.maskedImage) 

185 

186 online_masked_image = coadd_exposure.maskedImage 

187 

188 # The coadds match at the <1e-5 level. 

189 testing.assert_array_almost_equal(online_masked_image.image.array, 

190 afw_masked_image.image.array, decimal=5) 

191 # The computed variances match at the <1e-4 level. 

192 testing.assert_array_almost_equal(online_masked_image.variance.array, 

193 afw_masked_image.variance.array, decimal=4) 

194 testing.assert_array_equal(online_masked_image.mask.array, 

195 afw_masked_image.mask.array) 

196 

197 def test_online_coadd_image(self): 

198 """Test online coaddition with regular non-masked images.""" 

199 exposures, weights = self.make_test_images_to_coadd() 

200 coadd_exposure = self.make_coadd_exposure(exposures[0]) 

201 stats_ctrl = self.make_stats_ctrl() 

202 stats_ctrl.setAndMask(0) 

203 stats_ctrl.setCalcErrorFromInputVariance(True) 

204 mask_map = self.make_mask_map(stats_ctrl) 

205 

206 stats_flags = afwMath.stringToStatisticsProperty("MEAN") 

207 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED") 

208 

209 masked_image_list = [exp.maskedImage for exp in exposures] 

210 

211 afw_masked_image = afwMath.statisticsStack(masked_image_list, 

212 stats_flags, 

213 stats_ctrl, 

214 weights, 

215 clipped, 

216 mask_map) 

217 

218 # Make the stack with the online accumulator 

219 stacker = AccumulatorMeanStack( 

220 coadd_exposure.image.array.shape, 

221 stats_ctrl.getAndMask(), 

222 mask_map=mask_map, 

223 no_good_pixels_mask=stats_ctrl.getNoGoodPixelsMask(), 

224 calc_error_from_input_variance=stats_ctrl.getCalcErrorFromInputVariance(), 

225 compute_n_image=True) 

226 

227 for exposure, weight in zip(exposures, weights): 

228 stacker.add_image(exposure.image, weight=weight) 

229 

230 stacker.fill_stacked_image(coadd_exposure.image) 

231 

232 online_image = coadd_exposure.image 

233 

234 # The unmasked coadd good pixels should match at the <1e-5 level 

235 # The masked pixels will not be correct for straight image stacking. 

236 good_pixels = np.where(afw_masked_image.mask.array == 0) 

237 

238 testing.assert_array_almost_equal(online_image.array[good_pixels], 

239 afw_masked_image.image.array[good_pixels], 

240 decimal=5) 

241 

242 

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

244 pass 

245 

246 

247def setup_module(module): 

248 lsst.utils.tests.init() 

249 

250 

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

252 lsst.utils.tests.init() 

253 unittest.main()