Coverage for tests/test_calcFunctors.py: 26%
64 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-23 02:50 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-23 02:50 -0700
1# This file is part of analysis_drp.
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 numpy as np
27import lsst.utils.tests
28from lsst.afw.geom import Quadrupole
29from lsst.analysis.drp.calcFunctors import CalcE, CalcE1, CalcE2, CalcEDiff, CalcShapeSize
30from lsst.pex.config import FieldValidationError
33class ShapeSizeTestCase(lsst.utils.tests.TestCase):
34 """Test ellipiticity and size calculations."""
36 @classmethod
37 def setUpClass(cls):
38 cls.data = np.array(
39 [
40 (1.3, 1.3, 0.0), # e1 = e2 = 0
41 (2.4, 1.2, 0.6), # e1 = e2 != 0
42 (1.0, 2.0, 0.0), # e1 < 0; e2 = 0
43 (3.5, 3.5, 0.5), # e1 = 0; e2 > 0
44 (3.0, 1.5, -1.2), # e1 > 0; e2 < 0
45 ],
46 dtype=[("ixx", "<f8"), ("iyy", "<f8"), ("ixy", "<f8")],
47 )
49 def test_size(self):
50 """Test CalcShapeSize functor"""
51 traceSize = CalcShapeSize(sizeType="trace")(self.data)
52 determinantSize = CalcShapeSize(sizeType="determinant")(self.data)
54 for idx, row in enumerate(self.data):
55 shape = Quadrupole(ixx=row["ixx"], iyy=row["iyy"], ixy=row["ixy"])
56 self.assertFloatsAlmostEqual(traceSize[idx], shape.getTraceRadius(), rtol=1e-8)
57 self.assertFloatsAlmostEqual(determinantSize[idx], shape.getDeterminantRadius(), rtol=1e-8)
58 # Arithmetic mean >= Geometric mean implies that
59 # trace radius is never smaller than determinant radius.
60 self.assertGreaterEqual(traceSize[idx], determinantSize[idx])
62 def test_complex_shear(self):
63 """Test CalcE functor
65 Test that our ellipticity calculation under the two conventions are
66 accurate by comparing with GalSim routines.
67 """
68 shear = CalcE(ellipticityType="epsilon")(self.data)
69 distortion = CalcE(ellipticityType="chi")(self.data)
70 size = CalcShapeSize(sizeType="determinant")(self.data)
71 for idx, row in enumerate(self.data):
72 galsim_shear = galsim.Shear(shear[idx])
73 self.assertFloatsAlmostEqual(distortion[idx].real, galsim_shear.e1)
74 self.assertFloatsAlmostEqual(distortion[idx].imag, galsim_shear.e2)
75 # Check that the ellipiticity values correspond to the moments.
76 A = galsim_shear.getMatrix() * size[idx]
77 M = np.dot(A.transpose(), A)
78 self.assertFloatsAlmostEqual(M[0, 0], row["ixx"], rtol=1e-8)
79 self.assertFloatsAlmostEqual(M[1, 1], row["iyy"], rtol=1e-8)
80 self.assertFloatsAlmostEqual(M[0, 1], row["ixy"], rtol=1e-8)
82 def test_halve_angle(self):
83 """Test ``halvePhaseAngle`` parameter in CalcE
85 Test that setting ``halvePhaseAngle`` to True halves the phase angle
86 while keeping the magnitude the same.
87 """
88 ellip = CalcE(ellipticityType="epsilon")(self.data)
89 ellip_half = CalcE(ellipticityType="epsilon", halvePhaseAngle=True)(self.data)
90 self.assertFloatsAlmostEqual(np.abs(ellip), np.abs(ellip_half))
92 for idx, row in enumerate(self.data):
93 galsim_shear = galsim.Shear(ellip[idx])
94 galsim_shear_half = galsim.Shear(ellip_half[idx])
95 self.assertFloatsAlmostEqual(np.abs(ellip_half[idx]), galsim_shear.g)
96 self.assertFloatsAlmostEqual(
97 galsim_shear.beta / galsim.radians, 2 * galsim_shear_half.beta / galsim.radians
98 )
100 @lsst.utils.tests.methodParameters(ellipticityType=("chi", "epsilon"))
101 def test_shear_components(self, ellipticityType):
102 """Test CalcE1 and CalcE2 functors
104 This test checks if CalcE1 and CalcE2 correspond to the real and
105 imaginary components of CalcE.
106 """
107 ellip = CalcE(ellipticityType=ellipticityType)(self.data)
108 e1 = CalcE1(ellipticityType=ellipticityType)(self.data)
109 e2 = CalcE2(ellipticityType=ellipticityType)(self.data)
111 self.assertFloatsAlmostEqual(np.real(ellip), e1)
112 self.assertFloatsAlmostEqual(np.imag(ellip), e2)
114 def test_e1_validation(self):
115 """Test that CalcE1 throws an exception when misconfigured."""
116 CalcE1(ellipticityType="chi", colXy=None).validate()
117 with self.assertRaises(FieldValidationError):
118 CalcE1(ellipticityType="epsilon", colXy=None).validate()
120 def test_size_validation(self):
121 """Test that CalcShapeSize throws an exception when misconfigured."""
122 CalcShapeSize(sizeType="trace", colXy=None).validate()
123 with self.assertRaises(FieldValidationError):
124 CalcShapeSize(sizeType="determinant", colXy=None).validate()
126 def test_ediff_validation(self):
127 """Test that CalcEDiff takes ellipticities of same convention."""
128 ellipA = CalcE(ellipticityType="epsilon")
129 ellipB = CalcE(ellipticityType="chi")
130 with self.assertRaises(FieldValidationError):
131 CalcEDiff(colA=ellipA, colB=ellipB).validate()
134if __name__ == "__main__": 134 ↛ 135line 134 didn't jump to line 135, because the condition on line 134 was never true
135 lsst.utils.tests.init()
136 unittest.main()