Hide keyboard shortcuts

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 

4 

5__all__ = ( 

6 "stellarLocusResid", 

7 "calcP1P2", 

8 "getCoeffs", 

9 "p1CoeffsFromP2x0y0", 

10 "p2p1CoeffsFromLinearFit", 

11 "calcQuartileClippedStats", 

12) 

13 

14 

15def stellarLocusResid(gmags, rmags, imags, **filterargs): 

16 

17 gr = gmags - rmags 

18 ri = rmags - imags 

19 

20 # Also trim large values of r-i, since those will skew the linear regression 

21 okfitcolors = ( 

22 (gr < 1.1) 

23 & (gr > 0.3) 

24 & (np.abs(ri) < 1.0) 

25 & np.isfinite(gmags) 

26 & np.isfinite(rmags) 

27 & np.isfinite(imags) 

28 ) 

29 # Eventually switch to using orthogonal regression instead of linear (as in pipe-analysis)? 

30 

31 slope, intercept, r_value, p_value, std_err = scipyStats.linregress( 

32 gr[okfitcolors], ri[okfitcolors] 

33 ) 

34 p2p1coeffs = p2p1CoeffsFromLinearFit(slope, intercept, 0.3, slope * 0.3 + intercept) 

35 p1coeffs = p2p1coeffs.p1Coeffs.copy() 

36 # hack to put the zeros in for u, z coeffs 

37 p1coeffs.insert(0, 0.0) 

38 p1coeffs.insert(4, 0.0) 

39 p2coeffs = list(p2p1coeffs.p2Coeffs.copy()) 

40 p2coeffs.insert(0, 0.0) 

41 p2coeffs.insert(4, 0.0) 

42 umags = np.zeros(len(gmags)) 

43 zmags = np.zeros(len(gmags)) 

44 p1_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p1coeffs) 

45 p2_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p2coeffs) 

46 okp1_fit = (p1_fit < 0.6) & (p1_fit > -0.2) 

47 

48 # Do a second iteration, removing large (>3 sigma) outliers in p2: 

49 clippedStats = calcQuartileClippedStats(p2_fit[okp1_fit], 3.0) 

50 keep = np.abs(p2_fit) < clippedStats.clipValue 

51 

52 slope, intercept, r_value, p_value, std_err = scipyStats.linregress( 

53 gr[okfitcolors & keep], ri[okfitcolors & keep] 

54 ) 

55 p2p1coeffs = p2p1CoeffsFromLinearFit(slope, intercept, 0.3, slope * 0.3 + intercept) 

56 p1coeffs = p2p1coeffs.p1Coeffs.copy() 

57 # hack to put the zeros in for u, z coeffs 

58 p1coeffs.insert(0, 0.0) 

59 p1coeffs.insert(4, 0.0) 

60 p2coeffs = list(p2p1coeffs.p2Coeffs.copy()) 

61 p2coeffs.insert(0, 0.0) 

62 p2coeffs.insert(4, 0.0) 

63 p1_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p1coeffs) 

64 p2_fit = calcP1P2([umags, gmags, rmags, imags, zmags], p2coeffs) 

65 okp1_fit = (p1_fit < 0.6) & (p1_fit > -0.2) 

66 

67 return p1_fit[okp1_fit], p2_fit[okp1_fit], p1coeffs, p2coeffs 

68 

69 

70def calcP1P2(mags, coeffs): 

71 # P1 =A′u+B′g+C′r+D′i+E′z+F′ 

72 # P2′=Au+Bg+Cr+Di+Ez+F 

73 p1p2 = ( 

74 float(coeffs[0]) * mags[0] 

75 + float(coeffs[1]) * mags[1] 

76 + float(coeffs[2]) * mags[2] 

77 + float(coeffs[3]) * mags[3] 

78 + float(coeffs[4]) * mags[4] 

79 + float(coeffs[5]) 

80 ) 

81 return p1p2 

82 

83 

84def getCoeffs(): 

85 # Coefficients from the Ivezic+2004 paper. Warning - if possible, the Coefficients 

