Coverage for tests/test_psfResiduals.py: 25%
65 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-14 03:19 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-14 03:19 -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/>.
22import unittest
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, CalcShapeSize
29from lsst.pex.config import FieldValidationError
32class ShapeSizeTestCase(lsst.utils.tests.TestCase):
33 """Test ellipiticity and size calculations."""
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 )
48 cls.kwargs = {"band": "i"}
50 def test_size(self):
51 """Test CalcShapeSize functor"""
52 traceSize = CalcShapeSize(sizeType="trace")(self.data, **self.kwargs)
53 determinantSize = CalcShapeSize(sizeType="determinant")(self.data, **self.kwargs)
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])
63 def test_complex_shear(self):
64 """Test CalcE functor
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 = CalcShapeSize(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)
83 def test_halve_angle(self):
84 """Test ``halvePhaseAngle`` parameter in CalcE
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))
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 )
101 @lsst.utils.tests.methodParameters(ellipticityType=("distortion", "shear"))
102 def test_shear_components(self, ellipticityType):
103 """Test CalcE1 and CalcE2 functors
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)
112 self.assertFloatsAlmostEqual(np.real(ellip), e1)
113 self.assertFloatsAlmostEqual(np.imag(ellip), e2)
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()
121 def test_size_validation(self):
122 """Test that CalcShapeSize throws an exception when misconfigured."""
123 CalcShapeSize(sizeType="trace", colXy=None).validate()
124 with self.assertRaises(FieldValidationError):
125 CalcShapeSize(sizeType="determinant", colXy=None).validate()
127 def test_ediff_validation(self):
128 """Test that CalcEDiff takes ellipticities of same convention."""
129 ellipA = CalcE(ellipticityType="shear")
130 ellipB = CalcE(ellipticityType="distortion")
131 with self.assertRaises(FieldValidationError):
132 CalcEDiff(colA=ellipA, colB=ellipB).validate()
135if __name__ == "__main__": 135 ↛ 136line 135 didn't jump to line 136, because the condition on line 135 was never true
136 lsst.utils.tests.init()
137 unittest.main()