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
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
1import numpy as np
2import matplotlib.pyplot as plt
3import scipy.odr as scipyODR
5from lsst.geom import Box2D, SpherePoint, degrees
8def parsePlotInfo(dataId, runName, tableName):
9 """Parse plot info from the dataId
11 Parameters
12 ----------
13 dataId : `lsst.daf.butler.core.dimensions.`
14 `_coordinate._ExpandedTupleDataCoordinate`
15 runName : `str`
17 Returns
18 -------
19 plotInfo : `dict`
20 """
21 plotInfo = {"run": runName, "tractTableType": tableName}
23 for dataInfo in dataId:
24 plotInfo[dataInfo.name] = dataId[dataInfo.name]
26 if "filter" not in plotInfo.keys():
27 plotInfo["filter"] = "N/A"
29 if "tract" not in plotInfo.keys():
30 plotInfo["tract"] = "N/A"
32 return plotInfo
35def generateSummaryStats(cat, colName, skymap, plotInfo):
36 """Generate a summary statistic in each patch or detector
38 Parameters
39 ----------
40 cat : `pandas.core.frame.DataFrame`
41 colName : `str`
42 skymap : `lsst.skymap.ringsSkyMap.RingsSkyMap`
43 plotInfo : `dict`
45 Returns
46 -------
47 patchInfoDict : `dict`
48 """
50 # TODO: what is the more generic type of skymap?
51 tractInfo = skymap.generateTract(plotInfo["tract"])
52 tractWcs = tractInfo.getWcs()
54 # For now also convert the gen 2 patchIds to gen 3
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)
75 corners = Box2D(patchInfo.getInnerBBox()).getCorners()
76 skyCoords = tractWcs.pixelToSky(corners)
78 patchInfoDict[gen3PatchId] = (skyCoords, stat)
80 tractCorners = Box2D(tractInfo.getBBox()).getCorners()
81 skyCoords = tractWcs.pixelToSky(tractCorners)
82 patchInfoDict["tract"] = (skyCoords, np.nan)
84 return patchInfoDict
87def generateSummaryStatsVisit(cat, colName, visitSummaryTable, plotInfo):
88 """Generate a summary statistic in each patch or detector
90 Parameters
91 ----------
92 cat : `pandas.core.frame.DataFrame`
93 colName : `str`
94 visitSummaryTable : `pandas.core.frame.DataFrame`
95 plotInfo : `dict`
97 Returns
98 -------
99 visitInfoDict : `dict`
100 """
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])
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)
116 visitInfoDict[ccd] = (cornersOut, stat)
118 return visitInfoDict
121def addPlotInfo(fig, plotInfo):
122 """Add useful information to the plot
124 Parameters
125 ----------
126 fig : `matplotlib.figure.Figure`
127 plotInfo : `dict`
129 Returns
130 -------
131 fig : `matplotlib.figure.Figure`
132 """
134 # TO DO: figure out how to get this information
135 photocalibDataset = "None"
136 astroDataset = "None"
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)
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
149def stellarLocusFit(xs, ys, paramDict):
150 """Make a fit to the stellar locus
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
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
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 """
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]
223 linear = scipyODR.polynomial(1)
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])
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}
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
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"]
251 bPerpMin = yBoxMin - mPerp*xBoxMin
253 paramsOut["yBoxMin"] = yBoxMin
254 paramsOut["bPerpMin"] = bPerpMin
256 bPerpMax = yBoxMax - mPerp*xBoxMax
258 paramsOut["yBoxMax"] = yBoxMax
259 paramsOut["bPerpMax"] = bPerpMax
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])
269 paramsOut["mODR2"] = float(params.beta[1])
270 paramsOut["bODR2"] = float(params.beta[0])
272 paramsOut["mPerp"] = -1.0/paramsOut["mODR2"]
274 return paramsOut
277def perpDistance(p1, p2, points):
278 """Calculate the perpendicular distance to a line from a point
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
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)
301 return dists