Coverage for tests / test_psfs.py: 18%

79 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-01 08:36 +0000

1# This file is part of lsst-images. 

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# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12from __future__ import annotations 

13 

14import os 

15import unittest 

16 

17import numpy as np 

18 

19from lsst.images import Box 

20from lsst.images.psfs import ( 

21 GaussianPointSpreadFunction, 

22 PiffWrapper, 

23 PointSpreadFunction, 

24 PSFExWrapper, 

25) 

26from lsst.images.tests import RoundtripFits, RoundtripJson, compare_psf_to_legacy 

27 

28DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None) 

29 

30 

31class PointSpreadFunctionTestCase(unittest.TestCase): 

32 """Tests for the PointSpreadFunction classes.""" 

33 

34 def test_gaussian(self) -> None: 

35 """Test the built-in Gaussian PSF implementation.""" 

36 bounds = Box.factory[-1024:1024, -2048:2048] 

37 psf = GaussianPointSpreadFunction(2.5, bounds=bounds, stamp_size=33) 

38 self.assertEqual(psf.bounds, bounds) 

39 

40 kernel = psf.compute_kernel_image(x=5.0, y=3.0) 

41 self.assertEqual(kernel.bbox, psf.kernel_bbox) 

42 self.assertAlmostEqual(float(kernel.array.sum()), 1.0) 

43 center = kernel.array.shape[0] // 2 

44 self.assertEqual(np.unravel_index(np.argmax(kernel.array), kernel.array.shape), (center, center)) 

45 

46 stellar = psf.compute_stellar_image(x=5.25, y=3.75) 

47 self.assertEqual(stellar.bbox, psf.compute_stellar_bbox(x=5.25, y=3.75)) 

48 self.assertAlmostEqual(float(stellar.array.sum()), 1.0) 

49 self.assertGreater(stellar.array[center - 1, center], stellar.array[center + 1, center]) 

50 self.assertGreater(stellar.array[center, center], stellar.array[center, center - 1]) 

51 self.assertGreater(stellar.array[center, center], stellar.array[center - 1, center]) 

52 

53 with RoundtripFits(self, psf) as roundtrip: 

54 self.assertEqual(roundtrip.result, psf, f"{roundtrip.result} != {psf}") 

55 

56 with self.assertRaises(ValueError): 

57 # Even stamp size. 

58 GaussianPointSpreadFunction(2.5, bounds=bounds, stamp_size=32) 

59 

60 with self.assertRaises(ValueError): 

61 # Negative stamp size. 

62 GaussianPointSpreadFunction(2.5, bounds=bounds, stamp_size=-33) 

63 

64 with self.assertRaises(ValueError): 

65 # Negative sigma. 

66 GaussianPointSpreadFunction(-2.5, bounds=bounds, stamp_size=33) 

67 

68 @unittest.skipUnless(DATA_DIR is not None, "TESTDATA_IMAGES_DIR is not in the environment.") 

69 def test_piff(self) -> None: 

70 """Test that we can: 

71 

72 - read a legacy Piff PSF with afw; 

73 - convert it to the new `PiffWrapper` class; 

74 - get consistent behavior from the two; 

75 - round-trip the new PSF through a FITS archive; 

76 - still get consistent behavior with the round-tripped PSF. 

77 

78 This test is skipped if legacy modules cannot be imported. 

79 """ 

80 try: 

81 from piff import PSF 

82 

83 from lsst.afw.image import ExposureFitsReader 

84 except ImportError: 

85 raise unittest.SkipTest("'lsst.afw.image' could not be imported.") from None 

86 assert DATA_DIR is not None, "Guaranteed by decorator." 

87 filename = os.path.join(DATA_DIR, "dp2", "legacy", "visit_image.fits") 

88 reader = ExposureFitsReader(filename) 

89 legacy_psf = reader.readPsf() 

90 bounds = Box.from_legacy(reader.readBBox()) 

91 psf = PointSpreadFunction.from_legacy(legacy_psf, bounds) 

92 self.assertIsInstance(psf, PiffWrapper) 

93 self.assertEqual(psf.bounds, bounds) 

94 self.assertIsInstance(psf.piff_psf, PSF) 

95 compare_psf_to_legacy(self, psf, legacy_psf) 

96 with RoundtripFits(self, psf) as roundtrip1: 

97 pass 

98 compare_psf_to_legacy(self, roundtrip1.result, legacy_psf) 

99 with RoundtripJson(self, psf) as roundtrip2: 

100 pass 

101 compare_psf_to_legacy(self, roundtrip2.result, legacy_psf) 

102 

103 @unittest.skipUnless(DATA_DIR is not None, "TESTDATA_IMAGES_DIR is not in the environment.") 

104 def test_psfex(self) -> None: 

105 """Test that we can: 

106 

107 - read a legacy PSFEX PSF with afw; 

108 - wrap it inthe new `LegacyPointSpreadFunction` class; 

109 - get consistent behavior from the two. 

110 

111 This test is skipped if legacy modules cannot be imported. 

112 """ 

113 try: 

114 from lsst.afw.image import ExposureFitsReader 

115 from lsst.meas.extensions.psfex import PsfexPsf 

116 except ImportError: 

117 raise unittest.SkipTest("'lsst.afw.image' could not be imported.") from None 

118 assert DATA_DIR is not None, "Guaranteed by decorator." 

119 filename = os.path.join(DATA_DIR, "dp2", "legacy", "preliminary_visit_image.fits") 

120 reader = ExposureFitsReader(filename) 

121 legacy_psf = reader.readPsf() 

122 bounds = Box.from_legacy(reader.readBBox()) 

123 psf = PointSpreadFunction.from_legacy(legacy_psf, bounds) 

124 self.assertIsInstance(psf, PSFExWrapper) 

125 self.assertEqual(psf.bounds, bounds) 

126 self.assertIsInstance(psf.legacy_psf, PsfexPsf) 

127 compare_psf_to_legacy(self, psf, legacy_psf) 

128 compare_psf_to_legacy(self, psf, legacy_psf) 

129 with RoundtripFits(self, psf) as roundtrip1: 

130 pass 

131 compare_psf_to_legacy(self, roundtrip1.result, legacy_psf) 

132 with RoundtripJson(self, psf) as roundtrip2: 

133 pass 

134 compare_psf_to_legacy(self, roundtrip2.result, legacy_psf) 

135 

136 

137if __name__ == "__main__": 

138 unittest.main()