Coverage for python/lsst/sims/maf/plots/spatialPlotters.py : 8%

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
1from builtins import zip
2import numbers
3import numpy as np
4import warnings
5import healpy as hp
6from matplotlib import colors
7from matplotlib import ticker
8import matplotlib.pyplot as plt
9import matplotlib.cm as cm
10from matplotlib.ticker import FuncFormatter
11import matplotlib as mpl
12from matplotlib.patches import Ellipse
13from matplotlib.collections import PatchCollection
15from lsst.sims.maf.utils import optimalBins, percentileClipping
16from .plotHandler import BasePlotter, applyZPNorm
18from lsst.sims.utils import _equatorialFromGalactic, _healbin
19from .perceptual_rainbow import makePRCmap
20perceptual_rainbow = makePRCmap()
21import numpy.ma as ma
23__all__ = ['setColorLims', 'setColorMap', 'HealpixSkyMap', 'HealpixPowerSpectrum',
24 'HealpixHistogram', 'OpsimHistogram', 'BaseHistogram',
25 'BaseSkyMap', 'HealpixSDSSSkyMap', 'LambertSkyMap']
27baseDefaultPlotDict = {'title': None, 'xlabel': None, 'label': None,
28 'logScale': False, 'percentileClip': None, 'normVal': None, 'zp': None,
29 'cbarFormat': None, 'cmap': perceptual_rainbow, 'cbar_edge': True, 'nTicks': 10,
30 'colorMin': None, 'colorMax': None,
31 'xMin': None, 'xMax': None, 'yMin': None, 'yMax': None,
32 'labelsize': None, 'fontsize': None, 'figsize': None, 'subplot': 111,
33 'maskBelow': None}
36def setColorLims(metricValue, plotDict):
37 """Set up color bar limits."""
38 # Use plot dict if these values are set.
39 colorMin = plotDict['colorMin']
40 colorMax = plotDict['colorMax']
41 # If not, try to use percentile clipping.
42 if (plotDict['percentileClip'] is not None) & (np.size(metricValue.compressed()) > 0):
43 pcMin, pcMax = percentileClipping(metricValue.compressed(), percentile=plotDict['percentileClip'])
44 if colorMin is None:
45 colorMin = pcMin
46 if colorMax is None:
47 colorMax = pcMax
48 # If not, just use the data limits.
49 if colorMin is None:
50 colorMin = metricValue.compressed().min()
51 if colorMax is None:
52 colorMax = metricValue.compressed().max()
53 # But make sure there is some range on the colorbar
54 if colorMin == colorMax:
55 colorMin = colorMin - 0.5
56 colorMax = colorMax + 0.5
57 return np.sort([colorMin, colorMax])
60def setColorMap(plotDict):
61 cmap = plotDict['cmap']
62 if cmap is None:
63 cmap = 'perceptual_rainbow'
64 if type(cmap) == str:
65 cmap = getattr(cm, cmap)
66 # Set background and masked pixel colors default healpy white and gray.
67 cmap.set_over(cmap(1.0))
68 cmap.set_under('w')
69 cmap.set_bad('gray')
70 return cmap
73class HealpixSkyMap(BasePlotter):
74 """
75 Generate a sky map of healpix metric values using healpy's mollweide view.
76 """
77 def __init__(self):
78 super(HealpixSkyMap, self).__init__()
79 # Set the plotType
80 self.plotType = 'SkyMap'
81 self.objectPlotter = False
82 # Set up the default plotting parameters.
83 self.defaultPlotDict = {}
84 self.defaultPlotDict.update(baseDefaultPlotDict)
85 self.defaultPlotDict.update({'rot': (0, 0, 0), 'flip': 'astro', 'coord': 'C',
86 'nside': 8, 'reduceFunc': np.mean})
87 # Note: for alt/az sky maps using the healpix plotter, you can use
88 # {'rot': (90, 90, 90), 'flip': 'geo'}
89 self.healpy_visufunc = hp.mollview
90 self.healpy_visufunc_params = {}
91 self.ax = None
92 self.im = None
94 def __call__(self, metricValueIn, slicer, userPlotDict, fignum=None):
95 """
96 Parameters
97 ----------
98 metricValue : numpy.ma.MaskedArray
99 slicer : lsst.sims.maf.slicers.HealpixSlicer
100 userPlotDict: dict
101 Dictionary of plot parameters set by user (overrides default values).
102 fignum : int
103 Matplotlib figure number to use (default = None, starts new figure).
105 Returns
106 -------
107 int
108 Matplotlib figure number used to create the plot.
109 """
110 # Override the default plotting parameters with user specified values.
111 plotDict = {}
112 plotDict.update(self.defaultPlotDict)
113 plotDict.update(userPlotDict)
115 # Check if we have a valid HEALpix slicer
116 if 'Heal' in slicer.slicerName:
117 # Update the metric data with zeropoint or normalization.
118 metricValue = applyZPNorm(metricValueIn, plotDict)
119 else:
120 # Bin the values up on a healpix grid.
121 metricValue = _healbin(slicer.slicePoints['ra'], slicer.slicePoints['dec'],
122 metricValueIn.filled(slicer.badval), nside=plotDict['nside'],
123 reduceFunc=plotDict['reduceFunc'], fillVal=slicer.badval)
124 mask = np.zeros(metricValue.size)
125 mask[np.where(metricValue == slicer.badval)] = 1
126 metricValue = ma.array(metricValue, mask=mask)
127 metricValue = applyZPNorm(metricValue, plotDict)
129 if plotDict['maskBelow'] is not None:
130 toMask = np.where(metricValue <= plotDict['maskBelow'])[0]
131 metricValue.mask[toMask] = True
132 badval = hp.UNSEEN
133 else:
134 badval = slicer.badval
136 # Generate a Mollweide full-sky plot.
137 fig = plt.figure(fignum, figsize=plotDict['figsize'])
138 # Set up color bar limits.
139 clims = setColorLims(metricValue, plotDict)
140 cmap = setColorMap(plotDict)
141 # Set log scale?
142 norm = None
143 if plotDict['logScale']:
144 norm = 'log'
145 # Avoid trying to log scale when zero is in the range.
146 if (norm == 'log') & ((clims[0] <= 0 <= clims[1]) or (clims[0] >= 0 >= clims[1])):
147 # Try something simple
148 above = metricValue[np.where(metricValue > 0)]
149 if len(above) > 0:
150 clims[0] = above.max()
151 # If still bad, give up and turn off norm
152 if ((clims[0] <= 0 <= clims[1]) or (clims[0] >= 0 >= clims[1])):
153 norm = None
154 warnings.warn("Using norm was set to log, but color limits pass through 0. "
155 "Adjusting so plotting doesn't fail")
156 if plotDict['coord'] == 'C':
157 notext = True
158 else:
159 notext = False
161 visufunc_params = {'title': plotDict['title'],
162 'cbar': False,
163 'min': clims[0],
164 'max': clims[1],
165 'rot': plotDict['rot'],
166 'flip': plotDict['flip'],
167 'coord': plotDict['coord'],
168 'cmap': cmap,
169 'norm': norm,
170 'sub': plotDict['subplot'],
171 'fig':fig.number,
172 'notext': notext}
173 visufunc_params.update(self.healpy_visufunc_params)
174 self.healpy_visufunc(metricValue.filled(badval), **visufunc_params)
176 # Add a graticule (grid) over the globe.
177 hp.graticule(dpar=30, dmer=30, verbose=False)
178 # Add colorbar (not using healpy default colorbar because we want more tickmarks).
179 self.ax = plt.gca()
180 im = self.ax.get_images()[0]
181 # Add label.
182 if plotDict['label'] is not None:
183 plt.figtext(0.8, 0.8, '%s' % (plotDict['label']))
184 # Make a color bar. Supress silly colorbar warnings.
185 with warnings.catch_warnings():
186 warnings.simplefilter("ignore")
187 cb = plt.colorbar(im, shrink=0.75, aspect=25, pad=0.1, orientation='horizontal',
188 format=plotDict['cbarFormat'], extendrect=True)
189 cb.set_label(plotDict['xlabel'], fontsize=plotDict['fontsize'])
190 if plotDict['labelsize'] is not None:
191 cb.ax.tick_params(labelsize=plotDict['labelsize'])
192 if norm == 'log':
193 tick_locator = ticker.LogLocator(numticks=plotDict['nTicks'])
194 cb.locator = tick_locator
195 cb.update_ticks()
196 if (plotDict['nTicks'] is not None) & (norm != 'log'):
197 tick_locator = ticker.MaxNLocator(nbins=plotDict['nTicks'])
198 cb.locator = tick_locator
199 cb.update_ticks()
200 # If outputing to PDF, this fixes the colorbar white stripes
201 if plotDict['cbar_edge']:
202 cb.solids.set_edgecolor("face")
203 return fig.number
206class HealpixPowerSpectrum(BasePlotter):
207 def __init__(self):
208 self.plotType = 'PowerSpectrum'
209 self.objectPlotter = False
210 self.defaultPlotDict = {}
211 self.defaultPlotDict.update(baseDefaultPlotDict)
212 self.defaultPlotDict.update({'maxl': None, 'removeDipole': True, 'linestyle': '-'})
214 def __call__(self, metricValue, slicer, userPlotDict, fignum=None):
215 """
216 Generate and plot the power spectrum of metricValue (calculated on a healpix grid).
217 """
218 if 'Healpix' not in slicer.slicerName:
219 raise ValueError('HealpixPowerSpectrum for use with healpix metricBundles.')
220 plotDict = {}
221 plotDict.update(self.defaultPlotDict)
222 plotDict.update(userPlotDict)
224 fig = plt.figure(fignum, figsize=plotDict['figsize'])
225 ax = fig.add_subplot(plotDict['subplot'])
226 # If the mask is True everywhere (no data), just plot zeros
227 if False not in metricValue.mask:
228 return None
229 if plotDict['removeDipole']:
230 cl = hp.anafast(hp.remove_dipole(metricValue.filled(slicer.badval)), lmax=plotDict['maxl'])
231 else:
232 cl = hp.anafast(metricValue.filled(slicer.badval), lmax=plotDict['maxl'])
233 ell = np.arange(np.size(cl))
234 if plotDict['removeDipole']:
235 condition = (ell > 1)
236 else:
237 condition = (ell > 0)
238 ell = ell[condition]
239 cl = cl[condition]
240 # Plot the results.
241 plt.plot(ell, (cl * ell * (ell + 1)) / 2.0 / np.pi,
242 color=plotDict['color'], linestyle=plotDict['linestyle'], label=plotDict['label'])
243 if cl.max() > 0 and plotDict['logScale']:
244 plt.yscale('log')
245 plt.xlabel(r'$l$', fontsize=plotDict['fontsize'])
246 plt.ylabel(r'$l(l+1)C_l/(2\pi)$', fontsize=plotDict['fontsize'])
247 if plotDict['labelsize'] is not None:
248 plt.tick_params(axis='x', labelsize=plotDict['labelsize'])
249 plt.tick_params(axis='y', labelsize=plotDict['labelsize'])
250 if plotDict['title'] is not None:
251 plt.title(plotDict['title'])
252 # Return figure number (so we can reuse/add onto/save this figure if desired).
253 return fig.number
256class HealpixHistogram(BasePlotter):
257 def __init__(self):
258 self.plotType = 'Histogram'
259 self.objectPlotter = False
260 self.defaultPlotDict = {}
261 self.defaultPlotDict.update(baseDefaultPlotDict)
262 self.defaultPlotDict.update({'ylabel': 'Area (1000s of square degrees)',
263 'bins': None, 'binsize': None, 'cumulative': False,
264 'scale': None, 'linestyle': '-'})
265 self.baseHist = BaseHistogram()
267 def __call__(self, metricValue, slicer, userPlotDict, fignum=None):
268 """
269 Histogram metricValue for all healpix points.
270 """
271 if 'Healpix' not in slicer.slicerName:
272 raise ValueError('HealpixHistogram is for use with healpix slicer.')
273 plotDict = {}
274 plotDict.update(self.defaultPlotDict)
275 plotDict.update(userPlotDict)
276 if plotDict['scale'] is None:
277 plotDict['scale'] = (hp.nside2pixarea(slicer.nside, degrees=True) / 1000.0)
278 fignum = self.baseHist(metricValue, slicer, plotDict, fignum=fignum)
279 return fignum
282class OpsimHistogram(BasePlotter):
283 def __init__(self):
284 self.plotType = 'Histogram'
285 self.objectPlotter = False
286 self.defaultPlotDict = {}
287 self.defaultPlotDict.update(baseDefaultPlotDict)
288 self.defaultPlotDict.update({'ylabel': 'Number of Fields', 'yaxisformat': '%d',
289 'bins': None, 'binsize': None, 'cumulative': False,
290 'scale': 1.0, 'linestyle': '-'})
291 self.baseHist = BaseHistogram()
293 def __call__(self, metricValue, slicer, userPlotDict, fignum=None):
294 """
295 Histogram metricValue for all healpix points.
296 """
297 if slicer.slicerName != 'OpsimFieldSlicer':
298 raise ValueError('OpsimHistogram is for use with OpsimFieldSlicer.')
299 plotDict = {}
300 plotDict.update(self.defaultPlotDict)
301 plotDict.update(userPlotDict)
302 fignum = self.baseHist(metricValue, slicer, plotDict, fignum=fignum)
303 return fignum
306class BaseHistogram(BasePlotter):
307 def __init__(self):
308 self.plotType = 'Histogram'
309 self.objectPlotter = False
310 self.defaultPlotDict = {}
311 self.defaultPlotDict.update(baseDefaultPlotDict)
312 self.defaultPlotDict.update({'ylabel': 'Count', 'bins': None, 'binsize': None, 'cumulative': False,
313 'scale': 1.0, 'yaxisformat': '%.3f', 'linestyle': '-'})
315 def __call__(self, metricValueIn, slicer, userPlotDict, fignum=None):
316 """
317 Plot a histogram of metricValues (such as would come from a spatial slicer).
318 """
319 # Adjust metric values by zeropoint or normVal, and use 'compressed' version of masked array.
320 plotDict = {}
321 plotDict.update(self.defaultPlotDict)
322 plotDict.update(userPlotDict)
323 metricValue = applyZPNorm(metricValueIn, plotDict)
324 metricValue = metricValue.compressed()
325 # Toss any NaNs or infs
326 metricValue = metricValue[np.isfinite(metricValue)]
327 # Determine percentile clipped X range, if set. (and xmin/max not set).
328 if plotDict['xMin'] is None and plotDict['xMax'] is None:
329 if plotDict['percentileClip']:
330 plotDict['xMin'], plotDict['xMax'] = percentileClipping(metricValue,
331 percentile=plotDict['percentileClip'])
332 # Set the histogram range values, to avoid cases of trying to histogram single-valued data.
333 # First we try to use the range specified by a user, if there is one. Then use the data if not.
334 # all of this only works if plotDict is not cumulative.
335 histRange = [plotDict['xMin'], plotDict['xMax']]
336 if histRange[0] is None:
337 histRange[0] = metricValue.min()
338 if histRange[1] is None:
339 histRange[1] = metricValue.max()
340 # Need to have some range of values on the histogram, or it will fail.
341 if histRange[0] == histRange[1]:
342 warnings.warn('Histogram range was single-valued; expanding default range.')
343 histRange[1] = histRange[0] + 1.0
344 # Set up the bins for the histogram. User specified 'bins' overrides 'binsize'.
345 # Note that 'bins' could be a single number or an array, simply passed to plt.histogram.
346 if plotDict['bins'] is not None:
347 bins = plotDict['bins']
348 elif plotDict['binsize'] is not None:
349 # If generating a cumulative histogram, want to use full range of data (but with given binsize).
350 # .. but if user set histRange to be wider than full range of data, then
351 # extend bins to cover this range, so we can make prettier plots.
352 if plotDict['cumulative']:
353 if plotDict['xMin'] is not None:
354 # Potentially, expand the range for the cumulative histogram.
355 bmin = np.min([metricValue.min(), plotDict['xMin']])
356 else:
357 bmin = metricValue.min()
358 if plotDict['xMax'] is not None:
359 bmax = np.max([metricValue.max(), plotDict['xMax']])
360 else:
361 bmax = metricValue.max()
362 bins = np.arange(bmin, bmax + plotDict['binsize'] / 2.0, plotDict['binsize'])
363 # Otherwise, not cumulative so just use metric values, without potential expansion.
364 else:
365 bins = np.arange(histRange[0], histRange[1] + plotDict['binsize'] / 2.0, plotDict['binsize'])
366 # Catch edge-case where there is only 1 bin value
367 if bins.size < 2:
368 bins = np.arange(bins.min() - plotDict['binsize'] * 2.0,
369 bins.max() + plotDict['binsize'] * 2.0, plotDict['binsize'])
370 else:
371 # If user did not specify bins or binsize, then we try to figure out a good number of bins.
372 bins = optimalBins(metricValue)
373 # Generate plots.
374 fig = plt.figure(fignum, figsize=plotDict['figsize'])
375 ax = fig.add_subplot(plotDict['subplot'])
376 # Check if any data falls within histRange, because otherwise histogram generation will fail.
377 if isinstance(bins, np.ndarray):
378 condition = ((metricValue >= bins.min()) & (metricValue <= bins.max()))
379 else:
380 condition = ((metricValue >= histRange[0]) & (metricValue <= histRange[1]))
381 plotValue = metricValue[condition]
382 if len(plotValue) == 0:
383 # No data is within histRange/bins. So let's just make a simple histogram anyway.
384 n, b, p = plt.hist(metricValue, bins=50, histtype='step', cumulative=plotDict['cumulative'],
385 log=plotDict['logScale'], label=plotDict['label'],
386 color=plotDict['color'])
387 else:
388 # There is data to plot, and we've already ensured histRange/bins are more than single value.
389 n, b, p = plt.hist(metricValue, bins=bins, range=histRange,
390 histtype='step', log=plotDict['logScale'],
391 cumulative=plotDict['cumulative'],
392 label=plotDict['label'], color=plotDict['color'])
393 hist_ylims = plt.ylim()
394 if n.max() > hist_ylims[1]:
395 plt.ylim(top = n.max())
396 if n.min() < hist_ylims[0] and not plotDict['logScale']:
397 plt.ylim(bottom = n.min())
398 # Fill in axes labels and limits.
399 # Option to use 'scale' to turn y axis into area or other value.
401 def mjrFormatter(y, pos):
402 if not isinstance(plotDict['scale'], numbers.Number):
403 raise ValueError('plotDict["scale"] must be a number to scale the y axis.')
404 return plotDict['yaxisformat'] % (y * plotDict['scale'])
406 ax.yaxis.set_major_formatter(FuncFormatter(mjrFormatter))
407 # Set optional x, y limits.
408 if 'xMin' in plotDict:
409 plt.xlim(left=plotDict['xMin'])
410 if 'xMax' in plotDict:
411 plt.xlim(right=plotDict['xMax'])
412 if 'yMin' in plotDict:
413 plt.ylim(bottom=plotDict['yMin'])
414 if 'yMax' in plotDict:
415 plt.ylim(top=plotDict['yMax'])
416 # Set/Add various labels.
417 plt.xlabel(plotDict['xlabel'], fontsize=plotDict['fontsize'])
418 plt.ylabel(plotDict['ylabel'], fontsize=plotDict['fontsize'])
419 plt.title(plotDict['title'])
420 if plotDict['labelsize'] is not None:
421 plt.tick_params(axis='x', labelsize=plotDict['labelsize'])
422 plt.tick_params(axis='y', labelsize=plotDict['labelsize'])
423 # Return figure number
424 return fig.number
427class BaseSkyMap(BasePlotter):
428 def __init__(self):
429 self.plotType = 'SkyMap'
430 self.objectPlotter = False # unless 'metricIsColor' is true..
431 self.defaultPlotDict = {}
432 self.defaultPlotDict.update(baseDefaultPlotDict)
433 self.defaultPlotDict.update({'projection': 'aitoff', 'radius': np.radians(1.75), 'alpha': 1.0,
434 'plotMask': False, 'metricIsColor': False, 'cbar': True,
435 'raCen': 0.0, 'mwZone': True, 'bgcolor': 'gray'})
437 def _plot_tissot_ellipse(self, lon, lat, radius, ax=None, **kwargs):
438 """Plot Tissot Ellipse/Tissot Indicatrix
440 Parameters
441 ----------
442 lon : float or array_like
443 longitude-like of ellipse centers (radians)
444 lat : float or array_like
445 latitude-like of ellipse centers (radians)
446 radius : float or array_like
447 radius of ellipses (radians)
448 ax : Axes object (optional)
449 matplotlib axes instance on which to draw ellipses.
451 Other Parameters
452 ----------------
453 other keyword arguments will be passed to matplotlib.patches.Ellipse.
455 # The code in this method adapted from astroML, which is BSD-licensed.
456 # See http: //github.com/astroML/astroML for details.
457 """
458 # Code adapted from astroML, which is BSD-licensed.
459 # See http: //github.com/astroML/astroML for details.
460 ellipses = []
461 if ax is None:
462 ax = plt.gca()
463 for l, b, diam in np.broadcast(lon, lat, radius * 2.0):
464 el = Ellipse((l, b), diam / np.cos(b), diam, **kwargs)
465 ellipses.append(el)
466 return ellipses
468 def _plot_ecliptic(self, raCen=0, ax=None):
469 """
470 Plot a red line at location of ecliptic.
471 """
472 if ax is None:
473 ax = plt.gca()
474 ecinc = 23.439291 * (np.pi / 180.0)
475 ra_ec = np.arange(0, np.pi * 2., (np.pi * 2. / 360.))
476 dec_ec = np.sin(ra_ec) * ecinc
477 lon = -(ra_ec - raCen - np.pi) % (np.pi * 2) - np.pi
478 ax.plot(lon, dec_ec, 'r.', markersize=1.8, alpha=0.4)
480 def _plot_mwZone(self, raCen=0, peakWidth=np.radians(10.), taperLength=np.radians(80.), ax=None):
481 """
482 Plot blue lines to mark the milky way galactic exclusion zone.
483 """
484 if ax is None:
485 ax = plt.gca()
486 # Calculate galactic coordinates for mw location.
487 step = 0.02
488 galL = np.arange(-np.pi, np.pi + step / 2., step)
489 val = peakWidth * np.cos(galL / taperLength * np.pi / 2.)
490 galB1 = np.where(np.abs(galL) <= taperLength, val, 0)
491 galB2 = np.where(np.abs(galL) <= taperLength, -val, 0)
492 # Convert to ra/dec.
493 # Convert to lon/lat and plot.
494 ra, dec = _equatorialFromGalactic(galL, galB1)
495 lon = -(ra - raCen - np.pi) % (np.pi * 2) - np.pi
496 ax.plot(lon, dec, 'b.', markersize=1.8, alpha=0.4)
497 ra, dec = _equatorialFromGalactic(galL, galB2)
498 lon = -(ra - raCen - np.pi) % (np.pi * 2) - np.pi
499 ax.plot(lon, dec, 'b.', markersize=1.8, alpha=0.4)
501 def __call__(self, metricValueIn, slicer, userPlotDict, fignum=None):
502 """
503 Plot the sky map of metricValue for a generic spatial slicer.
504 """
505 if 'ra' not in slicer.slicePoints or 'dec' not in slicer.slicePoints:
506 errMessage = 'SpatialSlicer must contain "ra" and "dec" in slicePoints metadata.'
507 errMessage += ' SlicePoints only contains keys %s.' % (slicer.slicePoints.keys())
508 raise ValueError(errMessage)
509 plotDict = {}
510 plotDict.update(self.defaultPlotDict)
511 plotDict.update(userPlotDict)
512 metricValue = applyZPNorm(metricValueIn, plotDict)
514 fig = plt.figure(fignum, figsize=plotDict['figsize'])
515 # other projections available include
516 # ['aitoff', 'hammer', 'lambert', 'mollweide', 'polar', 'rectilinear']
517 ax = fig.add_subplot(plotDict['subplot'], projection=plotDict['projection'])
518 # Set up valid datapoints and colormin/max values.
519 if plotDict['plotMask']:
520 # Plot all data points.
521 mask = np.ones(len(metricValue), dtype='bool')
522 else:
523 # Only plot points which are not masked. Flip numpy ma mask where 'False' == 'good'.
524 good = ~metricValue.mask
526 # Add ellipses at RA/Dec locations - but don't add colors yet.
527 lon = -(slicer.slicePoints['ra'][good] - plotDict['raCen'] - np.pi) % (np.pi * 2) - np.pi
528 ellipses = self._plot_tissot_ellipse(lon, slicer.slicePoints['dec'][good],
529 plotDict['radius'], rasterized=True, ax=ax)
530 if plotDict['metricIsColor']:
531 current = None
532 for ellipse, mVal in zip(ellipses, metricValue.data[good]):
533 if mVal[3] > 1:
534 ellipse.set_alpha(1.0)
535 ellipse.set_facecolor((mVal[0], mVal[1], mVal[2]))
536 ellipse.set_edgecolor('k')
537 current = ellipse
538 else:
539 ellipse.set_alpha(mVal[3])
540 ellipse.set_color((mVal[0], mVal[1], mVal[2]))
541 ax.add_patch(ellipse)
542 if current:
543 ax.add_patch(current)
544 else:
545 # Determine color min/max values. metricValue.compressed = non-masked points.
546 clims = setColorLims(metricValue, plotDict)
547 # Determine whether or not to use auto-log scale.
548 if plotDict['logScale'] == 'auto':
549 if clims[0] > 0:
550 if np.log10(clims[1]) - np.log10(clims[0]) > 3:
551 plotDict['logScale'] = True
552 else:
553 plotDict['logScale'] = False
554 else:
555 plotDict['logScale'] = False
556 if plotDict['logScale']:
557 # Move min/max values to things that can be marked on the colorbar.
558 #clims[0] = 10 ** (int(np.log10(clims[0])))
559 #clims[1] = 10 ** (int(np.log10(clims[1])))
560 norml = colors.LogNorm()
561 p = PatchCollection(ellipses, cmap=plotDict['cmap'], alpha=plotDict['alpha'],
562 linewidth=0, edgecolor=None, norm=norml, rasterized=True)
563 else:
564 p = PatchCollection(ellipses, cmap=plotDict['cmap'], alpha=plotDict['alpha'],
565 linewidth=0, edgecolor=None, rasterized=True)
566 p.set_array(metricValue.data[good])
567 p.set_clim(clims)
568 ax.add_collection(p)
569 # Add color bar (with optional setting of limits)
570 if plotDict['cbar']:
571 cb = plt.colorbar(p, aspect=25, extendrect=True, orientation='horizontal',
572 format=plotDict['cbarFormat'])
573 # If outputing to PDF, this fixes the colorbar white stripes
574 if plotDict['cbar_edge']:
575 cb.solids.set_edgecolor("face")
576 cb.set_label(plotDict['xlabel'], fontsize=plotDict['fontsize'])
577 cb.ax.tick_params(labelsize=plotDict['labelsize'])
578 tick_locator = ticker.MaxNLocator(nbins=plotDict['nTicks'])
579 cb.locator = tick_locator
580 cb.update_ticks()
581 # Add ecliptic
582 self._plot_ecliptic(plotDict['raCen'], ax=ax)
583 if plotDict['mwZone']:
584 self._plot_mwZone(plotDict['raCen'], ax=ax)
585 ax.grid(True, zorder=1)
586 ax.xaxis.set_ticklabels([])
587 if plotDict['bgcolor'] is not None:
588 ax.set_facecolor(plotDict['bgcolor'])
589 # Add label.
590 if plotDict['label'] is not None:
591 plt.figtext(0.75, 0.9, '%s' % plotDict['label'], fontsize=plotDict['fontsize'])
592 if plotDict['title'] is not None:
593 plt.text(0.5, 1.09, plotDict['title'], horizontalalignment='center',
594 transform=ax.transAxes, fontsize=plotDict['fontsize'])
595 return fig.number
598class HealpixSDSSSkyMap(BasePlotter):
599 def __init__(self):
600 self.plotType = 'SkyMap'
601 self.objectPlotter = False
602 self.defaultPlotDict = {}
603 self.defaultPlotDict.update(baseDefaultPlotDict)
604 self.defaultPlotDict.update({'cbarFormat': '%.2f',
605 'raMin': -90, 'raMax': 90, 'raLen': 45,
606 'decMin': -2., 'decMax': 2.})
608 def __call__(self, metricValueIn, slicer, userPlotDict, fignum=None):
610 """
611 Plot the sky map of metricValue using healpy cartview plots in thin strips.
612 raMin: Minimum RA to plot (deg)
613 raMax: Max RA to plot (deg). Note raMin/raMax define the centers that will be plotted.
614 raLen: Length of the plotted strips in degrees
615 decMin: minimum dec value to plot
616 decMax: max dec value to plot
617 metricValueIn: metric values
618 """
619 plotDict = {}
620 plotDict.update(self.defaultPlotDict)
621 plotDict.update(userPlotDict)
622 metricValue = applyZPNorm(metricValueIn, plotDict)
623 norm = None
624 if plotDict['logScale']:
625 norm = 'log'
626 clims = setColorLims(metricValue, plotDict)
627 cmap = setColorMap(plotDict)
628 racenters = np.arange(plotDict['raMin'], plotDict['raMax'], plotDict['raLen'])
629 nframes = racenters.size
630 fig = plt.figure(fignum)
631 # Do not specify or use plotDict['subplot'] because this is done in each call to hp.cartview.
632 for i, racenter in enumerate(racenters):
633 if i == 0:
634 useTitle = plotDict['title'] + ' /n' + '%i < RA < %i' % (racenter - plotDict['raLen'],
635 racenter + plotDict['raLen'])
636 else:
637 useTitle = '%i < RA < %i' % (racenter - plotDict['raLen'], racenter + plotDict['raLen'])
638 hp.cartview(metricValue.filled(slicer.badval), title=useTitle, cbar=False,
639 min=clims[0], max=clims[1], flip='astro', rot=(racenter, 0, 0),
640 cmap=cmap, norm=norm, lonra=[-plotDict['raLen'], plotDict['raLen']],
641 latra=[plotDict['decMin'], plotDict['decMax']], sub=(nframes + 1, 1, i + 1), fig=fig)
642 hp.graticule(dpar=20, dmer=20, verbose=False)
643 # Add colorbar (not using healpy default colorbar because want more tickmarks).
644 ax = fig.add_axes([0.1, .15, .8, .075]) # left, bottom, width, height
645 # Add label.
646 if plotDict['label'] is not None:
647 plt.figtext(0.8, 0.9, '%s' % plotDict['label'])
648 # Make the colorbar as a seperate figure,
649 # from http: //matplotlib.org/examples/api/colorbar_only.html
650 cnorm = colors.Normalize(vmin=clims[0], vmax=clims[1])
651 # supress silly colorbar warnings
652 with warnings.catch_warnings():
653 warnings.simplefilter("ignore")
654 cb = mpl.colorbar.ColorbarBase(ax, cmap=cmap, norm=cnorm,
655 orientation='horizontal', format=plotDict['cbarFormat'])
656 cb.set_label(plotDict['xlabel'])
657 cb.ax.tick_params(labelsize=plotDict['labelsize'])
658 if norm == 'log':
659 tick_locator = ticker.LogLocator(numticks=plotDict['nTicks'])
660 cb.locator = tick_locator
661 cb.update_ticks()
662 if (plotDict['nTicks'] is not None) & (norm != 'log'):
663 tick_locator = ticker.MaxNLocator(nbins=plotDict['nTicks'])
664 cb.locator = tick_locator
665 cb.update_ticks()
666 # If outputing to PDF, this fixes the colorbar white stripes
667 if plotDict['cbar_edge']:
668 cb.solids.set_edgecolor("face")
669 fig = plt.gcf()
670 return fig.number
673def project_lambert(longitude, latitude):
674 """Project from RA,dec to plane
675 https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection
676 """
678 # flipping the sign on latitude goes north pole or south pole centered
679 r_polar = 2*np.cos((np.pi/2+latitude)/2.)
680 # Add pi/2 so north is up
681 theta_polar = longitude + np.pi/2
683 x = r_polar * np.cos(theta_polar)
684 y = r_polar * np.sin(theta_polar)
685 return x, y
688def draw_grat(ax):
689 """Draw some graticule lines on an axis
690 """
691 decs = np.radians(90.-np.array([20, 40, 60, 80]))
692 ra = np.radians(np.arange(0, 361, 1))
693 for dec in decs:
694 temp_dec = ra*0+dec
695 x, y = project_lambert(ra, temp_dec)
696 ax.plot(x, y, 'k--', alpha=0.5)
698 ras = np.radians(np.arange(0, 360+45, 45))
699 dec = np.radians(90.-np.arange(0, 81, 1))
700 for ra in ras:
701 temp_ra = dec*0 + ra
702 x, y = project_lambert(temp_ra, dec)
703 ax.plot(x, y, 'k--', alpha=0.5)
705 for dec in decs:
706 x, y = project_lambert(np.radians(45.), dec)
707 ax.text(x, y, '%i' % np.round(np.degrees(dec)))
709 return ax
711class LambertSkyMap(BasePlotter):
712 """
713 Use basemap and contour to make a Lambertian projection.
714 Note that the plotDict can include a 'basemap' key with a dictionary of
715 arbitrary kwargs to use with the call to Basemap.
716 """
718 def __init__(self):
719 self.plotType = 'SkyMap'
720 self.objectPlotter = False
721 self.defaultPlotDict = {}
722 self.defaultPlotDict.update(baseDefaultPlotDict)
723 self.defaultPlotDict.update({'basemap': {'projection': 'nplaea', 'boundinglat': 1, 'lon_0': 180,
724 'resolution': None, 'celestial': False, 'round': False},
725 'levels': 200, 'cbarFormat': '%i', 'norm': None})
727 def __call__(self, metricValueIn, slicer, userPlotDict, fignum=None):
729 if 'ra' not in slicer.slicePoints or 'dec' not in slicer.slicePoints:
730 errMessage = 'SpatialSlicer must contain "ra" and "dec" in slicePoints metadata.'
731 errMessage += ' SlicePoints only contains keys %s.' % (slicer.slicePoints.keys())
732 raise ValueError(errMessage)
734 plotDict = {}
735 plotDict.update(self.defaultPlotDict)
736 plotDict.update(userPlotDict)
738 metricValue = applyZPNorm(metricValueIn, plotDict)
739 clims = setColorLims(metricValue, plotDict)
740 # Calculate the levels to use for the contour
741 if np.size(plotDict['levels']) > 1:
742 levels = plotDict['levels']
743 else:
744 step = (clims[1] - clims[0]) / plotDict['levels']
745 levels = np.arange(clims[0], clims[1] + step, step)
747 fig = plt.figure(fignum, figsize=plotDict['figsize'])
748 ax = fig.add_subplot(plotDict['subplot'])
750 x, y = project_lambert(slicer.slicePoints['ra'], slicer.slicePoints['dec'])
751 # Contour the plot first to remove any anti-aliasing artifacts. Doesn't seem to work though. See:
752 # http: //stackoverflow.com/questions/15822159/aliasing-when-saving-matplotlib\
753 # -filled-contour-plot-to-pdf-or-eps
754 # tmpContour = m.contour(np.degrees(slicer.slicePoints['ra']),
755 # np.degrees(slicer.slicePoints['dec']),
756 # metricValue.filled(np.min(clims)-1), levels, tri=True,
757 # cmap=plotDict['cmap'], ax=ax, latlon=True,
758 # lw=1)
760 # Set masked values to be below the lowest contour level.
761 if plotDict['norm'] == 'log':
762 z_val = metricValue.filled(np.min(clims)-0.9)
763 norm = colors.LogNorm(vmin=z_val.min(), vmax=z_val.max())
764 else:
765 norm = plotDict['norm']
766 tcf = ax.tricontourf(x, y, metricValue.filled(np.min(clims)-0.9), levels,
767 cmap=plotDict['cmap'], norm=norm)
769 ax = draw_grat(ax)
771 ax.set_xticks([])
772 ax.set_yticks([])
773 alt_limit = 10.
774 x, y = project_lambert(0, np.radians(alt_limit))
775 max_val = np.max(np.abs([x, y]))
776 ax.set_xlim([-max_val, max_val])
777 ax.set_ylim([-max_val, max_val])
779 # Try to fix the ugly pdf contour problem
780 for c in tcf.collections:
781 c.set_edgecolor("face")
783 cb = plt.colorbar(tcf, format=plotDict['cbarFormat'])
784 cb.set_label(plotDict['xlabel'])
785 if plotDict['labelsize'] is not None:
786 cb.ax.tick_params(labelsize=plotDict['labelsize'])
787 # Pop in an extra line to raise the title a bit
788 ax.set_title(plotDict['title']+'\n ')
789 # If outputing to PDF, this fixes the colorbar white stripes
790 if plotDict['cbar_edge']:
791 cb.solids.set_edgecolor("face")
792 return fig.number