#! /usr/bin/env python
"""
**APlot** --- Standardized plot generator.
------------------------------------------
This module defines the APlot class.
"""
from AbstractPlot import AbstractPlot
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.widgets import RadioButtons
import PlotControl
import numpy.ma as ma
import utils
from admit.util.AdmitLogging import AdmitLogging as logging
# import mpld3 # needs matplotlib 1.3
[docs]class APlot(AbstractPlot):
"""
Basic ADMIT plotter that uses matplotlib calls to create figures.
- simple parser of extra layout commands on top of general style
- plotter, scatter or histogram
line x0 y0 x1 y1 ...
point x0 y0 ...
See Also
--------
admit.util.AbstractPlot
"""
def __init__(self,pmode=None,ptype=None,figno=None,abspath=""):
# @todo figno given here really must be figno-1 since every
# method increments it. probably should start static at 1 and
# increment AFTER plt is made.
self.version = "28-mar-2016"
AbstractPlot.__init__(self,pmode,ptype,figno,abspath)
[docs] def parse(self,lines):
"""APlot parser to pass along some matplotlib commands
lines : list
A list of matplotlib commands to be added to the current figure.
Currently supported:
grid
display a grid on the plot
axis equal
ensure axis scales to be the same, if you want squares and
circles to come out as such.
"""
for line in lines:
if line=='grid':
plt.grid()
elif line=='axis equal':
plt.axis('equal')
else:
print "Skipping unknown APlot command: %s" % line
#@todo how is this actually used? a plot instance must know when
# to call final() ?!?
[docs] def final(self):
"""final signoff from APlot
This would be needed for plotmode=PlotControl.SHOW_AT_END, but
this does not seem to work (anymore)
"""
if self._plot_mode == PlotControl.NOPLOT:
return
if self._plot_mode == PlotControl.SHOW_AT_END:
plt.ioff()
plt.show()
[docs] def scatter(self,x,y,title=None,figname=None,xlab=None,ylab=None,color=None,size=None,cmds=None,thumbnail=True, xrange=None, yrange=None):
"""Scatter plot of multiple columns against one column
Parameters
----------
x : numpy array
X axis (abscissa) values
y : numpy array
Y axis (ordinate) values
title : str
Title string for plot
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
xlab : str
X axis label
ylab : str
Y axis label
color : str
Color as defined in matplotlib.Axes.scatter() argument 'c'.
size : numpy array or scalar
Point size as defined matplotlib.Axes.scatter() argument,
It is a size in points^2. It is a scalar
or an array of the same length as x and y.
cmds :
add matplot lib commands. See parse()
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
xrange : tuple
X axis range (xmin,xmax). Default:None meaning show full range of data
yrange: tuple
Y axis range, (ymin,ymax). Default:None meaning show full range of data
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
if color==None and size==None:
ax1.scatter(x,y)
elif color==None:
ax1.scatter(x,y,s=size)
elif size==None:
ax1.scatter(x,y,c=color)
else:
ax1.scatter(x,y,c=color,s=size)
if title: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
if ylab: ax1.set_ylabel(ylab)
if xrange != None:
ax1.set_xlim(xrange)
if yrange != None:
ax1.set_ylim(yrange)
if cmds != None:
self.parse(cmds)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
# mpld3.save_html(fig, "mpld3.html")
plt.close()
[docs] def plotter(self,x,y,title=None,figname=None,xlab=None,ylab=None,xrange=None,yrange=None,segments=None,labels=[],histo=False,thumbnail=True,x2range=None,y2range=None,x2lab=None,y2lab=None):
"""Simple plotter of multiple columns against one column, optionally in histogram style
Parameters
----------
x : numpy array
X axis (abscissa) values
y : list of numpy array
Y axis (ordinate) values
title : str
Title string for plot
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
xlab : str
X axis label
ylab : str
Y axis label
xrange : tuple
X axis range (xmin,xmax). Default:None meaning show full range of data
yrange: tuple
Y axis range, (ymin,ymax). Default:None meaning show full range of data
segments : list
list of segment end points pairs for overlaying horizontal
segment lines. e.g. [[0,2],[5,6.3],[10.1,14.27]]
labels : list
labels for the plot traces. In general, there should be one per plot trace.
histo : boolean
Histogram style? not implemented yet
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
x2range: tuple
If given and non-empty, label the upper x axis with a second
range. Useful for, e.g. plotting frequency and velocity on
the same plot. Default: None, no second x axis
y2range: tuple
If given and non-empty, label the right y axis with a second
range. Default: None, no second y axis
x2lab : str
second X axis label
y2lab : str
second Y axis label
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# if filename: plt.ion()
#plt.ion()
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
if len(labels) > 0:
for (yi,li) in zip(y,labels):
ax1.plot(x,yi,label=li)
# @todo try loc='lower center'
# smaller font
ax1.legend(loc='best',prop={'size':8})
else:
for yi in y:
ax1.plot(x,yi)
ylim = ax1.get_ylim()[1]/3.0
if segments:
for s in segments:
#print "SEGMENTS",s
#ax1.plot([s[0],s[1]],[s[2],s[3]])
if len(s) == 2:
#print "plotting ",[s[0],s[1]],[0.01,0.01]
ax1.plot([s[0],s[1]],[ylim,ylim],'k-')
else:
#print "plotting",[s[0],s[1]],[s[2],s[3]]
ax1.plot([s[0],s[1]],[s[2],s[3]],'k-')
if title and not x2range: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
if ylab: ax1.set_ylabel(ylab)
if xrange: ax1.set_xlim(xrange)
if yrange: ax1.set_ylim(yrange)
if x2range:
ax2 = ax1.twiny()
ax2.set_xlim(x2range)
if x2lab: ax2.set_xlabel(x2lab)
# if 2nd x-axis specified, then title will be
# placed interior to plot
if title: ax1.text(.5,.92,title, horizontalalignment='center', transform=ax1.transAxes)
if y2range:
ax3 = ax1.twinx()
ax3.set_ylim(y2range)
if y2lab: ax3.set_ylabel(y2lab)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def multiplotter(self,x,y,title=None,figname=None,xlab=None,ylab=None,xrange=None,yrange=None,labels=[],thumbnail=True,x2range=None,y2range=None,x2lab=None,y2lab=None):
"""Plotter of multiple x against multiple y as traces on same plot.
Parameters
----------
x : list of numpy array
X axis (abscissa) values
y : list of numpy array
Y axis (ordinate) values
title : str
Title string for plot
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
xlab : str
X axis label
ylab : str
Y axis label
xrange : tuple
X axis range (xmin,xmax). Default:None meaning show full range of data
yrange: tuple
Y axis range, (ymin,ymax). Default:None meaning show full range of data
labels : list
labels for the plot traces. In general, there should be one per plot trace.
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
x2range: tuple
If given and non-empty, label the upper x axis with a second
range. Useful for, e.g. plotting frequency and velocity on
the same plot. Default: None, no second x axis
y2range: tuple
If given and non-empty, label the right y axis with a second
range. Default: None, no second y axis
x2lab : str
second X axis label
y2lab : str
second Y axis label
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
if len(x) != len(y):
raise Exception("Input x and y arrays are not the same length [%d,%d]"%(len(x),len(y)))
# if filename: plt.ion()
#plt.ion()
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
if len(labels) > 0:
for (xi,yi,li) in zip(x,y,labels):
ax1.plot(xi,yi,label=li)
# @todo try loc='lower center'
# smaller font
ax1.legend(loc='best',prop={'size':8})
else:
for i in len(y):
ax1.plot(x[i],y[i])
ylim = ax1.get_ylim()[1]/3.0
if title and not x2range: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
if ylab: ax1.set_ylabel(ylab)
if xrange: ax1.set_xlim(xrange)
if yrange: ax1.set_ylim(yrange)
if x2range:
ax2 = ax1.twiny()
ax2.set_xlim(x2range)
if x2lab: ax2.set_xlabel(x2lab)
# if 2nd x-axis specified, then title will be
# placed interior to plot
if title: ax1.text(.5,.92,title, horizontalalignment='center', transform=ax1.transAxes)
if y2range:
ax3 = ax1.twinx()
ax3.set_ylim(y2range)
if y2lab: ax3.set_ylabel(y2lab)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def segplotter(self,x,y,title="",figname="",xlab="",ylab="",segments=[],cutoff=0.0, continuum=None, thumbnail=True):
"""Plotter for spectral line segments. Based on plotter, but extended to create a legend.
Parameters
----------
x : numpy array
X axis (abscissa) values
y : numpy array
Y axis (ordinate) values
title : str
Title string for plot
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
xlab : str
X axis label
ylab : str
Y axis label
segments : list
list of segment end points pairs for overlaying horizontal segment lines.
e.g. [[0,2],[5,6.3],[10.1,14.27]]
cutoff : float or list of floats
y-axis value(s) at which to draw the cutoff-level indicator (horizontal line)
continuum : float or list of floats
y-axis value(s) at which to draw the continuum "Mean/Baseline" indicator (horizontal line)
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
# reverse the numpy arrays
x = x[::-1].copy()
y = y[::-1].copy()
if continuum is not None:
c = continuum[::-1].copy()
fig = plt.figure(APlot.figno, figsize=(18,12), dpi=80)
ax1 = fig.add_subplot(1,1,1)
ax1.set_xlim([ma.min(x) - 0.0001,ma.max(x) + 0.0001])
ax1.plot(x,y,'k-')
# @todo temporary: turn on red plusses for debug
ax1.plot(x,y,'r+')
#ax1.plot([x[0],x[-1]],[0.0,0.0],'b-')
first = True
neg = False
if np.max(y) < 0.:
neg = True
if isinstance(cutoff, list) or isinstance(cutoff, np.ndarray) or isinstance(cutoff, ma.masked_array):
ax1.plot(x,cutoff[::-1].copy(),'g-',label='Cutoff level')
else:
ax1.plot([x[0],x[-1]],[cutoff,cutoff],'g-',label='Cutoff level')
ylim = ax1.get_ylim()[1]/3.0
if neg:
ylim = ax1.get_ylim()[0]/2.
yseg = ylim/15.0
ncol = 0
if continuum is not None:
ax1.plot(x, c, 'b-', label='Mean/Baseline')
ncol = 1
else:
ax1.plot([x[0],x[-1]],[0.0,0.0],'b-')
if segments:
for s in segments:
if first:
ax1.plot([s[0],s[1]],[ylim,ylim],'r-',label="Potential Line")
first = False
else:
ax1.plot([s[0],s[1]],[ylim,ylim],'r-')
ax1.plot([s[0],s[0]],[ylim-yseg,ylim+yseg],'r-')
ax1.plot([s[1],s[1]],[ylim-yseg,ylim+yseg],'r-')
ax1.xaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter(useOffset=False))
ax1.set_title(title)
ax1.set_xlabel(xlab)
ax1.set_ylabel(ylab)
ax1.legend(loc="lower center",ncol=2+ncol)
if figname != "":
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def histogram(self,columns,title=None,figname=None,xlab=None,xrange=None,ylab="#",bins=80,thumbnail=True):
"""Simple histogram of one or more columns
Parameters
----------
columns : numpy array or list of numpy arrays
X axis (abscissa) values
title : str
Title string for plot
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
xlab : str
X axis label
xrange: tuple
X axis range, (xmin,xmax)
ylab : str
Y axis label
bins: int
Number of histogram bins. Default:80
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# if filename: plt.ion()
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
if isinstance(columns,list):
for xi in columns:
ax1.hist(xi,bins=bins,range=xrange)
# handle the case of just putting in a single nparray
# (old behavior of mkhisto.)
else:
if len(columns) == 0:
# PJT debug: handle if no data, fake it
ax1.hist(np.array([0,1,2,3]),bins=bins)
else:
ax1.hist(columns,bins=bins,range=xrange)
if title: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
ax1.set_ylabel(ylab)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def hisplot(self,x,title=None,figname=None,xlab=None,range=None,bins=80,gauss=None,thumbnail=True):
"""simple histogram of one column with optional overlayed gaussfit
This plot could be merged with histogram()
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# if filename: plt.ion()
# better example: http://matplotlib.org/examples/statistics/histogram_demo_features.html
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
if range == None:
h=ax1.hist(x,bins=bins,range=range)
if title: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
ax1.set_ylabel("#")
if gauss != None: # overplot a gaussian
if len(gauss) == 3:
m = gauss[0] # mean
s = gauss[1] # std
a = gauss[2] # amp
elif len(gauss) == 2:
m = gauss[0] # mean
s = gauss[1] # std
a = max(h[0]) # match peak value in histogram
else:
print "bad gauss estimator"
s = -1.0
#print "PJT: GaussPlot(%g,%g,%g)" % (m,s,a)
d = s/10.0
if d > 0.0:
if range == None:
xmin = x.min()
xmax = x.max()
else:
xmin = range[0]
xmax = range[1]
#print "PJT: GaussRange(%g,%g,%g)" % (xmin,xmax,d)
nx = int((xmax-xmin)/d)
if nx > 0 and nx < 10000:
# don't plot the gauss if it's too narrow
gx = np.arange(xmin,xmax,d)
arg = (gx-m)/s
gy = a * np.exp(-0.5*arg*arg)
ax1.plot(gx,gy)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def map1(self,data,title=None,figname=None,xlab=None,ylab=None,range=None,
contours=None,cmap='hot',segments=None,circles=None,
thumbnail=True,zoom=1,star=None):
"""
display map; horrible hack, the caller should call np.flipud(np.rot90()) since
casa and numpy do not have their axes in the same order.
We cannot call casa here, since APlot needs to stay casa agnostic.
To be resolved.
data: a classic numpy array, i.e. data[ny][nx] where we want data[0][0]
in the lower left corner
segments [x0,x1,y0,y1]
star [x,y]
See also casa.viewer() calls in e.g. Moment_AT
"""
if self._plot_mode == PlotControl.NOPLOT:
return
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
# Zoom calculation. (n= X direction; m= Y direction)
# @todo for zoom>1 star and circles not plotted right
m, n = data.shape
m2 = m/2
n2 = n/2
m0 = m2-m2/zoom # Y direction
m1 = m2+m2/zoom
n0 = n2-n2/zoom # X direction
n1 = n2+n2/zoom
#logging.info("type(data) %s m,n,m0,n0,m1,n1 %g %g %g %g %g %g" % (type(data),m,n,m0,n0,m1,n1))
if segments:
for s in segments:
ax1.plot([s[0]-n0,s[1]-n0],[s[2]-m0,s[3]-m0],c='skyblue')
# ax1.plot([s[0],s[1]],[s[2],s[3]],c='skyblue')
if star:
ax1.plot(star[0],star[1],'*',c='white')
if circles:
# @todo awkward, these are closes circles, we want open
for c in circles:
circ = plt.Circle((c[0]-m0,c[1]-n0),radius=c[2], color='green')
ax1.add_patch(circ)
# plt.plot([c[0]], [c[1]], 'g.', markersize=c[2])
if title: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
if ylab: ax1.set_ylabel(ylab)
ax1.tick_params(axis='both',color='white',width=1)
# Note this (inadvertently) can change the axis order if m0>m1 or n0>n1!
zoom = data[m0:m1,n0:n1]
# logging.info("data[0,0] %g data[m1,n1] %g zoom[0,0] %g zoom[m1,n1] %g" % (data[0,0],data[m1-1,n1-1],zoom[0,0],zoom[m1-1,n1-1]))
# print("Zoom==data? %s " % np.array_equal(zoom,data) )
# zoom = data
if range == None:
alplot = ax1.imshow(zoom, origin='lower')
elif len(range) == 1:
alplot = ax1.imshow(zoom, origin='lower', vmin = range[0])
elif len(range) == 2:
alplot = ax1.imshow(zoom, origin='lower', vmin = range[0], vmax = range[1])
alplot.set_cmap(cmap)
if contours != None:
ax1.contour(zoom, contours, colors='g')
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
[docs] def summaryspec(self, stat, spec, pvc, figname, lines=[], thumbnail=True, force=[]):
""" Method to plot a summary of all spectra with overlayed id's. All spectra
are in S/N units.
Parameters
----------
stat : list
List of Spectrum class holding the CubeStats based spectrum
spec : list
List of Spectrum classes, each holding the CubeSpectrum based
spectrum
pvc : Spectrum instance
Instance of Spectrum class holding the PVCorr based spectrum
figname : str
The name of the output figure
lines : list
List of LineData objects, one for each detected transition or segment
thumbnail : bool
If True then generate a thumbnail of the image. Default: True
force : list
List of LineData objects, one for each forced line id.
Default: []
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# get the peak value
#pk = max(y)
#pm = min(y)
# do the plot and set the labels
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno, figsize=(12,12), dpi=80)
ax1 = fig.add_subplot(1,1,1)
#ax1.plot(x,y,"-")
offset = 0
pm = 0
pk = 0
fm = 0.0
fx = 0.0
delta = 0
offset = 0.0
#
# Determines frequency delta in the presence of masked values.
def findDelta(freq):
for i in range(len(freq)-1):
if not freq.mask[i] and not freq.mask[i+1]:
return freq.mask[i+1]-freq.mask[i]
return 0.0
#
for i in range(len(stat)):
mult = 1.
addon = " Max"
if i == 1:
mult = -1.
addon = " Min"
ax1.plot(stat[i].freq(), offset + (mult * stat[i].spec()), '-', label="CubeStat" + addon)
pm = ma.min(stat[i].spec())
pk = ma.max(stat[i].spec())
fm = ma.min(stat[i].freq())
fx = ma.max(stat[i].freq())
delta = abs(findDelta(stat[i].freq()))
ax1.plot([fm, fx], [offset, offset], 'k-')
offset += max(ma.max(stat[i].spec()) + 1., 4.0)
for i in range(len(spec)):
sp = (spec[i].spec()/spec[i].noise())
ax1.plot(spec[i].freq(), sp + offset, '-', label="CubeSpec%i" % (i))
pm = min(pm, min(sp) + offset)
pk = max(pk, max(sp) + offset)
fm = max(fm, ma.min(spec[i].freq()))
fx = max(fx, ma.max(spec[i].freq()))
ax1.plot([fm, fx], [offset, offset], 'k-')
offset += max(ma.max(sp) + 1., 4.0)
delta = abs(findDelta(spec[i].freq()))
if pvc is not None:
sp = (pvc.spec()/pvc.noise())
pm = min(pm, min(sp) + offset)
pk = max(pk, max(sp) + offset)
fm = max(fm, ma.min(pvc.freq()))
fx = max(fx, ma.max(pvc.freq()))
delta = abs(findDelta(pvc.freq()))
ax1.plot(pvc.freq(), sp + offset, '-', label="PVCorr")
ax1.plot([fm, fx], [offset, offset], 'k-')
ax1.set_ylabel("Signal/Noise")
ax1.set_xlabel("Frequency (GHz)")
ax1.set_title("Summary Plot")
# reset the plot y axis to be a bit taller
#ax1.set_ylim(ymax=pk*1.2)
# make sure we can read the frequencies
ax1.xaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter(useOffset=False))
# label each line
step = (pk-pm)/20.0
place = pm + (pk-pm)/5.0
first = True
ylim = ax1.get_ylim()[1]
ax1.set_ylim([pm - 1.0, ylim * 1.2])
ax1.set_xlim([fm - (2.0 * delta), fx + (2.0 * delta)])
size = fig.get_size_inches()*fig.dpi
ylim = ax1.get_ylim()
first = True
for l in lines:
extra = ""
# label
if l.getkey("blend") != 0:
extra += "*"
frqs = [l.getkey("frequency")]
ax1.plot([min(frqs), max(frqs)], [pk*.92, pk*.92], 'k-')
ax1.plot([min(frqs), max(frqs)], [place-step/2.0,place-step/2.0], 'k-')
ax1.text(l.getkey("frequency"),0.0,l.getkey("uid") + extra,withdash=True,rotation='vertical',dashdirection=1,dashlength=size[1]*0.7,dashrotation=90)
if l.getfstart() != 0 and l.getfend() != 0:
if first:
ax1.add_patch(patches.Rectangle((l.getfstart(), ylim[0]), l.getfend() - l.getfstart(), ylim[1] - ylim[0], alpha=0.1, ec='none', fc='blue', label="Channel Range"))
first = False
else:
ax1.add_patch(patches.Rectangle((l.getfstart(), ylim[0]), l.getfend() - l.getfstart(), ylim[1] - ylim[0], alpha=0.1, ec='none', fc='blue'))
first = True
for l in force:
ax1.text(l.getkey("frequency"),0.0,l.getkey("uid"),withdash=True,rotation='vertical',dashdirection=1,dashlength=size[1]*0.7,dashrotation=90,color='green')
if l.getfstart() != 0 and l.getfend() != 0:
if first:
ax1.add_patch(patches.Rectangle((l.getfstart(), ylim[0]), l.getfend() - l.getfstart(), ylim[1] - ylim[0], alpha=0.1, ec='none', fc='green', label="Forced Channel Range"))
first = False
else:
ax1.add_patch(patches.Rectangle((l.getfstart(), ylim[0]), l.getfend() - l.getfstart(), ylim[1] - ylim[0], alpha=0.1, ec='none', fc='green'))
ncol = 2
box = ax1.get_position()
ax1.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])
# Put a legend below current axis
ax1.legend(loc='upper center', bbox_to_anchor=(0.5, -0.10),
fancybox=True, shadow=True, ncol=ncol)
#ax1.legend(loc="lower center",ncol=3)
# finally, if a reference line was given, label the top axis in km/s as well
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
#print self._figurefiles[APlot.figno]
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
[docs] def makespec(self, x, y, cutoff, figname, title="", xlabel="", lines=[],
force=[], blends=[], continuum=None, ylabel=None,
thumbnail=True, references={}, refline=None, chan=None):
""" Plots a spectrum, overlaying known spectral and reference lines.
Parameters
----------
x : list
The x data to plot (usually freq or velocity).
y : list
The spectrum to plot.
cutoff : float or numpy array
A horizontal line at this level will be plotted
figname : str
Root of output file name. An extension matching the plot
type will be appended. For instance, for, figname='fig'
and plottype=PlotControl.PNG, the output file is 'fig.png'
title : str
The title to put at the top.
Default: ""
xlabel : str
Label along the X axis
lines : list
A list of LineData objects, one for each detected transition.
The labels and channel ranges are plotted from this
information.
force : list
A list of LineData objects for forced lines. The labels and
channel ranges are plotted from this information.
blends : list
A list of LineData objects for blended lines. The labels and
channel ranges are plotted from this information.
references : dictionary
A dictionary of frequencies and reference line names to be
overplotted. This can be useful if you want to plot some well
known, but possibly not detected, lines.
Default: empty
continuum : list or numpy array?
Matching to the size of the input spectrum, this is
the continuum of the spectrum.
Default: None
ylabel : str
Label along the Y axis.
Default: None (no label)
thumbnail : boolean
If True, create a thumbnail when creating an output figure.
Thumbnails will have '_thumb' appended for file root.
For instance, if the output file is 'fig.jpg', the thumbnail
will be 'fig_thumb.jpg'
references : dict
Dictionary of reference lines to plot. The keys are the
frequencies and the values are the labels to place. Default: {}
refline : float
If given, this is the reference frequency for which VLSR = 0.0,
the top axis will then be labeled in KM/S. Otherwise the middle
of the plot is chosen to be VLSR = 0.0.
Default: None (use the center of the spectrum)
chan : list of int, optional
Channel numbers corresponding to `x`
(default: if `None`, use index number).
Returns
-------
None
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# get the peak value
pk = ma.max(y)
pm = ma.min(y)
# do the plot and set the labels
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno, figsize=(21,14), dpi=72)
# X-axis frequency limits.
# We have to step through the masked arrays manually to make sure
# the channel and frequency axes remain properly aligned.
axpad = 0.04
for i in range(len(x)):
if not x.mask[i]:
xmin = x[i]
cmin = chan[i]
break
#
for i in range(len(x)-1,0,-1):
if not x.mask[i]:
xmax = x[i]
cmax = chan[i]
break
#
if xmin > xmax:
xmin, xmax = xmax, xmin
cmin, cmax = cmax, cmin
#
fmin = xmin-axpad*(xmax-xmin)
fmax = xmax+axpad*(xmax-xmin)
# Make ax1 the "twin" so that the tracker displays its coordinates.
ax2 = fig.add_subplot(1,1,1)
ax1 = ax2.twiny()
ax1.xaxis.tick_bottom()
ax1.xaxis.set_label_position('bottom')
ax1.yaxis.set_visible(True)
ax2.xaxis.tick_top()
ax2.xaxis.set_label_position('top')
ax2.yaxis.set_visible(False)
plt.subplots_adjust(bottom=0.15)
# Configure minor ticks to display channel number.
# Display every channel up to 1000 channels; subsample after that.
# Labels are subsampled sooner to avoid excessive clutter.
maxloc = 1000
maxlab = 500
tickcolor = 'g'
if chan is None:
chan = range(len(x))
tickcolor = 'r'
locstride = (len(chan)+maxloc-1)/maxloc
labstride = (locstride*maxloc)/maxlab
matplotlib.ticker.Locator.MAXTICKS = 2*(maxloc+1)
# Preliminary tick formatting.
minorLabels = []
minorLocations = []
for i in range(0,len(chan),locstride):
minorLabels.append('' if i%labstride else str("%.0f" % chan[i]))
minorLocations.append(x[i])
#
minorFormatter = matplotlib.ticker.FixedFormatter(minorLabels)
ax1.xaxis.set_minor_formatter(minorFormatter)
#
minorLocator = matplotlib.ticker.FixedLocator(minorLocations)
#
majstride = 5
majorLocator = matplotlib.ticker.MaxNLocator(10*majstride)
ax1.xaxis.set_major_locator(majorLocator)
ax1.xaxis.set_minor_locator(minorLocator)
ax1.set_xlim(fmin, fmax)
#print "plotting\n\n",x,"\n\n"
axlines = ax1.plot(x,y,"-", zorder=4)
# @todo temporary: turn on red plusses for debug for spectra < 1000 points
axlines += ax1.plot(x,y,"r+")
# Cutoff line. Force a list for use with minor tick guide lines.
if not hasattr(cutoff, "__len__"): cutoff = [cutoff for xi in x]
axlines += ax1.plot(x,cutoff,label='Cutoff level')
#df = cutoff - continuum
#ax1.plot(x,continuum-df)
# Finalize minor tick formatting.
ax1.xaxis.set_tick_params(which='minor', colors=tickcolor,
labelsize=1, pad=1, width=0.25)
minors = ax1.xaxis.get_minor_ticks()
msize = minors[0].tick1line.get_markersize()
ymin = ax1.get_ylim()[0]
guides = []
for i in range(len(minors)):
minors[i].tick2On = False
if i%(labstride/locstride):
# Shorten unlabeled minor ticks.
minors[i].tick1line.set_markersize(msize/2)
for i in range(0,len(x),labstride):
# Draw guide lines from labeled minor ticks.
guides += ax1.plot([x[i], x[i]], [ymin, cutoff[i]],
color='0.7', ls='-', lw=0.05, zorder=0)
for label in ax1.xaxis.get_ticklabels(minor=True):
label.set_rotation(90)
# Finalize major tick formatting.
majTicks = ax1.xaxis.get_major_ticks()
majSize = majTicks[0].tick1line.get_markersize()
for i in range(len(majTicks)):
if i%majstride:
majTicks[i].label1On = False
majTicks[i].tick1line.set_markersize(0.8*majSize)
else:
majTicks[i].tick1line.set_markersize(1.2*majSize)
ncol = 2
if continuum is not None:
axlines += ax1.plot(x,continuum,'c-',label='Mean/Baseline')
ncol += 1
ax1.set_xlabel(xlabel)
if ylabel == None:
ax1.set_ylabel("Intensity")
else:
ax1.set_ylabel(ylabel)
plt.title(title, y=1.04)
# Set Y-axis limits (leave room for line IDs, include zero if close).
ymin = pm-axpad*(pk-pm) if pm < 0 or pm-2*axpad*(pk-pm) > 0 else 0.0
ax1.set_ylim(ymin=ymin, ymax=pk+0.20*(pk-pm))
# make sure we can read the frequencies
ax1.xaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter(useOffset=False))
# label each line
step = (pk-pm)/20.0
place = pm + (pk-pm)/5.0
first = True
seglines = []
axtexts = []
if isinstance(lines,dict): lines = lines.values()
for l in lines:
extra = ""
# label
if l.getkey("blend") != 0:
extra += "*"
frqs = [l.getkey("frequency")]
for b in blends:
if b.getkey("blend") == l.getkey("blend"):
frqs.append(b.getkey("frequency"))
axtexts.append(ax1.text(b.getkey("frequency"),pk*.85,"",withdash=True,rotation='vertical',dashdirection=1,dashlength=50,dashrotation=90))
seglines += ax1.plot([b.getkey("frequency"),b.getkey("frequency")],[place-step/2.0,place+step/2.0],'k-')
seglines += ax1.plot([min(frqs), max(frqs)], [pk*.92, pk*.92], 'k-')
seglines += ax1.plot([min(frqs), max(frqs)], [place-step/2.0,place-step/2.0], 'k-')
axtexts.append(ax1.text(l.getkey("frequency"),pk*.85,l.getkey("uid")+extra,withdash=True,rotation='vertical',dashdirection=1,dashlength=50,dashrotation=90))
# black tickmark
seglines += ax1.plot([l.getkey("frequency"),l.getkey("frequency")],[place-step/2.0,place+step/2.0],'k-')
# red segment
if first:
seglines += ax1.plot([l.getfstart(),l.getfend()],[place,place],'r-',label="Chan. Ranges")
first = False
else:
seglines += ax1.plot([l.getfstart(),l.getfend()],[place,place],'r-')
seglines += ax1.plot([l.getfstart(),l.getfstart()],[place-step,place+step],'r-')
seglines += ax1.plot([l.getfend(),l.getfend()],[place-step,place+step],'r-')
first = True
for l in force:
axtexts.append(ax1.text(l.getkey("frequency"),pk*.85,l.getkey("uid"),withdash=True,rotation='vertical',dashdirection=1,dashlength=50,dashrotation=90,color='green'))
# green tickmark
seglines += ax1.plot([l.getkey("frequency"),l.getkey("frequency")],[place-step/2.0,place+step/2.0],'g-')
# green segment
if first:
seglines += ax1.plot([l.getfstart(),l.getfend()],[place,place],'g-',label="Forced Transition")
first = False
else:
seglines += ax1.plot([l.getfstart(),l.getfend()],[place,place],'g-')
seglines += ax1.plot([l.getfstart(),l.getfstart()],[place-step,place+step],'g-')
seglines += ax1.plot([l.getfend(),l.getfend()],[place-step,place+step],'g-')
# done
# Add references
for f in references.keys():
if f<fmin or f>fmax: continue
r = references[f]
seglines += ax1.plot([f,f],[2.0*place-step,2.0*place+step],'k-')
axtexts.append(ax1.text(f,2.0*place+step,r,withdash=True,rotation='vertical',dashdirection=1,dashlength=50,dashrotation=45))
# Put a legend below current axis.
ax1.legend(loc="lower center", bbox_to_anchor=(0.5,-0.14),
fancybox=True, shadow=True, ncol=ncol)
# Label the top axis in km/s vlsr offset from reference (or mid-band).
if refline is None:
# @todo for now cheat, and always take the middle of the band
refline = 0.5 * (ma.min(x) + ma.max(x))
ax2.set_xlabel('VLSR Offset (km/s) from %g' % refline, color='r')
# use the radio definition
vmin = (1-fmin/refline)*utils.c
vmax = (1-fmax/refline)*utils.c
#print "REFERENCE LINE @ ",refline," VEL RANGE",vmin,vmax
ax2.set_xlim(xmin=vmin, xmax=vmax)
ax2.xaxis.labelpad = 3
ax2.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
for t1 in ax2.get_xticklabels():
t1.set_color('r')
ax2.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
for tick in ax2.yaxis.get_minor_ticks():
tick.tick1On = True
tick.tick2On = True
if figname and self._plot_mode != PlotControl.INTERACTIVE:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
#print self._figurefiles[APlot.figno]
fig.savefig(self._figurefiles[APlot.figno], dpi=fig.get_dpi())
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
# Remove channel-based ticks and guides for interactive mode.
ax1.xaxis.cla()
ax1.xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(10))
ax1.xaxis.set_major_formatter(
matplotlib.ticker.ScalarFormatter(useOffset=False)
)
ax1.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax1.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
ax1.set_xlabel(xlabel)
#
for line in guides: line.remove()
# Make room for radio buttons and legend.
plt.subplots_adjust(left=0.17)
# Convert frequency to channel (assumed uniform).
def freq_to_chan(f):
return cmin+(cmax-cmin)*(f-xmin)/(xmax-xmin)
# Convert channel to frequency (assumed uniform).
def chan_to_freq(c):
return xmin+(xmax-xmin)*(c-cmin)/(cmax-cmin)
# Radio buttons.
rcolor = 'lightgoldenrodyellow'
rlabels = (xlabel, 'Channel Number')
rax = plt.axes([0.02, 0.02, 0.14, 0.10], axisbg=rcolor)
radio = RadioButtons(rax, rlabels)
# X-axis callback, plot frequency or channels.
def xcall(label):
if (label == rlabels[0]) == (ax1.get_xlabel() == xlabel):
# User re-selected the currently displayed view.
return
# Translate X-axis data.
xl, xr = ax1.get_xlim()
if label == rlabels[0]:
z = x
zlabel = rlabels[0]
xl = chan_to_freq(xl)
xr = chan_to_freq(xr)
for line in seglines:
cdata = line.get_xdata()
fdata = []
for c in cdata: fdata.append(chan_to_freq(c))
line.set_xdata(fdata)
for text in axtexts:
pos = text.get_position()
text.set_position((chan_to_freq(pos[0]), pos[1]))
else:
z = chan
zlabel = rlabels[1]
xl = freq_to_chan(xl)
xr = freq_to_chan(xr)
for line in seglines:
fdata = line.get_xdata()
cdata = []
for f in fdata: cdata.append(freq_to_chan(f))
line.set_xdata(cdata)
for text in axtexts:
pos = text.get_position()
text.set_position((freq_to_chan(pos[0]), pos[1]))
for line in axlines: line.set_xdata(z)
ax1.set_xlim(xl, xr)
ax1.set_xlabel(zlabel)
plt.draw()
radio.on_clicked(xcall)
plt.show()
plt.close()
[docs] def makeUlines(self,x,y,noise=0.0,title=None,figname=None,xlab=None,ylab=None,segments=None,tags=None, thumbnail=True):
"""simple plotter of multiple columns against one column
stolen from atable, allowing some extra bars in the plot
for line_id
This code should be merged with plotter()
"""
if self._plot_mode == PlotControl.NOPLOT:
return
# if filename: plt.ion()
#plt.ion()
plt.ioff()
APlot.figno = APlot.figno + 1
if self._abspath != "" and figname:
figname = self._abspath + figname
fig = plt.figure(APlot.figno)
ax1 = fig.add_subplot(1,1,1)
xlim = ax1.get_xlim()
ylim = ax1.get_ylim()
for yi in y:
ax1.plot(x,yi)
ax1.plot([xlim[0],xlim[1]],[noise,noise],'-')
height = abs(ylim[0]-ylim[1])/2.0
inc = height/20.
if segments:
for s in segments:
ax1.plot([s[0],s[1]],[height,height])
if tags:
for t in tags:
ax1.plot([t,t],[height-inc,height+inc])
if title: ax1.set_title(title)
if xlab: ax1.set_xlabel(xlab)
if ylab: ax1.set_ylabel(ylab)
if figname:
self._figurefiles[APlot.figno] = figname + PlotControl.mkext(self._plot_type,True)
fig.savefig(self._figurefiles[APlot.figno])
if thumbnail: self.makeThumbnail(APlot.figno, fig=fig)
if self._plot_mode==PlotControl.INTERACTIVE:
plt.show()
plt.close()
if __name__ == "__main__":
import PlotControl
x = np.arange(0,1,0.1)
psize = x*200 # vary the point size for scatter plot
y = x*x
z = y-x
print "a1"
a1 = APlot(pmode=PlotControl.INTERACTIVE,ptype=PlotControl.PNG,figno=10,abspath="/tmp")
#a1.backend('agg')
a1.plotter(x,[y],figname="figone")
a1.plotter(x,[z])
a1.plotter(x,[y,z])
a1.show()
print "a2"
a2 = APlot(pmode=PlotControl.INTERACTIVE,ptype=PlotControl.PNG,figno=20)
a2.backend('agg')
#a2.histogram([x,y])
#a2.show()
a2.plotter(x,[y],labels=['Example label','DEF','ZYZ']) # last two labels unused.
a2.show()
a2.scatter(y,x,figname="scatter",color='red',size=psize, cmds=['grid'])
a2.show()
print "a3"
a3 = APlot(pmode=PlotControl.BATCH,ptype=PlotControl.PNG,figno=29)
a3.histogram([x,y],figname="histo")
a3.plotter(x,[y],figname="plot")
a3.show()
print "A3 plotmode: %d" % a3.plotmode
print "Abs,AP figno %d,%d" % (AbstractPlot.figno, APlot.figno)
if a3.plotmode == PlotControl.BATCH:
print "BATCH"