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__ = ("stellarLocusResid", "calcP1P2", "getCoeffs", "p1CoeffsFromP2x0y0", 

6 "p2p1CoeffsFromLinearFit", "calcQuartileClippedStats") 

7 

8 

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

10 

11 gr = gmags-rmags 

12 ri = rmags-imags 

13 

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)? 

18 

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) 

34 

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) 

38 

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) 

52 

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

54 

55 

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 

63 

64 

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 

77 

78 

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? 

81 

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] 

107 

108 return p1Coeffs 

109 

110 

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 

145 

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) 

149 

150 return Struct( 

151 p2Coeffs=p2Coeffs, 

152 p1Coeffs=p1Coeffs, 

153 ) 

154 

155 

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)) 

200 

201 return Struct( 

202 median=median, 

203 mean=quartileClippedMean, 

204 stdDev=quartileClippedStdDev, 

205 rms=quartileClippedRms, 

206 clipValue=clipValue, 

207 goodArray=good, 

208 )