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

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

104 statements  

1import numpy as np 

2import matplotlib.pyplot as plt 

3import scipy.odr as scipyODR 

4 

5from lsst.geom import Box2D, SpherePoint, degrees 

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 if "tract" not in plotInfo.keys(): 

30 plotInfo["tract"] = "N/A" 

31 

32 return plotInfo 

33 

34 

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

36 """Generate a summary statistic in each patch or detector 

37 

38 Parameters 

39 ---------- 

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

41 colName : `str` 

42 skymap : `lsst.skymap.ringsSkyMap.RingsSkyMap` 

43 plotInfo : `dict` 

44 

45 Returns 

46 ------- 

47 patchInfoDict : `dict` 

48 """ 

49 

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

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

52 tractWcs = tractInfo.getWcs() 

53 

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

55 

56 patchInfoDict = {} 

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

58 if patch is None: 

59 continue 

60 # Once the objectTable_tract catalogues are using gen 3 patches 

61 # this will go away 

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

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

64 try: 

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

66 patchInfo = tractInfo.getPatchInfo(patchTuple) 

67 gen3PatchId = tractInfo.getSequentialPatchIndex(patchInfo) 

68 except AttributeError: 

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

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

71 # converted repos we can tidy this up 

72 gen3PatchId = patch 

73 patchInfo = tractInfo.getPatchInfo(patch) 

74 

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

76 skyCoords = tractWcs.pixelToSky(corners) 

77 

78 patchInfoDict[gen3PatchId] = (skyCoords, stat) 

79 

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

81 skyCoords = tractWcs.pixelToSky(tractCorners) 

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

83 

84 return patchInfoDict 

85 

86 

87def generateSummaryStatsVisit(cat, colName, visitSummaryTable, plotInfo): 

88 """Generate a summary statistic in each patch or detector 

89 

90 Parameters 

91 ---------- 

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

93 colName : `str` 

94 visitSummaryTable : `pandas.core.frame.DataFrame` 

95 plotInfo : `dict` 

96 

97 Returns 

98 ------- 

99 visitInfoDict : `dict` 

100 """ 

101 

102 visitInfoDict = {} 

103 for ccd in cat.detector.unique(): 

104 if ccd is None: 

105 continue 

106 onCcd = (cat["detector"] == ccd) 

107 stat = np.nanmedian(cat[colName].values[onCcd]) 

108 

109 sumRow = (visitSummaryTable["id"] == ccd) 

110 corners = zip(visitSummaryTable["raCorners"][sumRow][0], visitSummaryTable["decCorners"][sumRow][0]) 

111 cornersOut = [] 

112 for (ra, dec) in corners: 

113 corner = SpherePoint(ra, dec, units=degrees) 

114 cornersOut.append(corner) 

115 

116 visitInfoDict[ccd] = (cornersOut, stat) 

117 

118 return visitInfoDict 

119 

120 

121def addPlotInfo(fig, plotInfo): 

122 """Add useful information to the plot 

123 

124 Parameters 

125 ---------- 

126 fig : `matplotlib.figure.Figure` 

127 plotInfo : `dict` 

128 

129 Returns 

130 ------- 

131 fig : `matplotlib.figure.Figure` 

132 """ 

133 

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

135 photocalibDataset = "None" 

136 astroDataset = "None" 

137 

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

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

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

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

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

143 

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

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

146 return fig 

147 

148 

149def stellarLocusFit(xs, ys, paramDict): 

150 """Make a fit to the stellar locus 

151 

152 Parameters 

153 ---------- 

154 xs : `numpy.ndarray` 

155 The color on the xaxis 

156 ys : `numpy.ndarray` 

157 The color on the yaxis 

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

159 A dictionary of parameters for line fitting 

160 xMin : `float` 

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

162 xMax : `float` 

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

164 yMin : `float` 

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

166 yMax : `float` 

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

168 mHW : `float` 

169 The hardwired gradient for the fit 

170 bHW : `float` 

171 The hardwired intercept of the fit 

172 

173 Returns 

174 ------- 

175 paramsOut : `dict` 

176 A dictionary of the calculated fit parameters 

177 xMin : `float` 

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

179 xMax : `float` 

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

181 yMin : `float` 

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

183 yMax : `float` 

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

185 mHW : `float` 

186 The hardwired gradient for the fit 

187 bHW : `float` 

188 The hardwired intercept of the fit 

189 mODR : `float` 

190 The gradient calculated by the ODR fit 

191 bODR : `float` 

192 The intercept calculated by the ODR fit 

193 yBoxMin : `float` 

194 The y value of the fitted line at xMin 

195 yBoxMax : `float` 

196 The y value of the fitted line at xMax 

197 bPerpMin : `float` 

198 The intercept of the perpendicular line that goes through xMin 

199 bPerpMax : `float` 

200 The intercept of the perpendicular line that goes through xMax 

201 mODR2 : `float` 

202 The gradient from the second round of fitting 

203 bODR2 : `float` 

204 The intercept from the second round of fitting 

205 mPerp : `float` 

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

207 second fit 

208 

209 Notes 

210 ----- 

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

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

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

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

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

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

217 """ 

218 

219 # Points to use for the fit 

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

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

222 

223 linear = scipyODR.polynomial(1) 

224 

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

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

227 params = odr.run() 

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

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

230 

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

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

233 "mODR": mODR, "bODR": bODR} 

234 

235 # Having found the initial fit calculate perpendicular ends 

236 mPerp = -1.0/mODR 

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

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

239 

240 if np.fabs(mODR) > 1: 

241 yBoxMin = paramDict["yMin"] 

242 xBoxMin = (yBoxMin - bODR)/mODR 

243 yBoxMax = paramDict["yMax"] 

244 xBoxMax = (yBoxMax - bODR)/mODR 

245 else: 

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

247 xBoxMin = paramDict["xMin"] 

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

249 xBoxMax = paramDict["xMax"] 

250 

251 bPerpMin = yBoxMin - mPerp*xBoxMin 

252 

253 paramsOut["yBoxMin"] = yBoxMin 

254 paramsOut["bPerpMin"] = bPerpMin 

255 

256 bPerpMax = yBoxMax - mPerp*xBoxMax 

257 

258 paramsOut["yBoxMax"] = yBoxMax 

259 paramsOut["bPerpMax"] = bPerpMax 

260 

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

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

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

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

265 params = odr.run() 

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

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

268 

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

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

271 

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

273 

274 return paramsOut 

275 

276 

277def perpDistance(p1, p2, points): 

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

279 

280 Parameters 

281 ---------- 

282 p1 : `numpy.ndarray` 

283 A point on the line 

284 p2 : `numpy.ndarray` 

285 Another point on the line 

286 points : `zip` 

287 The points to calculate the distance to 

288 

289 Returns 

290 ------- 

291 dists : `list` 

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

293 product to work this out. 

294 """ 

295 dists = [] 

296 for point in points: 

297 point = np.array(point) 

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

299 dists.append(distToLine) 

300 

301 return dists