Coverage for tests / test_source.py: 11%
134 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:40 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 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/>.
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
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 ]
57 def test_empty_constructor(self):
58 source = Source([])
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, ())
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)
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)
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)
103 self.assertImageEqual(
104 source.get_model(),
105 model,
106 )
107 self.assertIsNone(source.get_model(True))
109 source = Source([])
110 result = source.get_model()
111 self.assertEqual(result, 0)
113 def test_shallow_copy(self):
114 source = Source(self.components)
115 source_copy = source.copy()
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)
125 def test_deepcopy(self):
126 source = Source(self.components)
127 source_deepcopy = source.copy(deep=True)
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)
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)
147 for comp, comp_sliced in zip(source.components, source_sliced.components):
148 self.assertFactorizedComponentEqual(comp["g":"r"], comp_sliced)
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)
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)
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)
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)
182 def test_indexing_errors(self):
183 source = Source(self.components)
185 with self.assertRaises(IndexError):
186 source["x"]
188 with self.assertRaises(IndexError):
189 source["r":"x"]
191 with self.assertRaises(IndexError):
192 source["x":"i"]
194 with self.assertRaises(IndexError):
195 source["g", "x", "i"]
197 with self.assertRaises(IndexError):
198 source[Box((0, 0), (10, 10))]
200 with self.assertRaises(IndexError):
201 source[:, 10:20, 10:20]
203 with self.assertRaises(IndexError):
204 source[1:]
206 with self.assertRaises(IndexError):
207 source[1]
209 with self.assertRaises(IndexError):
210 source[0, 1]