Coverage for python/lsst/faro/utils/stellar_locus.py : 14%

Hot-keys 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
1import numpy as np
2import scipy.stats as scipyStats
3from lsst.pipe.base import Struct
5__all__ = ("stellarLocusResid", "calcP1P2", "getCoeffs", "p1CoeffsFromP2x0y0",
6 "p2p1CoeffsFromLinearFit", "calcQuartileClippedStats")
9def stellarLocusResid(gmags, rmags, imags, **filterargs):
11 gr = gmags-rmags
12 ri = rmags-imags
14 # Also trim large values of r-i, since those will skew the linear regression
15 okfitcolors = ((gr < 1.1) & (gr > 0.3) & (np.abs(ri) < 1.0)
16 & np.isfinite(gmags) & np.isfinite(rmags) & np.isfinite(imags))
17 # Eventually switch to using orthogonal regression instead of linear (as in pipe-analysis)?
19 slope, intercept, r_value, p_value, std_err = scipyStats.linregress(gr[okfitcolors],
20 ri[okfitcolors])
21 p2p1coeffs = p2p1CoeffsFromLinearFit(slope, intercept, 0.3, slope*0.3+intercept)
22 p1coeffs = p2p1coeffs.p1Coeffs.copy()
23 # hack to put the zeros in for u, z coeffs
24 p1coeffs.insert(0, 0.0)
25 p1coeffs.insert(4, 0.0)
26 p2coeffs = list(p2p1coeffs.p2Coeffs.copy())
27 p2coeffs.insert(0, 0.0)
28 p2coeffs.insert(4, 0.0)
29 umags = np.zeros(len(gmags))
30 zmags = np.zeros(len(gmags))
31 p1_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p1coeffs)
32 p2_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p2coeffs)
33 okp1_fit = (p1_fit < 0.6) & (p1_fit > -0.2)
35 # Do a second iteration, removing large (>3 sigma) outliers in p2:
36 clippedStats = calcQuartileClippedStats(p2_fit[okp1_fit], 3.0)
37 keep = (np.abs(p2_fit) < clippedStats.clipValue)
39 slope, intercept, r_value, p_value, std_err = scipyStats.linregress(gr[okfitcolors & keep],
40 ri[okfitcolors & keep])
41 p2p1coeffs = p2p1CoeffsFromLinearFit(slope, intercept, 0.3, slope*0.3+intercept)
42 p1coeffs = p2p1coeffs.p1Coeffs.copy()
43 # hack to put the zeros in for u, z coeffs
44 p1coeffs.insert(0, 0.0)
45 p1coeffs.insert(4, 0.0)
46 p2coeffs = list(p2p1coeffs.p2Coeffs.copy())
47 p2coeffs.insert(0, 0.0)
48 p2coeffs.insert(4, 0.0)
49 p1_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p1coeffs)
50 p2_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p2coeffs)
51 okp1_fit = (p1_fit < 0.6) & (p1_fit > -0.2)
53 return p1_fit[okp1_fit], p2_fit[okp1_fit], p1coeffs, p2coeffs
56def calcP1P2(mags, coeffs):
57 # P1 =A′u+B′g+C′r+D′i+E′z+F′
58 # P2′=Au+Bg+Cr+Di+Ez+F
59 p1p2 = (float(coeffs[0])*mags[0] + float(coeffs[1])*mags[1]
60 + float(coeffs[2])*mags[2] + float(coeffs[3])*mags[3]
61 + float(coeffs[4])*mags[4] + float(coeffs[5]))
62 return p1p2
65def getCoeffs():
66 # Coefficients from the Ivezic+2004 paper. Warning - if possible, the Coefficients
67 # should be derived from a fit to the stellar locus rather than these "fallback" values.
68 ivezicCoeffs = {'P1s': [0.91, -0.495, -0.415, 0.0, 0.0, -1.28],
69 'P1w': [0.0, 0.928, -0.556, -0.372, 0.0, -0.425],
70 'P2s': [-0.249, 0.794, -0.555, 0.0, 0.0, 0.234],
71 'P2w': [0.0, -0.227, 0.792, -0.567, 0.0, 0.050]}
72 ivezicCoeffsHSC = {'P1s': [0.91, -0.495, -0.415, 0.0, 0.0, -1.28],
73 'P1w': [0.0, 0.888, -0.427, -0.461, 0.0, -0.478],
74 'P2s': [-0.249, 0.794, -0.555, 0.0, 0.0, 0.234],
75 'P2w': [0.0, -0.274, 0.803, -0.529, 0.0, 0.041]}
76 return ivezicCoeffs, ivezicCoeffsHSC
79# Everything below this is copied directly from pipe_analysis/utils.py.
80# Should we move all those functions here once pipe_analysis is rewritten?
82def p1CoeffsFromP2x0y0(p2Coeffs, x0, y0):
83 """Compute Ivezic P1 coefficients using the P2 coeffs and origin (x0, y0)
84 Reference: Ivezic et al. 2004 (2004AN....325..583I)
85 theta = arctan(mP1), where mP1 is the slope of the equivalent straight
86 line (the P1 line) from the P2 coeffs in the (x, y)
87 coordinate system and x = c1 - c2, y = c2 - c3
88 P1 = cos(theta)*c1 + ((sin(theta) - cos(theta))*c2 - sin(theta)*c3 + deltaP1
89 P1 = 0 at x0, y0 ==> deltaP1 = -cos(theta)*x0 - sin(theta)*y0
90 Parameters
91 ----------
92 p2Coeffs : `list` of `float`
93 List of the four P2 coefficients from which, along with the origin point
94 (``x0``, ``y0``), to compute/derive the associated P1 coefficients.
95 x0, y0 : `float`
96 Coordinates at which to set P1 = 0 (i.e. the P1/P2 axis origin).
97 Returns
98 -------
99 p1Coeffs: `list` of `float`
100 The four P1 coefficients.
101 """
102 mP1 = p2Coeffs[0]/p2Coeffs[2]
103 cosTheta = np.cos(np.arctan(mP1))
104 sinTheta = np.sin(np.arctan(mP1))
105 deltaP1 = -cosTheta*x0 - sinTheta*y0
106 p1Coeffs = [cosTheta, sinTheta - cosTheta, -sinTheta, deltaP1]
108 return p1Coeffs
111def p2p1CoeffsFromLinearFit(m, b, x0, y0):
112 """Derive the Ivezic et al. 2004 P2 and P1 equations based on linear fit
113 Where the linear fit is to the given region in color-color space.
114 Reference: Ivezic et al. 2004 (2004AN....325..583I)
115 For y = m*x + b fit, where x = c1 - c2 and y = c2 - c3,
116 P2 = (-m*c1 + (m + 1)*c2 - c3 - b)/sqrt(m**2 + 1)
117 P2norm = P2/sqrt[(m**2 + (m + 1)**2 + 1**2)/(m**2 + 1)]
118 P1 = cos(theta)*x + sin(theta)*y + deltaP1, theta = arctan(m)
119 P1 = cos(theta)*(c1 - c2) + sin(theta)*(c2 - c3) + deltaP1
120 P1 = cos(theta)*c1 + ((sin(theta) - cos(theta))*c2 - sin(theta)*c3 + deltaP1
121 P1 = 0 at x0, y0 ==> deltaP1 = -cos(theta)*x0 - sin(theta)*y0
122 Parameters
123 ----------
124 m : `float`
125 Slope of line to convert.
126 b : `float`
127 Intercept of line to convert.
128 x0, y0 : `float`
129 Coordinates at which to set P1 = 0.
130 Returns
131 -------
132 result : `lsst.pipe.base.Struct`
133 Result struct with components:
134 - ``p2Coeffs`` : four P2 equation coefficents (`list` of `float`).
135 - ``p1Coeffs`` : four P1 equation coefficents (`list` of `float`).
136 """
137 # Compute Ivezic P2 coefficients using the linear fit slope and intercept
138 scaleFact = np.sqrt(m**2 + 1.0)
139 p2Coeffs = [-m/scaleFact, (m + 1.0)/scaleFact, -1.0/scaleFact, -b/scaleFact]
140 p2Norm = 0.0
141 for coeff in p2Coeffs[:-1]: # Omit the constant normalization term
142 p2Norm += coeff**2
143 p2Norm = np.sqrt(p2Norm)
144 p2Coeffs /= p2Norm
146 # Compute Ivezic P1 coefficients equation using the linear fit slope and
147 # point (x0, y0) as the origin
148 p1Coeffs = p1CoeffsFromP2x0y0(p2Coeffs, x0, y0)
150 return Struct(
151 p2Coeffs=p2Coeffs,
152 p1Coeffs=p1Coeffs,
153 )
156def calcQuartileClippedStats(dataArray, nSigmaToClip=3.0):
157 """Calculate the quartile-based clipped statistics of a data array.
158 The difference between quartiles[2] and quartiles[0] is the interquartile
159 distance. 0.74*interquartileDistance is an estimate of standard deviation
160 so, in the case that ``dataArray`` has an approximately Gaussian
161 distribution, this is equivalent to nSigma clipping.
162 Parameters
163 ----------
164 dataArray : `list` or `numpy.ndarray` of `float`
165 List or array containing the values for which the quartile-based
166 clipped statistics are to be calculated.
167 nSigmaToClip : `float`, optional
168 Number of \"sigma\" outside of which to clip data when computing the
169 statistics.
170 Returns
171 -------
172 result : `lsst.pipe.base.Struct`
173 The quartile-based clipped statistics with ``nSigmaToClip`` clipping.
174 Atributes are:
175 ``median``
176 The median of the full ``dataArray`` (`float`).
177 ``mean``
178 The quartile-based clipped mean (`float`).
179 ``stdDev``
180 The quartile-based clipped standard deviation (`float`).
181 ``rms``
182 The quartile-based clipped root-mean-squared (`float`).
183 ``clipValue``
184 The value outside of which to clip the data before computing the
185 statistics (`float`).
186 ``goodArray``
187 A boolean array indicating which data points in ``dataArray`` were
188 used in the calculation of the statistics, where `False` indicates
189 a clipped datapoint (`numpy.ndarray` of `bool`).
190 """
191 quartiles = np.percentile(dataArray, [25, 50, 75])
192 assert len(quartiles) == 3
193 median = quartiles[1]
194 interQuartileDistance = quartiles[2] - quartiles[0]
195 clipValue = nSigmaToClip*0.74*interQuartileDistance
196 good = np.logical_not(np.abs(dataArray - median) > clipValue)
197 quartileClippedMean = dataArray[good].mean()
198 quartileClippedStdDev = dataArray[good].std()
199 quartileClippedRms = np.sqrt(np.mean(dataArray[good]**2))
201 return Struct(
202 median=median,
203 mean=quartileClippedMean,
204 stdDev=quartileClippedStdDev,
205 rms=quartileClippedRms,
206 clipValue=clipValue,
207 goodArray=good,
208 )