Coverage for tests / test_source.py: 11%

134 statements  

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

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 numpy as np 

23from lsst.scarlet.lite import Box, Image, Source 

24from lsst.scarlet.lite.component import FactorizedComponent 

25from lsst.scarlet.lite.utils import integrated_circular_gaussian 

26from utils import ScarletTestCase 

27 

28 

29class TestSource(ScarletTestCase): 

30 def setUp(self) -> None: 

31 super().setUp() 

32 self.bands = tuple("grizy") 

33 self.center = (27, 32) 

34 self.morph1 = integrated_circular_gaussian(sigma=0.8).astype(np.float32) 

35 self.spectrum1 = np.arange(5).astype(np.float32) 

36 self.component_box1 = Box((15, 15), (20, 25)) 

37 self.morph2 = integrated_circular_gaussian(sigma=2.1).astype(np.float32) 

38 self.spectrum2 = np.arange(5)[::-1].astype(np.float32) 

39 self.component_box2 = Box((15, 15), (10, 35)) 

40 self.components = [ 

41 FactorizedComponent( 

42 self.bands, 

43 self.spectrum1, 

44 self.morph1, 

45 self.component_box1, 

46 self.center, 

47 ), 

48 FactorizedComponent( 

49 self.bands, 

50 self.spectrum2, 

51 self.morph2, 

52 self.component_box2, 

53 self.center, 

54 ), 

55 ] 

56 

57 def test_empty_constructor(self): 

58 source = Source([]) 

59 

60 self.assertEqual(source.n_components, 0) 

61 self.assertIsNone(source.center) 

62 self.assertIsNone(source.source_center) 

63 self.assertTrue(source.is_null) 

64 self.assertBoxEqual(source.bbox, Box((0, 0))) 

65 self.assertTupleEqual(source.bands, ()) 

66 

67 def test_single_component_constructor(self): 

68 source = Source(self.components[:1]) 

69 self.assertEqual(source.n_components, 1) 

70 self.assertTupleEqual(source.center, self.center) 

71 self.assertTupleEqual(source.source_center, (7, 7)) 

72 self.assertFalse(source.is_null) 

73 self.assertBoxEqual(source.bbox, self.component_box1) 

74 self.assertTupleEqual(source.bands, self.bands) 

75 self.assertImageEqual( 

76 source.get_model(), 

77 Image( 

78 self.spectrum1[:, None, None] * self.morph1[None, :, :], 

79 yx0=self.component_box1.origin, 

80 bands=self.bands, 

81 ), 

82 ) 

83 self.assertIsNone(source.get_model(True)) 

84 self.assertEqual(source.get_model().dtype, np.float32) 

85 

86 def test_multiple_component_constructor(self): 

87 # Test a source with multiple components 

88 source = Source(self.components) 

89 self.assertEqual(source.n_components, 2) 

90 self.assertTupleEqual(source.center, self.center) 

91 self.assertTupleEqual(source.source_center, (17, 7)) 

92 self.assertFalse(source.is_null) 

93 self.assertBoxEqual(source.bbox, Box((25, 25), (10, 25))) 

94 self.assertTupleEqual(source.bands, self.bands) 

95 self.assertEqual(str(source), "Source<2>") 

96 self.assertEqual(source.get_model().dtype, np.float32) 

97 

98 model = np.zeros((5, 25, 25), dtype=np.float32) 

99 model[:, 10:25, :15] = self.spectrum1[:, None, None] * self.morph1[None, :, :] 

100 model[:, :15, 10:25] += self.spectrum2[:, None, None] * self.morph2[None, :, :] 

101 model = Image(model, yx0=(10, 25), bands=self.bands) 

102 

103 self.assertImageEqual( 

104 source.get_model(), 

105 model, 

106 ) 

107 self.assertIsNone(source.get_model(True)) 

108 

109 source = Source([]) 

110 result = source.get_model() 

111 self.assertEqual(result, 0) 

112 

113 def test_shallow_copy(self): 

114 source = Source(self.components) 

115 source_copy = source.copy() 

116 

117 self.assertIsNot(source, source_copy) 

118 self.assertEqual(source.n_components, 2) 

119 self.assertEqual(source.n_components, source_copy.n_components) 

