Coverage for tests/test_operators.py: 10%

136 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-01 15:13 -0700

1# This file is part of lsst.scarlet.lite. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import os 

23 

24import numpy as np 

25from lsst.scarlet.lite import Image 

26from lsst.scarlet.lite.operators import ( 

27 Monotonicity, 

28 prox_connected, 

29 prox_monotonic_mask, 

30 prox_sdss_symmetry, 

31 prox_uncentered_symmetry, 

32) 

33from numpy.testing import assert_array_equal 

34from utils import ScarletTestCase 

35 

36 

37class TestOperators(ScarletTestCase): 

38 def setUp(self) -> None: 

39 filename = os.path.join(__file__, "..", "..", "data", "hsc_cosmos_35.npz") 

40 filename = os.path.abspath(filename) 

41 data = np.load(filename) 

42 self.detect = np.sum(data["images"], axis=0) 

43 self.centers = np.array([data["catalog"]["y"], data["catalog"]["x"]]).T 

44 

45 def test_prox_connected(self): 

46 image1 = Image(np.full((5, 10), 1), yx0=(5, 2)) 

47 image2 = Image(np.full((3, 9), 2), yx0=(20, 16)) 

48 image3 = Image(np.full((5, 12), 3), yx0=(10, 30)) 

49 image4 = Image(np.full((7, 3), 4), yx0=(29, 5)) 

50 image5 = Image(np.full((11, 15), 5), yx0=(30, 30)) 

51 

52 image = Image(np.zeros((50, 50), dtype=float)) 

53 image += image1 + image2 + image3 

54 full_image = image + image4 + image5 

55 

56 # Include 3 of the 5 box centers 

57 centers = [(7, 7), (21, 20), (12, 36)] 

58 

59 result = prox_connected(full_image.data, centers) 

60 assert_array_equal(result, image.data) 

61 

62 def test_monotonicity(self): 

63 shape = (201, 201) 

64 cx = (shape[1] - 1) >> 1 

65 cy = (shape[0] - 1) >> 1 

66 x = np.arange(shape[1], dtype=float) - cx 

67 y = np.arange(shape[0], dtype=float) - cy 

68 x, y = np.meshgrid(x, y) 

69 distance = np.sqrt(x**2 + y**2) 

70 

71 neighbor_dist = np.zeros((9,) + distance.shape, dtype=float) 

72 neighbor_dist[0, 1:, 1:] = distance[1:, 1:] - distance[:-1, :-1] 

73 neighbor_dist[1, 1:, :] = distance[1:, :] - distance[:-1, :] 

74 neighbor_dist[2, 1:, :-1] = distance[1:, :-1] - distance[:-1, 1:] 

75 neighbor_dist[3, :, 1:] = distance[:, 1:] - distance[:, :-1] 

76 # For the center pixel, set the distance to 1 just so that it is 

77 # non-zero 

78 neighbor_dist[4, cy, cx] = 1 

79 neighbor_dist[5, :, :-1] = distance[:, :-1] - distance[:, 1:] 

80 neighbor_dist[6, :-1, 1:] = distance[:-1, 1:] - distance[1:, :-1] 

81 neighbor_dist[7, :-1, :] = distance[:-1, :] - distance[1:, :] 

82 neighbor_dist[8, :-1, :-1] = distance[:-1, :-1] - distance[1:, 1:] 

83 

84 monotonicity = Monotonicity(shape) 

85 assert_array_equal(monotonicity.distance, distance) 

86 assert_array_equal(monotonicity.weights > 0, neighbor_dist > 0) 

87 self.assertTupleEqual(monotonicity.shape, (201, 201)) 

88 self.assertTupleEqual(monotonicity.center, (100, 100)) 

89 

90 # Since the monotonicity operators _are_ the test for monotonicity, 

91 # we just check that the two different monotonicty operators run, 

92 # and that the weighted monotonicity operator is still monotonic 

93 # according to the monotonicity mask operator. 

94 morph = self.detect.copy() 

95 cy, cx = self.centers[1].astype(int) 

96 morph = monotonicity(morph, (cy, cx)) 

97 # Add zero threshold 

98 morph[morph < 0] = 0 

99 # Get the monotonic mask soluton 

100 _, masked, _ = prox_monotonic_mask( 

101 morph.copy(), 

102 (cy, cx), 

103 0, 

104 0, 

105 0, 

106 ) 

107 # The operators are not exactly equal, since the weighted monotonicity 

108 # uses diagonal pixels and the monotonic mask does not take those 

109 # into account. So we allow for known pixels to be different. 

110 diff = np.abs(morph - masked) 

111 self.assertEqual(np.sum(diff > 0), 198) 

112 

113 # Test that interpolating edge pixels is working 

114 _, interpolated, _ = prox_monotonic_mask( 

115 morph.copy(), 

116 (cy, cx), 

117 0, 

118 0, 

119 3, 

120 ) 

121 

122 # Remove all of the diagonal weights and check that the 

123 # weighted monotonic solution agrees with the monotonic mask solution. 

124 weights = monotonicity.weights 

125 weights[0] = 0 

126 weights[2] = 0 

127 weights[6] = 0 

128 weights[8] = 0 

129 

130 morph = self.detect.copy() 