86 # should be derived from a fit to the stellar locus rather than these "fallback" values. 

87 ivezicCoeffs = { 

88 "P1s": [0.91, -0.495, -0.415, 0.0, 0.0, -1.28], 

89 "P1w": [0.0, 0.928, -0.556, -0.372, 0.0, -0.425], 

90 "P2s": [-0.249, 0.794, -0.555, 0.0, 0.0, 0.234], 

91 "P2w": [0.0, -0.227, 0.792, -0.567, 0.0, 0.050], 

92 } 

93 ivezicCoeffsHSC = { 

94 "P1s": [0.91, -0.495, -0.415, 0.0, 0.0, -1.28], 

95 "P1w": [0.0, 0.888, -0.427, -0.461, 0.0, -0.478], 

96 "P2s": [-0.249, 0.794, -0.555, 0.0, 0.0, 0.234], 

97 "P2w": [0.0, -0.274, 0.803, -0.529, 0.0, 0.041], 

98 } 

99 return ivezicCoeffs, ivezicCoeffsHSC 

100 

101 

102# Everything below this is copied directly from pipe_analysis/utils.py. 

103# Should we move all those functions here once pipe_analysis is rewritten? 

104 

105 

106def p1CoeffsFromP2x0y0(p2Coeffs, x0, y0): 

107 """Compute Ivezic P1 coefficients using the P2 coeffs and origin (x0, y0) 

108 Reference: Ivezic et al. 2004 (2004AN....325..583I) 

109 theta = arctan(mP1), where mP1 is the slope of the equivalent straight 

110 line (the P1 line) from the P2 coeffs in the (x, y) 

111 coordinate system and x = c1 - c2, y = c2 - c3 

112 P1 = cos(theta)*c1 + ((sin(theta) - cos(theta))*c2 - sin(theta)*c3 + deltaP1 

113 P1 = 0 at x0, y0 ==> deltaP1 = -cos(theta)*x0 - sin(theta)*y0 

114 Parameters 

115 ---------- 

116 p2Coeffs : `list` of `float` 

117 List of the four P2 coefficients from which, along with the origin point 

118 (``x0``, ``y0``), to compute/derive the associated P1 coefficients. 

119 x0, y0 : `float` 

120 Coordinates at which to set P1 = 0 (i.e. the P1/P2 axis origin). 

121 Returns 

122 ------- 

123 p1Coeffs: `list` of `float` 

124 The four P1 coefficients. 

125 """ 

126 mP1 = p2Coeffs[0] / p2Coeffs[2] 

127 cosTheta = np.cos(np.arctan(mP1)) 

128 sinTheta = np.sin(np.arctan(mP1)) 

129 deltaP1 = -cosTheta * x0 - sinTheta * y0 

130 p1Coeffs = [cosTheta, sinTheta - cosTheta, -sinTheta, deltaP1] 

131 

132 return p1Coeffs 

133 

134 

135def p2p1CoeffsFromLinearFit(m, b, x0, y0): 

136 """Derive the Ivezic et al. 2004 P2 and P1 equations based on linear fit 

137 Where the linear fit is to the given region in color-color space. 

138 Reference: Ivezic et al. 2004 (2004AN....325..583I) 

139 For y = m*x + b fit, where x = c1 - c2 and y = c2 - c3, 

140 P2 = (-m*c1 + (m + 1)*c2 - c3 - b)/sqrt(m**2 + 1) 

141 P2norm = P2/sqrt[(m**2 + (m + 1)**2 + 1**2)/(m**2 + 1)] 

142 P1 = cos(theta)*x + sin(theta)*y + deltaP1, theta = arctan(m) 

143 P1 = cos(theta)*(c1 - c2) + sin(theta)*(c2 - c3) + deltaP1 

144 P1 = cos(theta)*c1 + ((sin(theta) - cos(theta))*c2 - sin(theta)*c3 + deltaP1 

145 P1 = 0 at x0, y0 ==> deltaP1 = -cos(theta)*x0 - sin(theta)*y0 

146 Parameters 

147 ---------- 

148 m : `float` 

149 Slope of line to convert. 

150 b : `float` 

151 Intercept of line to convert. 

152 x0, y0 : `float` 

153 Coordinates at which to set P1 = 0. 

154 Returns 

155 ------- 

156 result : `lsst.pipe.base.Struct` 

157 Result struct with components: 

158 - ``p2Coeffs`` : four P2 equation coefficents (`list` of `float`). 

159 - ``p1Coeffs`` : four P1 equation coefficents (`list` of `float`). 

160 """ 

