Coverage for tests / test_extended_psf_candidates.py: 22%
71 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:40 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:40 +0000
1# This file is part of pipe_tasks.
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 unittest
23from typing import cast
25import astropy.units as u
26import lsst.images.tests
27import lsst.utils.tests
28import numpy as np
29from lsst.images import Image, Mask, MaskedImage, MaskSchema
30from lsst.pipe.tasks.extended_psf import ExtendedPsfCandidate, ExtendedPsfCandidateInfo, ExtendedPsfCandidates
33class ExtendedPsfCandidatesTestCase(lsst.utils.tests.TestCase):
34 """Test ExtendedPsfCandidates."""
36 def setUp(self):
37 rng = np.random.Generator(np.random.MT19937(seed=5))
38 cutout_size = (25, 25)
40 # Generate simulated stars
41 extended_psf_candidates = []
42 self.star_infos = []
43 mask_schema = MaskSchema([])
45 for i in range(3):
46 candidate_masked_image = MaskedImage(
47 image=Image(rng.random(cutout_size).astype(np.float32)),
48 mask=Mask(0, schema=mask_schema, shape=cutout_size),
49 variance=Image(rng.random(cutout_size).astype(np.float32)),
50 )
52 star_info = ExtendedPsfCandidateInfo(
53 visit=100 + i,
54 detector=200 + i,
55 ref_id=1000 + i,
56 ref_mag=10.0 + i,
57 position_x=float(rng.random()),
58 position_y=float(rng.random()),
59 focal_plane_radius=float(rng.random()) * u.mm,
60 focal_plane_angle=float(rng.random()) * u.rad,
61 )
62 self.star_infos.append(star_info)
64 # Build a normalized 2-D Gaussian kernel image directly.
65 yy, xx = np.mgrid[: cutout_size[0], : cutout_size[1]]
66 cy = (cutout_size[0] - 1) / 2.0
67 cx = (cutout_size[1] - 1) / 2.0
68 sigma = 1.5
69 kernel_array = np.exp(-((yy - cy) ** 2 + (xx - cx) ** 2) / (2.0 * sigma**2))
70 kernel_array /= np.sum(kernel_array)
71 psf_kernel_image = Image(kernel_array.astype(np.float64))
73 extended_psf_candidates.append(
74 ExtendedPsfCandidate(
75 image=candidate_masked_image.image,
76 mask=candidate_masked_image.mask,
77 variance=candidate_masked_image.variance,
78 psf_kernel_image=psf_kernel_image,
79 star_info=star_info,
80 )
81 )
83 self.global_metadata = {"TEST_KEY": "TEST VALUE"}
84 self.extended_psf_candidates = ExtendedPsfCandidates(extended_psf_candidates, self.global_metadata)
86 def tearDown(self):
87 del self.extended_psf_candidates
88 del self.star_infos
89 del self.global_metadata
91 def testExtendedPsfCandidates(self):
92 """Test that ExtendedPsfCandidates can be serialized/deserialized."""
94 with lsst.images.tests.RoundtripFits(
95 self, self.extended_psf_candidates, storage_class="ExtendedPsfCandidates"
96 ) as roundtrip:
97 pass
98 extended_psf_candidates = roundtrip.result
100 global_metadata = extended_psf_candidates.metadata
101 self.assertEqual(self.global_metadata["TEST_KEY"], global_metadata["TEST_KEY"])
102 self.assertEqual(len(self.extended_psf_candidates), len(extended_psf_candidates))
103 for input_info, input_candidate, output_candidate in zip(
104 self.star_infos, self.extended_psf_candidates, extended_psf_candidates
105 ):
106 self.assertEqual(input_candidate.metadata, {})
107 self.assertEqual(output_candidate.metadata, {})
109 output_info = output_candidate.star_info
110 self.assertEqual(input_info.visit, output_info.visit)
111 self.assertEqual(input_info.detector, output_info.detector)
112 self.assertEqual(input_info.ref_id, output_info.ref_id)
113 self.assertAlmostEqual(input_info.ref_mag, output_info.ref_mag, places=10)
114 self.assertAlmostEqual(input_info.position_x, output_info.position_x, places=10)
115 self.assertAlmostEqual(input_info.position_y, output_info.position_y, places=10)
117 self.assertIsNotNone(input_info.focal_plane_radius)
118 self.assertIsNotNone(output_info.focal_plane_radius)
119 input_radius = cast(u.Quantity, input_info.focal_plane_radius)
120 output_radius = cast(u.Quantity, output_info.focal_plane_radius)
121 self.assertAlmostEqual(input_radius.to_value(u.mm), output_radius.to_value(u.mm), places=10)
123 self.assertIsNotNone(input_info.focal_plane_angle)
124 self.assertIsNotNone(output_info.focal_plane_angle)
125 input_angle = cast(u.Quantity, input_info.focal_plane_angle)
126 output_angle = cast(u.Quantity, output_info.focal_plane_angle)
127 self.assertAlmostEqual(input_angle.to_value(u.rad), output_angle.to_value(u.rad), places=10)
129 np.testing.assert_allclose(
130 input_candidate.image.array, output_candidate.image.array, rtol=0.0, atol=1e-10
131 )
132 np.testing.assert_array_equal(input_candidate.mask.array, output_candidate.mask.array)
133 np.testing.assert_allclose(
134 input_candidate.variance.array, output_candidate.variance.array, rtol=0.0, atol=1e-10
135 )
136 np.testing.assert_allclose(
137 input_candidate.psf_kernel_image.array,
138 output_candidate.psf_kernel_image.array,
139 rtol=0.0,
140 atol=1e-10,
141 )
144class MemoryTester(lsst.utils.tests.MemoryTestCase):
145 pass
148def setup_module(module):
149 lsst.utils.tests.init()
152if __name__ == "__main__": 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 lsst.utils.tests.init()
154 unittest.main()