Coverage for tests/test_accumulator_mean_stack.py: 14%
117 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-08 02:15 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-08 02:15 -0700
1import unittest
3import numpy as np
4import numpy.testing as testing
6import lsst.utils.tests
7import lsst.afw.image as afwImage
8import lsst.afw.math as afwMath
9from lsst.meas.algorithms import AccumulatorMeanStack
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)
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)
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)
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
44 exposure.image.array[150, 150] = np.nan
45 exposure.mask.array[150, 150] = afwImage.Mask.getPlaneBitMask(['NO_DATA'])
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'])
53 exposure.mask.array[0: 2, :] |= afwImage.Mask.getPlaneBitMask(['EDGE'])
55 exposures.append(exposure)
57 weights = np.random.uniform(size=n_image, low=0.9, high=1.1)
59 return exposures, weights
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")
69 return coadd_exposure
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)
84 return stats_ctrl
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)]
96 return mask_map
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)
106 stats_flags = afwMath.stringToStatisticsProperty("MEAN")
107 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED")
109 masked_image_list = [exp.maskedImage for exp in exposures]
111 afw_masked_image = afwMath.statisticsStack(masked_image_list,
112 stats_flags,
113 stats_ctrl,
114 weights,
115 clipped,
116 mask_map)
118 mask_threshold_dict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(stats_ctrl)
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)
130 for exposure, weight in zip(exposures, weights):
131 stacker.add_masked_image(exposure.maskedImage, weight=weight)
133 stacker.fill_stacked_masked_image(coadd_exposure.maskedImage)
135 online_masked_image = coadd_exposure.maskedImage
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)
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)
153 stats_flags = afwMath.stringToStatisticsProperty("MEAN")
154 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED")
156 masked_image_list = [exp.maskedImage for exp in exposures]
158 afw_masked_image = afwMath.statisticsStack(masked_image_list,
159 stats_flags,
160 stats_ctrl,
161 weights,
162 clipped,
163 mask_map)
165 mask_threshold_dict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(stats_ctrl)
167 # Make the stack with the online accumulator
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)
181 for exposure, weight in zip(exposures, weights):
182 stacker.add_masked_image(exposure.maskedImage, weight=weight)
184 stacker.fill_stacked_masked_image(coadd_exposure.maskedImage)
186 online_masked_image = coadd_exposure.maskedImage
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)
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)
206 stats_flags = afwMath.stringToStatisticsProperty("MEAN")
207 clipped = afwImage.Mask.getPlaneBitMask("CLIPPED")
209 masked_image_list = [exp.maskedImage for exp in exposures]
211 afw_masked_image = afwMath.statisticsStack(masked_image_list,
212 stats_flags,
213 stats_ctrl,
214 weights,
215 clipped,
216 mask_map)
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)
227 for exposure, weight in zip(exposures, weights):
228 stacker.add_image(exposure.image, weight=weight)
230 stacker.fill_stacked_image(coadd_exposure.image)
232 online_image = coadd_exposure.image
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)
238 testing.assert_array_almost_equal(online_image.array[good_pixels],
239 afw_masked_image.image.array[good_pixels],
240 decimal=5)
243class TestMemory(lsst.utils.tests.MemoryTestCase):
244 pass
247def setup_module(module):
248 lsst.utils.tests.init()
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()