Coverage for python/lsst/analysis/drp/plotUtils.py: 9%

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

87 statements  

1import numpy as np 

2import matplotlib.pyplot as plt 

3import scipy.odr as scipyODR 

4 

5from lsst.geom import Box2D 

6 

7 

8def parsePlotInfo(dataId, runName, tableName): 

9 """Parse plot info from the dataId 

10 

11 Parameters 

12 ---------- 

13 dataId : `lsst.daf.butler.core.dimensions.` 

14 `_coordinate._ExpandedTupleDataCoordinate` 

15 runName : `str` 

16 

17 Returns 

18 ------- 

19 plotInfo : `dict` 

20 """ 

21 plotInfo = {"run": runName, "tractTableType": tableName} 

22 

23 for dataInfo in dataId: 

24 plotInfo[dataInfo.name] = dataId[dataInfo.name] 

25 

26 if "filter" not in plotInfo.keys(): 

27 plotInfo["filter"] = "N/A" 

28 

29 return plotInfo 

30 

31 

32def generateSummaryStats(cat, colName, skymap, plotInfo): 

33 """Generate a summary statistic in each patch 

34 

35 Parameters 

36 ---------- 

37 cat : `pandas.core.frame.DataFrame` 

38 colName : `str` 

39 skymap : `lsst.skymap.ringsSkyMap.RingsSkyMap` 

40 plotInfo : `dict` 

41 

42 Returns 

43 ------- 

44 patchInfoDict : `dict` 

45 """ 

46 

47 # TODO: what is the more generic type of skymap? 

48 tractInfo = skymap.generateTract(plotInfo["tract"]) 

49 tractWcs = tractInfo.getWcs() 

50 

51 # For now also convert the gen 2 patchIds to gen 3 

52 

53 patchInfoDict = {} 

54 for patch in cat.patch.unique(): 

55 if patch is None: 

56 continue 

57 # Once the objectTable_tract catalogues are using gen 3 patches 

58 # this will go away 

59 onPatch = (cat["patch"] == patch) 

60 stat = np.nanmedian(cat[colName].values[onPatch]) 

61 try: 

62 patchTuple = (int(patch.split(",")[0]), int(patch.split(",")[-1])) 

63 patchInfo = tractInfo.getPatchInfo(patchTuple) 

64 gen3PatchId = tractInfo.getSequentialPatchIndex(patchInfo) 

65 except AttributeError: 

66 # For native gen 3 tables the patches don't need converting 

67 # When we are no longer looking at the gen 2 -> gen 3 

68 # converted repos we can tidy this up 

69 gen3PatchId = patch 

70 patchInfo = tractInfo.getPatchInfo(patch) 

71 

72 corners = Box2D(patchInfo.getInnerBBox()).getCorners() 

73 skyCoords = tractWcs.pixelToSky(corners) 

74 

75 patchInfoDict[gen3PatchId] = (skyCoords, stat) 

76 

77 tractCorners = Box2D(tractInfo.getBBox()).getCorners() 

78 skyCoords = tractWcs.pixelToSky(tractCorners) 

79 patchInfoDict["tract"] = (skyCoords, np.nan) 

80 

81 return patchInfoDict 

82 

83 

84def addPlotInfo(fig, plotInfo): 

85 """Add useful information to the plot 

86 

87 Parameters 

88 ---------- 

89 fig : `matplotlib.figure.Figure` 

90 plotInfo : `dict` 

91 

92 Returns 

93 ------- 

94 fig : `matplotlib.figure.Figure` 

95 """ 

96 

97 # TO DO: figure out how to get this information 

98 photocalibDataset = "None" 

99 astroDataset = "None" 

100 

101 plt.text(0.02, 0.98, "Run:" + plotInfo["run"], fontsize=8, alpha=0.8, transform=fig.transFigure) 

102 datasetType = ("Datasets used: photocalib: " + photocalibDataset + ", astrometry: " + astroDataset) 

103 tableType = "Table: " + plotInfo["tractTableType"] 

104 dataIdText = "Tract: " + str(plotInfo["tract"]) + " , Filter: " + plotInfo["filter"] 

105 plt.text(0.02, 0.95, datasetType, fontsize=8, alpha=0.8, transform=fig.transFigure) 

106 

107 plt.text(0.02, 0.92, tableType, fontsize=8, alpha=0.8, transform=fig.transFigure) 

108 plt.text(0.02, 0.89, dataIdText, fontsize=8, alpha=0.8, transform=fig.transFigure) 

109 return fig 

110 

111 

112def stellarLocusFit(xs, ys, paramDict): 

