Coverage for python/lsst/analysis/tools/actions/vector/ellipticity.py: 39%
87 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 10:58 +0000
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-15 10:58 +0000
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/>.
21from __future__ import annotations
23__all__ = (
24 "CalcE",
25 "CalcEDiff",
26 "CalcE1",
27 "CalcE2",
28)
30import numpy as np
31from lsst.pex.config import ChoiceField, Field, FieldValidationError
32from lsst.pipe.tasks.configurableActions import ConfigurableActionField
34from ...interfaces import KeyedData, KeyedDataSchema, Vector, VectorAction
37class CalcE(VectorAction):
38 """Calculate a complex value representation of the ellipticity.
40 The complex ellipticity is typically defined as
41 e = |e|exp(j*2*theta) = ((Ixx - Iyy) + j*(2*Ixy))/(Ixx + Iyy), where j is
42 the square root of -1 and Ixx, Iyy, Ixy are second-order central moments.
43 This is sometimes referred to as distortion, and denoted by e = (e1, e2)
44 in GalSim and referred to as chi-type ellipticity following the notation
45 in Eq. 4.4 of Bartelmann and Schneider (2001). The other definition differs
46 in normalization. It is referred to as shear, and denoted by g = (g1, g2)
47 in GalSim and referred to as epsilon-type ellipticity again following the
48 notation in Eq. 4.10 of Bartelmann and Schneider (2001). It is defined as
49 g = ((Ixx - Iyy) + j*(2*Ixy))/(Ixx + Iyy + 2sqrt(Ixx*Iyy - Ixy**2)).
51 The shear measure is unbiased in weak-lensing shear, but may exclude some
52 objects in the presence of noisy moment estimates. The distortion measure
53 is biased in weak-lensing distortion, but does not suffer from selection
54 artifacts.
56 References
57 ----------
58 [1] Bartelmann, M. and Schneider, P., “Weak gravitational lensing”,
59 Physics Reports, vol. 340, no. 4–5, pp. 291–472, 2001.
60 doi:10.1016/S0370-1573(00)00082-X; https://arxiv.org/abs/astro-ph/9912508
62 Notes
63 -----
65 1. This is a shape measurement used for doing QA on the ellipticity
66 of the sources.
68 2. For plotting purposes we might want to plot |E|*exp(i*theta).
69 If `halvePhaseAngle` config parameter is set to `True`, then
70 the returned quantity therefore corresponds to |E|*exp(i*theta).
72 See Also
73 --------
74 CalcE1
75 CalcE2
76 """
78 colXx = Field[str](
79 doc="The column name to get the xx shape component from.",
80 default="{band}_ixx",
81 )
83 colYy = Field[str](
84 doc="The column name to get the yy shape component from.",
85 default="{band}_iyy",
86 )
88 colXy = Field[str](
89 doc="The column name to get the xy shape component from.",
90 default="{band}_ixy",
91 )
93 ellipticityType = ChoiceField[str](
94 doc="The type of ellipticity to calculate",
95 allowed={
96 "chi": ("Distortion, defined as (Ixx - Iyy + 2j*Ixy)/" "(Ixx + Iyy)"),
97 "epsilon": ("Shear, defined as (Ixx - Iyy + 2j*Ixy)/" "(Ixx + Iyy + 2*sqrt(Ixx*Iyy - Ixy**2))"),
98 },
99 default="chi",
100 )
102 halvePhaseAngle = Field[bool](
103 doc="Divide the phase angle by 2? Suitable for quiver plots.",
104 default=False,
105 )
107 component = ChoiceField[str](
108 doc="Which component of the ellipticity to return. If `None`, return complex ellipticity values.",
109 optional=True,
110 allowed={
111 "1": "e1 or g1 (depending on `ellipticityType`)",
112 "2": "e2 or g2 (depending on `ellipticityType`)",
113 },
114 )
116 def getInputSchema(self) -> KeyedDataSchema:
117 return ((self.colXx, Vector), (self.colXy, Vector), (self.colYy, Vector))
119 def __call__(self, data: KeyedData, **kwargs) -> Vector:
120 e = (data[self.colXx.format(**kwargs)] - data[self.colYy.format(**kwargs)]) + 1j * (
121 2 * data[self.colXy.format(**kwargs)]
122 )
123 denom = data[self.colXx.format(**kwargs)] + data[self.colYy.format(**kwargs)]
125 if self.ellipticityType == "epsilon":
126 denom += 2 * np.sqrt(
127 data[self.colXx.format(**kwargs)] * data[self.colYy.format(**kwargs)]
128 - data[self.colXy.format(**kwargs)] ** 2
129 )
131 e /= denom
133 if self.halvePhaseAngle:
134 # Ellipiticity is |e|*exp(i*2*theta), but we want to return
135 # |e|*exp(i*theta). So we multiply by |e| and take its square root
136 # instead of the more expensive trig calls.
137 e *= np.abs(e)
138 e = np.sqrt(e)
140 if self.component == "1":
141 return np.real(e)
142 elif self.component == "2":
143 return np.imag(e)
144 else:
145 return e
148class CalcEDiff(VectorAction):
149 """Calculate the difference of two ellipticities as a complex quantity.
151 The complex ellipticity difference between e_A and e_B is defined as
152 e_A - e_B = de = |de|exp(j*2*theta).
154 See Also
155 --------
156 CalcE
158 Notes
159 -----
161 1. This is a shape measurement used for doing QA on the ellipticity
162 of the sources.
164 2. For plotting purposes we might want to plot |de|*exp(j*theta).
165 If `halvePhaseAngle` config parameter is set to `True`, then
166 the returned quantity therefore corresponds to |e|*exp(j*theta).
167 """
169 colA = ConfigurableActionField(
170 doc="Ellipticity to subtract from",
171 dtype=VectorAction,
172 default=CalcE,
173 )
175 colB = ConfigurableActionField(
176 doc="Ellipticity to subtract",
177 dtype=VectorAction,
178 default=CalcE,
179 )
181 halvePhaseAngle = Field[bool](
182 doc="Divide the phase angle by 2? Suitable for quiver plots.",
183 default=False,
184 )
186 component = ChoiceField[str](
187 doc="Which component of the ellipticity to return. If `None`, return complex ellipticity values.",
188 optional=True,
189 allowed={
190 "1": "e1 or g1 (depending on the `ellipiticyType`)",
191 "2": "e2 or g2 (depending on the `ellipiticyType`)",
192 },
193 )
195 def getInputSchema(self) -> KeyedDataSchema:
196 yield from self.colA.getInputSchema()
197 yield from self.colB.getInputSchema()
199 def validate(self):
200 super().validate()
201 if self.colA.ellipticityType != self.colB.ellipticityType:
202 msg = "Both the ellipticities in CalcEDiff must have the same type."
203 raise FieldValidationError(self.colB.__class__.ellipticityType, self, msg)
205 def __call__(self, data: KeyedData, **kwargs) -> Vector:
206 eMeas = self.colA(data, **kwargs)
207 ePSF = self.colB(data, **kwargs)
208 eDiff = eMeas - ePSF
209 if self.halvePhaseAngle:
210 # Ellipiticity is |e|*exp(i*2*theta), but we want to return
211 # |e|*exp(j*theta). So we multiply by |e| and take its square root
212 # instead of the more expensive trig calls.
213 eDiff *= np.abs(eDiff)
214 eDiff = np.sqrt(eDiff)
216 if self.component == "1":
217 return np.real(eDiff)
218 elif self.component == "2":
219 return np.imag(eDiff)
220 else:
221 return eDiff
224class CalcE1(VectorAction):
225 """Calculate chi-type e1 = (Ixx - Iyy)/(Ixx + Iyy) or
226 epsilon-type g1 = (Ixx - Iyy)/(Ixx + Iyy + 2sqrt(Ixx*Iyy - Ixy**2)).
228 See Also
229 --------
230 CalcE
231 CalcE2
233 Note
234 ----
235 This is a shape measurement used for doing QA on the ellipticity
236 of the sources.
237 """
239 colXx = Field[str](
240 doc="The column name to get the xx shape component from.",
241 default="{band}_ixx",
242 )
244 colYy = Field[str](
245 doc="The column name to get the yy shape component from.",
246 default="{band}_iyy",
247 )
249 colXy = Field[str](
250 doc="The column name to get the xy shape component from.",
251 default="{band}_ixy",
252 optional=True,
253 )
255 ellipticityType = ChoiceField[str](
256 doc="The type of ellipticity to calculate",
257 allowed={
258 "chi": "Distortion, defined as (Ixx - Iyy)/(Ixx + Iyy)",
259 "epsilon": ("Shear, defined as (Ixx - Iyy)/" "(Ixx + Iyy + 2*sqrt(Ixx*Iyy - Ixy**2))"),
260 },
261 default="chi",
262 )
264 def getInputSchema(self) -> KeyedDataSchema:
265 if self.ellipticityType == "chi":
266 return (
267 (self.colXx, Vector),
268 (self.colYy, Vector),
269 )
270 else:
271 return (
272 (self.colXx, Vector),
273 (self.colYy, Vector),
274 (self.colXy, Vector),
275 )
277 def __call__(self, data: KeyedData, **kwargs) -> Vector:
278 denom = data[self.colXx.format(**kwargs)] + data[self.colYy.format(**kwargs)]
279 if self.ellipticityType == "epsilon":
280 denom += 2 * np.sqrt(
281 data[self.colXx.format(**kwargs)] * data[self.colYy.format(**kwargs)]
282 - data[self.colXy.format(**kwargs)] ** 2
283 )
284 e1 = (data[self.colXx.format(**kwargs)] - data[self.colYy.format(**kwargs)]) / denom
286 return e1
288 def validate(self):
289 super().validate()
290 if self.ellipticityType == "epsilon" and self.colXy is None:
291 msg = "colXy is required for epsilon-type shear ellipticity"
292 raise FieldValidationError(self.__class__.colXy, self, msg)
295class CalcE2(VectorAction):
296 """Calculate chi-type e2 = 2Ixy/(Ixx+Iyy) or
297 epsilon-type g2 = 2Ixy/(Ixx+Iyy+2sqrt(Ixx*Iyy - Ixy**2)).
299 See Also
300 --------
301 CalcE
302 CalcE1
304 Note
305 ----
306 This is a shape measurement used for doing QA on the ellipticity
307 of the sources.
308 """
310 colXx = Field[str](
311 doc="The column name to get the xx shape component from.",
312 default="{band}_ixx",
313 )
315 colYy = Field[str](
316 doc="The column name to get the yy shape component from.",
317 default="{band}_iyy",
318 )
320 colXy = Field[str](
321 doc="The column name to get the xy shape component from.",
322 default="{band}_ixy",
323 )
325 ellipticityType = ChoiceField[str](
326 doc="The type of ellipticity to calculate",
327 allowed={
328 "chi": "Distortion, defined as 2*Ixy/(Ixx + Iyy)",
329 "epsilon": ("Shear, defined as 2*Ixy/" "(Ixx + Iyy + 2*sqrt(Ixx*Iyy - Ixy**2))"),
330 },
331 default="chi",
332 )
334 def getInputSchema(self) -> KeyedDataSchema:
335 return (
336 (self.colXx, Vector),
337 (self.colYy, Vector),
338 (self.colXy, Vector),
339 )
341 def __call__(self, data: KeyedData, **kwargs) -> Vector:
342 denom = data[self.colXx.format(**kwargs)] + data[self.colYy.format(**kwargs)]
343 if self.ellipticityType == "epsilon":
344 denom += 2 * np.sqrt(
345 data[self.colXx.format(**kwargs)] * data[self.colYy.format(**kwargs)]
346 - data[self.colXy.format(**kwargs)] ** 2
347 )
348 e2 = 2 * data[self.colXy.format(**kwargs)] / denom
349 return e2