120 self.assertFactorizedComponentEqual(source.components[0], source_copy.components[0]) 

121 self.assertFactorizedComponentEqual(source.components[1], source_copy.components[1]) 

122 self.assertIs(source.flux_weighted_image, source_copy.flux_weighted_image) 

123 self.assertIs(source.metadata, source_copy.metadata) 

124 

125 def test_deepcopy(self): 

126 source = Source(self.components) 

127 source_deepcopy = source.copy(deep=True) 

128 

129 self.assertIsNot(source, source_deepcopy) 

130 self.assertEqual(source.n_components, source_deepcopy.n_components) 

131 for comp, comp_deepcopy in zip(source.components, source_deepcopy.components): 

132 self.assertIsNot(comp, comp_deepcopy) 

133 self.assertFactorizedComponentEqual(comp, comp_deepcopy) 

134 comp_deepcopy._spectrum.x += 1 

135 with self.assertRaises(AssertionError): 

136 np.testing.assert_array_equal(comp._spectrum.x, comp_deepcopy._spectrum.x) 

137 comp_deepcopy._morph.x += 1 

138 with self.assertRaises(AssertionError): 

139 np.testing.assert_array_equal(comp._morph.x, comp_deepcopy._morph.x) 

140 

141 def test_slice(self): 

142 source = Source(self.components) 

143 source_sliced = source["g":"r"] 

144 self.assertTupleEqual(source_sliced.bands, ("g", "r")) 

145 self.assertEqual(source.n_components, source_sliced.n_components) 

146 

147 for comp, comp_sliced in zip(source.components, source_sliced.components): 

148 self.assertFactorizedComponentEqual(comp["g":"r"], comp_sliced) 

149 

150 def test_reorder(self): 

151 source = Source(self.components) 

152 indices = ("i", "g", "r") 

153 source_reordered = source[indices] 

154 self.assertTupleEqual(source_reordered.bands, indices) 

155 self.assertEqual(source.n_components, source_reordered.n_components) 

156 for comp, comp_reordered in zip(source.components, source_reordered.components): 

157 self.assertFactorizedComponentEqual(comp[indices], comp_reordered) 

158 

159 source_reordered = source["igr"] 

160 self.assertTupleEqual(source_reordered.bands, indices) 

161 self.assertEqual(source.n_components, source_reordered.n_components) 

162 for comp, comp_reordered in zip(source.components, source_reordered.components): 

163 self.assertFactorizedComponentEqual(comp["igr"], comp_reordered) 

164 

165 def test_subset(self): 

166 source = Source(self.components) 

167 source_subset = source[("r",)] 

168 self.assertTupleEqual(source_subset.bands, ("r",)) 

169 self.assertEqual(source.n_components, source_subset.n_components) 

170 for comp, comp_subset in zip(source.components, source_subset.components): 

171 self.assertFactorizedComponentEqual(comp["r"], comp_subset) 

172 

173 source = source.copy(deep=True) 

174 for comp in source.components: 

175 comp._bands = ("ab", "cd", "ef") 

176 source_subset = source["ab"] 

177 self.assertTupleEqual(source_subset.bands, ("ab",)) 

178 self.assertEqual(source.n_components, source_subset.n_components) 

179 for comp, comp_subset in zip(source.components, source_subset.components): 

180 self.assertFactorizedComponentEqual(comp["ab"], comp_subset) 

181 

182 def test_indexing_errors(self): 

183 source = Source(self.components) 

184 

185 with self.assertRaises(IndexError): 

186 source["x"] 

187 

188 with self.assertRaises(IndexError): 

189 source["r":"x"] 

190 

191 with self.assertRaises(IndexError): 

192 source["x":"i"] 

193 

194 with self.assertRaises(IndexError): 

195 source["g", "x", "i"] 

196 

197 with self.assertRaises(IndexError): 

198 source[Box((0, 0), (10, 10))] 

199 

200 with self.assertRaises(IndexError): 

201 source[:, 10:20, 10:20] 

202 

203 with self.assertRaises(IndexError): 

204 source[1:] 

205 

206 with self.assertRaises(IndexError): 

207 source[1] 

208 

209 with self.assertRaises(IndexError): 

210 source[0, 1]