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