131 cy, cx = self.centers[1].astype(int) 

132 morph = monotonicity(morph, (cy, cx)) 

133 # Add zero threshold 

134 morph[morph < 0] = 0 

135 # Get the monotonic mask soluton 

136 _, masked, _ = prox_monotonic_mask( 

137 morph.copy(), 

138 (cy, cx), 

139 0, 

140 0, 

141 0, 

142 ) 

143 assert_array_equal(morph, masked) 

144 

145 def test_resize_monotonicity(self): 

146 monotonicity = Monotonicity((101, 101)) 

147 morph = self.detect.copy() 

148 cy, cx = self.centers[1].astype(int) 

149 morph = monotonicity(morph, (cy, cx)) 

150 self.assertTupleEqual(monotonicity.shape, (101, 101)) 

151 

152 monotonicity.update((201, 201)) 

153 morph2 = monotonicity(morph, (cy, cx)) 

154 self.assertTupleEqual(monotonicity.shape, (201, 201)) 

155 assert_array_equal(morph, morph2) 

156 

157 with self.assertRaises(ValueError): 

158 # Even shapes not allowed 

159 Monotonicity((100, 100)) 

160 

161 with self.assertRaises(ValueError): 

162 # The shape should only have 2 dimensions 

163 Monotonicity((101, 101, 101)) # type: ignore 

164 

165 with self.assertRaises(ValueError): 

166 # The shape should have exactly 2 dimensions 

167 Monotonicity((101,)) # type: ignore 

168 

169 def test_check_size(self): 

170 monotonicity = Monotonicity((11, 11)) 

171 self.assertTupleEqual(monotonicity.shape, (11, 11)) 

172 self.assertTupleEqual(monotonicity.sizes, (5, 5, 6, 6)) 

173 morph = self.detect.copy() 

174 cy, cx = self.centers[1].astype(int) 

175 monotonicity(morph, (cy, cx)) 

176 self.assertTupleEqual(monotonicity.shape, (73, 73)) 

177 self.assertTupleEqual(monotonicity.sizes, (36, 36, 37, 37)) 

178 

179 monotonicity = Monotonicity((11, 11), auto_update=False) 

180 with self.assertRaises(ValueError): 

181 monotonicity(morph, (cy, cx)) 

182 

183 def test_off_center_monotonicity(self): 

184 monotonicity = Monotonicity((101, 101)) 

185 morph = self.detect.copy() 

186 cy, cx = self.centers[1].astype(int) 

187 truth = monotonicity(morph.copy(), (cy, cx)) 

188 morph = monotonicity(morph, (cy + 1, cx)) 

189 assert_array_equal(morph, truth) 

190 

191 # Shift by 2 pixels and confirm that the morphologies are not equal 

192 morph = self.detect.copy() 

193 morph = monotonicity(morph, (cy + 2, cx)) 

194 with self.assertRaises(AssertionError): 

195 assert_array_equal(morph, truth) 

196 

197 # Now increase the search radius and try again 

198 monotonicity = Monotonicity((101, 101), fit_radius=2) 

199 morph = self.detect.copy() 

200 morph = monotonicity(morph, (cy + 2, cx)) 

201 assert_array_equal(morph, truth) 

202 

203 monotonicity = Monotonicity((101, 101), fit_radius=2) 

204 morph = self.detect.copy() 

205 morph = monotonicity(morph, (cy + 1, cx + 1)) 

206 assert_array_equal(morph, truth) 

207 

208 def test_symmetry(self): 

209 # Test simple symmetry 

210 morph = np.arange(27).reshape(3, 9) 

211 truth = np.array(list(range(14)) + list(range(13)[::-1])).reshape(3, 9) 

212 morph = prox_sdss_symmetry(morph) 

213 assert_array_equal(morph, truth) 

214 

215 # Test uncentered symmetry 

216 morph = np.arange(50).reshape(5, 10) 

217 

218 symmetric = [ 

219 [25, 26, 27, 28, 29], 

220 [35, 36, 37, 36, 35], 

221 [29, 28, 27, 26, 25], 

222 ] 

223 

224 # Test leaving the non-symmetric part of the morphology 

225 truth = morph.copy() 

226 truth[2:, 5:] = symmetric 

227 center = (3, 7) 

228 symmetric_morph = prox_uncentered_symmetry(morph.copy(), center) 

229 assert_array_equal(symmetric_morph, truth) 

230 

231 # Test setting the non-symmetric part of the morphology to zero 

232 truth = np.zeros(morph.shape, dtype=int) 

233 truth[2:, 5:] = symmetric 

234 symmetric_morph = prox_uncentered_symmetry(morph.copy(), center, 0) 

235 assert_array_equal(symmetric_morph, truth) 

236 

237 # Test skipping re-centering if the center of the source 

238 # is the center of the image 

239 _morph = morph[2:, 5:] 

240 symmetric_morph = prox_uncentered_symmetry(_morph.copy(), (1, 2)) 

241 assert_array_equal(symmetric_morph, symmetric) 

242 

243 # Test using the default center of the image 

244 _morph = morph[2:, 5:] 

245 symmetric_morph = prox_uncentered_symmetry(_morph.copy()) 

246 assert_array_equal(symmetric_morph, _morph)