Coverage for tests/test_profiles.py: 27%

75 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-09 03:23 -0800

1# 

2# LSST Data Management System 

3# Copyright 2008-2017 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

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

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

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

11# (at your option) any later version. 

12# 

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

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

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

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24import os 

25 

26import numpy as np 

27 

28import lsst.utils.tests 

29import lsst.geom 

30import lsst.afw.geom.ellipses as el 

31import lsst.shapelet.tractor 

32import lsst.shapelet.tests 

33import lsst.afw.image 

34 

35# These parameters match those used to generate the check images; see 

36# tests/data/generate.py 

37GALAXY_RADIUS = 8.0 

38PSF_SIGMA = 2.0 

39E1 = 0.3 

40E2 = -0.2 

41PROFILES = [ 

42 ("exp", 9, 8), 

43 ("dev", 9, 8), 

44] 

45 

46CHECK_COMPONENT_IMAGES = False 

47 

48 

49class ProfileTestCase(lsst.shapelet.tests.ShapeletTestCase): 

50 

51 def testRadii(self): 

52 """Check RadialProfile definitions of moments and half-light radii. 

53 """ 

54 s = np.linspace(-20.0, 20.0, 1000) 

55 x, y = np.meshgrid(s, s) 

56 r = (x**2 + y**2)**0.5 

57 dxdy = (s[1] - s[0])**2 

58 for name in ["gaussian", "exp", "ser2", "luv", "lux"]: 

59 profile = lsst.shapelet.RadialProfile.get(name) 

60 z = profile.evaluate(r) * dxdy 

61 # lux and luv don't use the true half-light radius; instead they use the half-light radius 

62 # of the exp and dev profiles they approximate 

63 if not name.startswith("lu"): 

64 self.assertFloatsAlmostEqual(z[r < 1].sum(), 0.5*z.sum(), rtol=0.01) 

65 # lhs of this comparison is the moments radius (using a sum approximation to the integral) 

66 self.assertFloatsAlmostEqual(((z*x**2).sum() / z.sum())**0.5, 

67 profile.getMomentsRadiusFactor(), rtol=0.01) 

68 

69 def testGaussian(self): 

70 """Test that the Gaussian profile's shapelet 'approximation' is actually exact. 

71 """ 

72 profile = lsst.shapelet.RadialProfile.get("gaussian") 

73 r = np.linspace(0.0, 4.0, 100) 

74 z1 = profile.evaluate(r) 

75 basis = profile.getBasis(1) 

76 z2 = lsst.shapelet.tractor.evaluateRadial(basis, r, sbNormalize=True)[0, :] 

77 self.assertFloatsAlmostEqual(z1, z2, rtol=1E-8) 

78 

79 def testShapeletApproximations(self): 

80 psf0 = lsst.shapelet.ShapeletFunction(0, lsst.shapelet.HERMITE, PSF_SIGMA) 

81 psf0.getCoefficients()[:] = 1.0 / lsst.shapelet.ShapeletFunction.FLUX_FACTOR 

82 psf = lsst.shapelet.MultiShapeletFunction() 

83 psf.addComponent(psf0) 

84 psf.normalize() 

85 ellipse = el.Separable[el.Distortion, el.DeterminantRadius](E1, E2, GALAXY_RADIUS) 

86 for name, nComponents, maxRadius in PROFILES: 

87 # check1 is the multi-Gaussian approximation, as convolved and evaluated by GalSim, 

88 check1 = lsst.afw.image.ImageD(os.path.join("tests", "data", name + "-approx.fits")).getArray() 

89 xc = check1.shape[1] // 2 

90 yc = check1.shape[0] // 2 

91 xb = np.arange(check1.shape[1], dtype=float) - xc 

92 yb = np.arange(check1.shape[0], dtype=float) - yc 

93 xg, yg = np.meshgrid(xb, yb) 

94 

95 basis = lsst.shapelet.RadialProfile.get(name).getBasis(nComponents, maxRadius) 

96 builder = lsst.shapelet.MatrixBuilderD.Factory(xg.ravel(), yg.ravel(), basis, psf)() 

97 image1 = np.zeros(check1.shape, dtype=float) 

98 matrix = image1.reshape(check1.size, 1) 

99 builder(matrix, el.Ellipse(ellipse)) 

100 self.assertFloatsAlmostEqual(check1, image1, plotOnFailure=False, rtol=5E-5, relTo=check1.max()) 

101 msf = basis.makeFunction(el.Ellipse(ellipse, lsst.geom.Point2D(xc, yc)), 

102 np.array([1.0], dtype=float)) 

103 msf = msf.convolve(psf) 

104 image2 = np.zeros(check1.shape, dtype=float) 

105 msf.evaluate().addToImage(lsst.afw.image.ImageD(image2, False)) 

106 self.assertFloatsAlmostEqual(check1, image2, plotOnFailure=False, rtol=5E-5, relTo=check1.max()) 

107 

108 if name == 'exp': 

109 # check2 is the exact profile, again by GalSim. 

110 # We only check exp against the exact profile. The other approximations are less 

111 # accurate, and we only really need to test one. The real measure of whether these 

112 # profiles are good enough is more complicated than what we can do in a unit test. 

113 check2 = lsst.afw.image.ImageD( 

114 os.path.join("tests", "data", name + "-exact.fits") 

115 ).getArray() 

116 self.assertFloatsAlmostEqual(check2, image1, plotOnFailure=False, 

117 rtol=1E-3, relTo=check2.max()) 

118 

119 if CHECK_COMPONENT_IMAGES: 

120 # This was once useful for debugging test failures, and may be again, but it's 

121 # redundant with the above and requires putting more check images in git, so 

122 # it's disabled by default. 

123 for n, sf in enumerate(msf.getComponents()): 

124 check = lsst.afw.image.ImageD( 

125 os.path.join("tests", "data", "%s-approx-%0d.fits" % (name, n)) 

126 ).getArray() 

127 image = np.zeros(check1.shape, dtype=float) 

128 sf.evaluate().addToImage(lsst.afw.image.ImageD(image, False)) 

129 self.assertFloatsAlmostEqual(check, image, plotOnFailure=False, 

130 rtol=5E-5, relTo=check1.max()) 

131 

132 

133class MemoryTester(lsst.utils.tests.MemoryTestCase): 

134 pass 

135 

136 

137def setup_module(module): 

138 lsst.utils.tests.init() 

139 

140 

141if __name__ == "__main__": 141 ↛ 142line 141 didn't jump to line 142, because the condition on line 141 was never true

142 lsst.utils.tests.init() 

143 unittest.main()