Coverage for python/lsst/analysis/drp/calcFunctors.py: 42%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1__all__ = ["SNCalculator", "KronFluxDivPsfFlux", "MagDiff", "ColorDiff", "ColorDiffPull"]
3from lsst.pipe.tasks.configurableActions import ConfigurableActionField
4from lsst.pipe.tasks.dataFrameActions import DataFrameAction, DivideColumns, MultiColumnAction
5from lsst.pex.config import Field
6from astropy import units as u
7import numpy as np
10class SNCalculator(DivideColumns):
11 """Calculate the signal to noise by default the i band PSF flux is used"""
13 def setDefaults(self):
14 super().setDefaults()
15 self.colA.column = "i_psfFlux"
16 self.colB.column = "i_psfFluxErr"
19class KronFluxDivPsfFlux(DivideColumns):
20 """Divide the Kron instFlux by the PSF instFlux"""
22 def setDefaults(self):
23 super().setDefaults()
24 self.colA.column = "i_kronFlux"
25 self.colB.column = "i_psfFlux"
28class MagDiff(MultiColumnAction):
29 """Calculate the difference between two magnitudes;
30 each magnitude is derived from a flux column.
32 Parameters
33 ----------
34 df : `pandas.core.frame.DataFrame`
35 The catalog to calculate the magnitude difference from.
37 Returns
38 -------
39 The magnitude difference in milli mags.
41 Notes
42 -----
43 The flux columns need to be in units (specifiable in
44 the fluxUnits1 and 2 config options) that can be converted
45 to janskies. This action doesn't have any calibration
46 information and assumes that the fluxes are already
47 calibrated.
48 """
50 col1 = Field(doc="Column to subtract from", dtype=str)
51 fluxUnits1 = Field(doc="Units for col1", dtype=str, default="nanojansky")
52 col2 = Field(doc="Column to subtract", dtype=str)
53 fluxUnits2 = Field(doc="Units for col2", dtype=str, default="nanojansky")
54 returnMillimags = Field(doc="Use millimags or not?", dtype=bool, default=True)
56 @property
57 def columns(self):
58 return (self.col1, self.col2)
60 def __call__(self, df):
61 flux1 = df[self.col1].values * u.Unit(self.fluxUnits1)
62 mag1 = flux1.to(u.ABmag)
64 flux2 = df[self.col2].values * u.Unit(self.fluxUnits2)
65 mag2 = flux2.to(u.ABmag)
67 magDiff = mag1 - mag2
69 if self.returnMillimags:
70 magDiff = magDiff*1000.0
71 return magDiff
74class CalcE(MultiColumnAction):
75 """Calculate a complex value representation of the ellipticity
77 This is a shape measurement used for doing QA on the ellipticity
78 of the sources.
80 The complex ellipticity is typically defined as
81 E = ((ixx - iyy) + 1j*(2*ixy))/(ixx + iyy) = |E|exp(i*2*theta).
83 For plotting purposes we might want to plot |E|*exp(i*theta).
84 If `halvePhaseAngle` config parameter is set to `True`, then
85 the returned quantity therefore corresponds to |E|*exp(i*theta)
86 """
88 colXx = Field(doc="The column name to get the xx shape component from.",
89 dtype=str,
90 default="ixx")
92 colYy = Field(doc="The column name to get the yy shape component from.",
93 dtype=str,
94 default="iyy")
96 colXy = Field(doc="The column name to get the xy shape component from.",
97 dtype=str,
98 default="ixy")
100 halvePhaseAngle = Field(doc=("Divide the phase angle by 2? "
101 "Suitable for quiver plots."),
102 dtype=bool,
103 default=False)
105 @property
106 def columns(self):
107 return (self.colXx, self.colYy, self.colXy)
109 def __call__(self, df):
110 e = (df[self.colXx] - df[self.colYy]) + 1j*(2*df[self.colXy])
111 e /= (df[self.colXx] + df[self.colYy])
112 if self.halvePhaseAngle:
113 # Ellipiticity is |e|*exp(i*2*theta), but we want to return
114 # |e|*exp(i*theta). So we multiply by |e| and take its square root
115 # instead of the more expensive trig calls.
116 e *= np.abs(e)
117 return np.sqrt(e)
118 else:
119 return e
122class CalcEDiff(DataFrameAction):
123 """Calculate the difference of two ellipticities as a complex quantity.
125 This is a shape measurement used for doing QA on the ellipticity
126 of the sources.
128 The complex ellipticity difference between E_A and E_B is efined as
129 dE = |dE|exp(i*2*theta).
131 For plotting purposes we might want to plot |dE|*exp(i*theta).
132 If `halvePhaseAngle` config parameter is set to `True`, then
133 the returned quantity therefore corresponds to |E|*exp(i*theta)
134 """
135 colA = ConfigurableActionField(doc="Ellipticity to subtract from",
136 dtype=MultiColumnAction,
137 default=CalcE)
139 colB = ConfigurableActionField(doc="Ellipticity to subtract",
140 dtype=MultiColumnAction,
141 default=CalcE)
143 halvePhaseAngle = Field(doc=("Divide the phase angle by 2? "
144 "Suitable for quiver plots."),
145 dtype=bool,
146 default=False)
148 @property
149 def columns(self):
150 yield from self.colA.columns
151 yield from self.colB.columns
153 def __call__(self, df):
154 eMeas = self.colA(df)
155 ePSF = self.colB(df)
156 eDiff = eMeas - ePSF
157 if self.halvePhaseAngle:
158 # Ellipiticity is |e|*exp(i*2*theta), but we want to return
159 # |e|*exp(i*theta). So we multiply by |e| and take its square root
160 # instead of the more expensive trig calls.
161 eDiff *= np.abs(eDiff)
162 return np.sqrt(eDiff)
163 else:
164 return eDiff
167class CalcE1(MultiColumnAction):
168 """Calculate E1: (ixx - iyy)/(ixx + iyy)
169 This is a shape measurement used for doing QA on the ellipticity
170 of the sources."""
172 colXx = Field(doc="The column name to get the xx shape component from.",
173 dtype=str,
174 default="ixx")
176 colYy = Field(doc="The column name to get the yy shape component from.",
177 dtype=str,
178 default="iyy")
180 @property
181 def columns(self):
182 return (self.colXx, self.colYy)
184 def __call__(self, df):
185 e1 = (df[self.colXx] - df[self.colYy])/(df[self.colXx] + df[self.colYy])
187 return e1
190class CalcE2(MultiColumnAction):
191 """Calculate E2: 2ixy/(ixx+iyy)
192 This is a shape measurement used for doing QA on the ellipticity
193 of the sources."""
195 colXx = Field(doc="The column name to get the xx shape component from.",
196 dtype=str,
197 default="ixx")
199 colYy = Field(doc="The column name to get the yy shape component from.",
200 dtype=str,
201 default="iyy")
203 colXy = Field(doc="The column name to get the xy shape component from.",
204 dtype=str,
205 default="ixy")
207 @property
208 def columns(self):
209 return (self.colXx, self.colYy, self.colXy)
211 def __call__(self, df):
212 e2 = 2*df[self.colXy]/(df[self.colXx] + df[self.colYy])
213 return e2
216class CalcShapeSize(MultiColumnAction):
217 """Calculate a size: (ixx*iyy - ixy**2)**0.25
218 This is a size measurement used for doing QA on the ellipticity
219 of the sources."""
221 colXx = Field(doc="The column name to get the xx shape component from.",
222 dtype=str,
223 default="ixx")
225 colYy = Field(doc="The column name to get the yy shape component from.",
226 dtype=str,
227 default="iyy")
229 colXy = Field(doc="The column name to get the xy shape component from.",
230 dtype=str,
231 default="ixy")
233 @property
234 def columns(self):
235 return (self.colXx, self.colYy, self.colXy)
237 def __call__(self, df):
238 size = np.power(df[self.colXx]*df[self.colYy] - df[self.colXy]**2, 0.25)
239 return size
242class ColorDiff(MultiColumnAction):
243 """Calculate the difference between two colors;
244 each color is derived from two flux columns.
246 The color difference is computed as (color1 - color2) with:
248 color1 = color1_mag1 - color1_mag2
249 color2 = color2_mag1 - color2_mag2
251 where color1_mag1 is the magnitude associated with color1_flux1, etc.
253 Parameters
254 ----------
255 df : `pandas.core.frame.DataFrame`
256 The catalog to calculate the color difference from.
258 Returns
259 -------
260 The color difference in millimags.
262 Notes
263 -----
264 The flux columns need to be in units that can be converted
265 to janskies. This action doesn't have any calibration
266 information and assumes that the fluxes are already
267 calibrated.
268 """
269 color1_flux1 = Field(doc="Column for flux1 to determine color1",
270 dtype=str)
271 color1_flux1_units = Field(doc="Units for color1_flux1",
272 dtype=str,
273 default="nanojansky")
274 color1_flux2 = Field(doc="Column for flux2 to determine color1",
275 dtype=str)
276 color1_flux2_units = Field(doc="Units for color1_flux2",
277 dtype=str,
278 default="nanojansky")
279 color2_flux1 = Field(doc="Column for flux1 to determine color2",
280 dtype=str)
281 color2_flux1_units = Field(doc="Units for color2_flux1",
282 dtype=str,
283 default="nanojansky")
284 color2_flux2 = Field(doc="Column for flux2 to determine color2",
285 dtype=str)
286 color2_flux2_units = Field(doc="Units for color2_flux2",
287 dtype=str,
288 default="nanojansky")
289 return_millimags = Field(doc="Use millimags or not?",
290 dtype=bool,
291 default=True)
293 @property
294 def columns(self):
295 return (self.color1_flux1,
296 self.color1_flux2,
297 self.color2_flux1,
298 self.color2_flux2)
300 def __call__(self, df):
301 color1_flux1 = df[self.color1_flux1].values*u.Unit(self.color1_flux1_units)
302 color1_mag1 = color1_flux1.to(u.ABmag).value
304 color1_flux2 = df[self.color1_flux2].values*u.Unit(self.color1_flux2_units)
305 color1_mag2 = color1_flux2.to(u.ABmag).value
307 color2_flux1 = df[self.color2_flux1].values*u.Unit(self.color2_flux1_units)
308 color2_mag1 = color2_flux1.to(u.ABmag).value
310 color2_flux2 = df[self.color2_flux2].values*u.Unit(self.color2_flux2_units)
311 color2_mag2 = color2_flux2.to(u.ABmag).value
313 color1 = color1_mag1 - color1_mag2
314 color2 = color2_mag1 - color2_mag2
316 color_diff = color1 - color2
318 if self.return_millimags:
319 color_diff *= 1000.0
321 return color_diff
324class ColorDiffPull(ColorDiff):
325 """Calculate the difference between two colors, scaled by the color error;
326 Each color is derived from two flux columns.
328 The color difference is computed as (color1 - color2) with:
330 color1 = color1_mag1 - color1_mag2
331 color2 = color2_mag1 - color2_mag2
333 where color1_mag1 is the magnitude associated with color1_flux1, etc.
335 The color difference (color1 - color2) is then scaled by the error on
336 the color as computed from color1_flux1_err, color1_flux2_err,
337 color2_flux1_err, color2_flux2_err. The errors on color2 may be omitted
338 if the comparison is between an "observed" catalog and a "truth" catalog.
340 Parameters
341 ----------
342 df : `pandas.core.frame.DataFrame`
343 The catalog to calculate the color difference from.
345 Returns
346 -------
347 The color difference scaled by the error.
349 Notes
350 -----
351 The flux columns need to be in units that can be converted
352 to janskies. This action doesn't have any calibration
353 information and assumes that the fluxes are already
354 calibrated.
355 """
356 color1_flux1_err = Field(doc="Error column for flux1 for color1",
357 dtype=str,
358 default="")
359 color1_flux2_err = Field(doc="Error column for flux2 for color1",
360 dtype=str,
361 default="")
362 color2_flux1_err = Field(doc="Error column for flux1 for color2",
363 dtype=str,
364 default="")
365 color2_flux2_err = Field(doc="Error column for flux2 for color2",
366 dtype=str,
367 default="")
369 def validate(self):
370 super().validate()
372 color1_errors = False
373 color2_errors = False
375 if self.color1_flux1_err and self.color1_flux2_err:
376 color1_errors = True
377 elif ((self.color1_flux1_err and not self.color1_flux2_err)
378 or (not self.color1_flux1_err and self.color1_flux2_err)):
379 raise ValueError("Must set both color1_flux1_err and color1_flux2_err if either is set.")
380 if self.color2_flux1_err and self.color2_flux2_err:
381 color2_errors = True
382 elif ((self.color2_flux1_err and not self.color2_flux2_err)
383 or (not self.color2_flux1_err and self.color2_flux2_err)):
384 raise ValueError("Must set both color2_flux1_err and color2_flux2_err if either is set.")
386 if not color1_errors and not color2_errors:
387 raise ValueError("Must configure flux errors for at least color1 or color2.")
389 @property
390 def columns(self):
391 columns = (self.color1_flux1,
392 self.color1_flux2,
393 self.color2_flux1,
394 self.color2_flux2)
396 if self.color1_flux1_err:
397 # Config validation ensures if one is set, both are set.
398 columns = columns + (self.color1_flux1_err,
399 self.color1_flux2_err)
401 if self.color2_flux1_err:
402 # Config validation ensures if one is set, both are set.
403 columns = columns + (self.color2_flux1_err,
404 self.color2_flux2_err)
406 return columns
408 def __call__(self, df):
409 k = 2.5/np.log(10.)
411 color1_flux1 = df[self.color1_flux1].values*u.Unit(self.color1_flux1_units)
412 color1_mag1 = color1_flux1.to(u.ABmag).value
413 if self.color1_flux1_err:
414 color1_mag1_err = k*df[self.color1_flux1_err].values/df[self.color1_flux1].values
415 else:
416 color1_mag1_err = 0.0
418 color1_flux2 = df[self.color1_flux2].values*u.Unit(self.color1_flux2_units)
419 color1_mag2 = color1_flux2.to(u.ABmag).value
420 if self.color1_flux2_err:
421 color1_mag2_err = k*df[self.color1_flux2_err].values/df[self.color1_flux2].values
422 else:
423 color1_mag2_err = 0.0
425 color2_flux1 = df[self.color2_flux1].values*u.Unit(self.color2_flux1_units)
426 color2_mag1 = color2_flux1.to(u.ABmag).value
427 if self.color2_flux1_err:
428 color2_mag1_err = k*df[self.color2_flux1_err].values/df[self.color2_flux1].values
429 else:
430 color2_mag1_err = 0.0
432 color2_flux2 = df[self.color2_flux2].values*u.Unit(self.color2_flux2_units)
433 color2_mag2 = color2_flux2.to(u.ABmag).value
434 if self.color2_flux2_err:
435 color2_mag2_err = k*df[self.color2_flux2_err].values/df[self.color2_flux2].values
436 else:
437 color2_mag2_err = 0.0
439 color1 = color1_mag1 - color1_mag2
440 err1_sq = color1_mag1_err**2. + color1_mag2_err**2.
441 color2 = color2_mag1 - color2_mag2
442 err2_sq = color2_mag1_err**2. + color2_mag2_err**2.
444 color_diff = color1 - color2
446 pull = color_diff/np.sqrt(err1_sq + err2_sq)
448 return pull