Coverage for tests/test_psfResiduals.py: 30%

61 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-28 04:18 -0700

1# This file is part of analysis_tools. 

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 unittest 

23 

24import galsim 

25import lsst.utils.tests 

26import numpy as np 

27from lsst.afw.geom import Quadrupole 

28from lsst.analysis.tools.actions.vector import CalcE, CalcE1, CalcE2, CalcEDiff, CalcMomentSize 

29from lsst.pex.config import FieldValidationError 

30 

31 

32class ShapeSizeTestCase(lsst.utils.tests.TestCase): 

33 """Test ellipiticity and size calculations.""" 

34 

35 @classmethod 

36 def setUpClass(cls): 

37 cls.data = np.array( 

38 [ 

39 (1.3, 1.3, 0.0), # e1 = e2 = 0 

40 (2.4, 1.2, 0.6), # e1 = e2 != 0 

41 (1.0, 2.0, 0.0), # e1 < 0; e2 = 0 

42 (3.5, 3.5, 0.5), # e1 = 0; e2 > 0 

43 (3.0, 1.5, -1.2), # e1 > 0; e2 < 0 

44 ], 

45 dtype=[("i_ixx", "<f8"), ("i_iyy", "<f8"), ("i_ixy", "<f8")], 

46 ) 

47 

48 cls.kwargs = {"band": "i"} 

49 

50 def test_size(self): 

51 """Test CalcMomentSize functor""" 

52 traceSize = CalcMomentSize(sizeType="trace")(self.data, **self.kwargs) 

53 determinantSize = CalcMomentSize(sizeType="determinant")(self.data, **self.kwargs) 

54 

55 for idx, row in enumerate(self.data): 

56 shape = Quadrupole(ixx=row["i_ixx"], iyy=row["i_iyy"], ixy=row["i_ixy"]) 

57 self.assertFloatsAlmostEqual(traceSize[idx], shape.getTraceRadius(), rtol=1e-8) 

58 self.assertFloatsAlmostEqual(determinantSize[idx], shape.getDeterminantRadius(), rtol=1e-8) 

59 # Arithmetic mean >= Geometric mean implies that 

60 # trace radius is never smaller than determinant radius. 

61 self.assertGreaterEqual(traceSize[idx], determinantSize[idx]) 

62 

63 def test_complex_shear(self): 

64 """Test CalcE functor 

65 

66 Test that our ellipticity calculation under the two conventions are 

67 accurate by comparing with GalSim routines. 

68 """ 

69 shear = CalcE(ellipticityType="shear")(self.data, **self.kwargs) 

70 distortion = CalcE(ellipticityType="distortion")(self.data, **self.kwargs) 

71 size = CalcMomentSize(sizeType="determinant")(self.data, **self.kwargs) 

72 for idx, row in enumerate(self.data): 

73 galsim_shear = galsim.Shear(shear[idx]) 

74 self.assertFloatsAlmostEqual(distortion[idx].real, galsim_shear.e1) 

75 self.assertFloatsAlmostEqual(distortion[idx].imag, galsim_shear.e2) 

76 # Check that the ellipiticity values correspond to the moments. 

77 A = galsim_shear.getMatrix() * size[idx] 

78 M = np.dot(A.transpose(), A) 

79 self.assertFloatsAlmostEqual(M[0, 0], row["i_ixx"], rtol=1e-8) 

80 self.assertFloatsAlmostEqual(M[1, 1], row["i_iyy"], rtol=1e-8) 

81 self.assertFloatsAlmostEqual(M[0, 1], row["i_ixy"], rtol=1e-8) 

82 

83 def test_halve_angle(self): 

84 """Test ``halvePhaseAngle`` parameter in CalcE 

85 

86 Test that setting ``halvePhaseAngle`` to True halves the phase angle 

87 while keeping the magnitude the same. 

88 """ 

89 ellip = CalcE(ellipticityType="shear")(self.data, **self.kwargs) 

90 ellip_half = CalcE(ellipticityType="shear", halvePhaseAngle=True)(self.data, **self.kwargs) 

91 self.assertFloatsAlmostEqual(np.abs(ellip), np.abs(ellip_half)) 

92 

93 for idx, row in enumerate(self.data): 

94 galsim_shear = galsim.Shear(ellip[idx]) 

95 galsim_shear_half = galsim.Shear(ellip_half[idx]) 

96 self.assertFloatsAlmostEqual(np.abs(ellip_half[idx]), galsim_shear.g) 

97 self.assertFloatsAlmostEqual( 

98 galsim_shear.beta / galsim.radians, 2 * galsim_shear_half.beta / galsim.radians 

99 ) 

100 

101 @lsst.utils.tests.methodParameters(ellipticityType=("distortion", "shear")) 

102 def test_shear_components(self, ellipticityType): 

103 """Test CalcE1 and CalcE2 functors 

104 

105 This test checks if CalcE1 and CalcE2 correspond to the real and 

106 imaginary components of CalcE. 

107 """ 

108 ellip = CalcE(ellipticityType=ellipticityType)(self.data, **self.kwargs) 

109 e1 = CalcE1(ellipticityType=ellipticityType)(self.data, **self.kwargs) 

110 e2 = CalcE2(ellipticityType=ellipticityType)(self.data, **self.kwargs) 

111 

112 self.assertFloatsAlmostEqual(np.real(ellip), e1) 

113 self.assertFloatsAlmostEqual(np.imag(ellip), e2) 

114 

115 def test_e1_validation(self): 

116 """Test that CalcE1 throws an exception when misconfigured.""" 

117 CalcE1(ellipticityType="distortion", colXy=None).validate() 

118 with self.assertRaises(FieldValidationError): 

119 CalcE1(ellipticityType="shear", colXy=None).validate() 

120 

121 def test_ediff_validation(self): 

122 """Test that CalcEDiff takes ellipticities of same convention.""" 

123 ellipA = CalcE(ellipticityType="shear") 

124 ellipB = CalcE(ellipticityType="distortion") 

125 with self.assertRaises(FieldValidationError): 

126 CalcEDiff(colA=ellipA, colB=ellipB).validate() 

127 

128 

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

130 lsst.utils.tests.init() 

131 unittest.main()