113 """Make a fit to the stellar locus 

114 

115 Parameters 

116 ---------- 

117 xs : `numpy.ndarray` 

118 The color on the xaxis 

119 ys : `numpy.ndarray` 

120 The color on the yaxis 

121 paramDict : lsst.pex.config.dictField.Dict 

122 A dictionary of parameters for line fitting 

123 xMin : `float` 

124 The minimum x edge of the box to use for initial fitting 

125 xMax : `float` 

126 The maximum x edge of the box to use for initial fitting 

127 yMin : `float` 

128 The minimum y edge of the box to use for initial fitting 

129 yMax : `float` 

130 The maximum y edge of the box to use for initial fitting 

131 mHW : `float` 

132 The hardwired gradient for the fit 

133 bHW : `float` 

134 The hardwired intercept of the fit 

135 

136 Returns 

137 ------- 

138 paramsOut : `dict` 

139 A dictionary of the calculated fit parameters 

140 xMin : `float` 

141 The minimum x edge of the box to use for initial fitting 

142 xMax : `float` 

143 The maximum x edge of the box to use for initial fitting 

144 yMin : `float` 

145 The minimum y edge of the box to use for initial fitting 

146 yMax : `float` 

147 The maximum y edge of the box to use for initial fitting 

148 mHW : `float` 

149 The hardwired gradient for the fit 

150 bHW : `float` 

151 The hardwired intercept of the fit 

152 mODR : `float` 

153 The gradient calculated by the ODR fit 

154 bODR : `float` 

155 The intercept calculated by the ODR fit 

156 yBoxMin : `float` 

157 The y value of the fitted line at xMin 

158 yBoxMax : `float` 

159 The y value of the fitted line at xMax 

160 bPerpMin : `float` 

161 The intercept of the perpendicular line that goes through xMin 

162 bPerpMax : `float` 

163 The intercept of the perpendicular line that goes through xMax 

164 mODR2 : `float` 

165 The gradient from the second round of fitting 

166 bODR2 : `float` 

167 The intercept from the second round of fitting 

168 mPerp : `float` 

169 The gradient of the line perpendicular to the line from the 

170 second fit 

171 

172 Notes 

173 ----- 

174 The code does two rounds of fitting, the first is initiated using the 

175 hardwired values given in the `paramDict` parameter and is done using 

176 an Orthogonal Distance Regression fit to the points defined by the 

177 box of xMin, xMax, yMin and yMax. Once this fitting has been done a 

178 perpendicular bisector is calculated at either end of the line and 

179 only points that fall within these lines are used to recalculate the fit. 

180 """ 

181 

182 # Points to use for the fit 

183 fitPoints = np.where((xs > paramDict["xMin"]) & (xs < paramDict["xMax"]) 

184 & (ys > paramDict["yMin"]) & (ys < paramDict["yMax"]))[0] 

185 

186 linear = scipyODR.polynomial(1) 

187 

188 data = scipyODR.Data(xs[fitPoints], ys[fitPoints]) 

189 odr = scipyODR.ODR(data, linear, beta0=[paramDict["bHW"], paramDict["mHW"]]) 

190 params = odr.run() 

191 mODR = float(params.beta[1]) 

192 bODR = float(params.beta[0]) 

193 

194 paramsOut = {"xMin": paramDict["xMin"], "xMax": paramDict["xMax"], "yMin": paramDict["yMin"], 

195 "yMax": paramDict["yMax"], "mHW": paramDict["mHW"], "bHW": paramDict["bHW"], 

196 "mODR": mODR, "bODR": bODR} 

197 

198 # Having found the initial fit calculate perpendicular ends 

199 mPerp = -1.0/mODR 

200 # When the gradient is really steep we need to use 

201 # the y limits of the box rather than the x ones 

202 

203 if np.fabs(mODR) > 1: 

204 yBoxMin = paramDict["yMin"] 

205 xBoxMin = (yBoxMin - bODR)/mODR 

206 yBoxMax = paramDict["yMax"] 

207 xBoxMax = (yBoxMax - bODR)/mODR 

208 else: 

209 yBoxMin = mODR*paramDict["xMin"] + bODR 

210 xBoxMin = paramDict["xMin"] 

211 yBoxMax = mODR*paramDict["xMax"] + bODR 

212 xBoxMax = paramDict["xMax"] 

213 

214 bPerpMin = yBoxMin - mPerp*xBoxMin 

215 

216 paramsOut["yBoxMin"] = yBoxMin 

217 paramsOut["bPerpMin"] = bPerpMin 

218 

219 bPerpMax = yBoxMax - mPerp*xBoxMax 

220 

221 paramsOut["yBoxMax"] = yBoxMax 

222 paramsOut["bPerpMax"] = bPerpMax 

223 

224 # Use these perpendicular lines to chose the data and refit 

225 fitPoints = ((ys > mPerp*xs + bPerpMin) & (ys < mPerp*xs + bPerpMax)) 

226 data = scipyODR.Data(xs[fitPoints], ys[fitPoints]) 

227 odr = scipyODR.ODR(data, linear, beta0=[bODR, mODR]) 

228 params = odr.run() 

229 mODR = float(params.beta[1]) 

230 bODR = float(params.beta[0]) 

231 

232 paramsOut["mODR2"] = float(params.beta[1]) 

233 paramsOut["bODR2"] = float(params.beta[0]) 

234 

235 paramsOut["mPerp"] = -1.0/paramsOut["mODR2"] 

236 

237 return paramsOut 

238 

239 

240def perpDistance(p1, p2, points): 

241 """Calculate the perpendicular distance to a line from a point 

242 

243 Parameters 

244 ---------- 

245 p1 : `numpy.ndarray` 

246 A point on the line 

247 p2 : `numpy.ndarray` 

248 Another point on the line 

249 points : `zip` 

250 The points to calculate the distance to 

251 

252 Returns 

253 ------- 

254 dists : `list` 

255 The distances from the line to the points. Uses the cross 

256 product to work this out. 

257 """ 

258 dists = [] 

259 for point in points: 

260 point = np.array(point) 

261 distToLine = np.cross(p1 - point, p2 - point)/np.linalg.norm(p2 - point) 

262 dists.append(distToLine) 

263 

264 return dists