161 # Compute Ivezic P2 coefficients using the linear fit slope and intercept 

162 scaleFact = np.sqrt(m ** 2 + 1.0) 

163 p2Coeffs = [-m / scaleFact, (m + 1.0) / scaleFact, -1.0 / scaleFact, -b / scaleFact] 

164 p2Norm = 0.0 

165 for coeff in p2Coeffs[:-1]: # Omit the constant normalization term 

166 p2Norm += coeff ** 2 

167 p2Norm = np.sqrt(p2Norm) 

168 p2Coeffs /= p2Norm 

169 

170 # Compute Ivezic P1 coefficients equation using the linear fit slope and 

171 # point (x0, y0) as the origin 

172 p1Coeffs = p1CoeffsFromP2x0y0(p2Coeffs, x0, y0) 

173 

174 return Struct(p2Coeffs=p2Coeffs, p1Coeffs=p1Coeffs,) 

175 

176 

177def calcQuartileClippedStats(dataArray, nSigmaToClip=3.0): 

178 """Calculate the quartile-based clipped statistics of a data array. 

179 The difference between quartiles[2] and quartiles[0] is the interquartile 

180 distance. 0.74*interquartileDistance is an estimate of standard deviation 

181 so, in the case that ``dataArray`` has an approximately Gaussian 

182 distribution, this is equivalent to nSigma clipping. 

183 Parameters 

184 ---------- 

185 dataArray : `list` or `numpy.ndarray` of `float` 

186 List or array containing the values for which the quartile-based 

187 clipped statistics are to be calculated. 

188 nSigmaToClip : `float`, optional 

189 Number of \"sigma\" outside of which to clip data when computing the 

190 statistics. 

191 Returns 

192 ------- 

193 result : `lsst.pipe.base.Struct` 

194 The quartile-based clipped statistics with ``nSigmaToClip`` clipping. 

195 Atributes are: 

196 ``median`` 

197 The median of the full ``dataArray`` (`float`). 

198 ``mean`` 

199 The quartile-based clipped mean (`float`). 

200 ``stdDev`` 

201 The quartile-based clipped standard deviation (`float`). 

202 ``rms`` 

203 The quartile-based clipped root-mean-squared (`float`). 

204 ``clipValue`` 

205 The value outside of which to clip the data before computing the 

206 statistics (`float`). 

207 ``goodArray`` 

208 A boolean array indicating which data points in ``dataArray`` were 

209 used in the calculation of the statistics, where `False` indicates 

210 a clipped datapoint (`numpy.ndarray` of `bool`). 

211 """ 

212 quartiles = np.percentile(dataArray, [25, 50, 75]) 

213 assert len(quartiles) == 3 

214 median = quartiles[1] 

215 interQuartileDistance = quartiles[2] - quartiles[0] 

216 clipValue = nSigmaToClip * 0.74 * interQuartileDistance 

217 good = np.logical_not(np.abs(dataArray - median) > clipValue) 

218 quartileClippedMean = dataArray[good].mean() 

219 quartileClippedStdDev = dataArray[good].std() 

220 quartileClippedRms = np.sqrt(np.mean(dataArray[good] ** 2)) 

221 

222 return Struct( 

223 median=median, 

224 mean=quartileClippedMean, 

225 stdDev=quartileClippedStdDev, 

226 rms=quartileClippedRms, 

227 clipValue=clipValue, 

228 goodArray=good, 

229 )