import numpy
import os
import bokeh.plotting
import bokeh.io
import matplotlib
import matplotlib.pyplot as plt
from .. import utilities
from .. import dsp
[docs]def recurrence( AA, ID=numpy.empty(0,dtype=int), colours=["red","blue","green"] ):
"""Plots a recurrence plot.
Args:
AA (numpy.array(float)): A 2D reccurence matrix.
ID (numpy.array(int), optional): A vector so that speaker( col[i] ) = ID[i]. Defaults to the 0 vector.
colours (list(str), optional): Colours for the plot.
Returns:
bokeh plot object
"""
isLower = utilities.is_lower_triangular(AA)
AA = AA/AA.max()
COLS = colours
N = AA.shape[0]
if not ID.size:
ID = numpy.zeros(N,dtype=int)
xs, ys, cols, alphas = list(), list(), list(), list()
cell_padding = .8
# Note: r and c are deliberately swapped so that the transpose of AA is plotted
for c in range(N):
for r in range(c+1 if isLower else N):
#for r in range(N):
#triangle bottom
xs.append([r,r+cell_padding,r+cell_padding])
ys.append([c,c,c+cell_padding])
cols.append( COLS[ID[r]] if AA[c][r]>=0 or c==r else "pink" )
alphas.append( 1-AA[c,r] )
#triangle top
xs.append([r,r,r+cell_padding])
ys.append([c,c+cell_padding,c+cell_padding])
cols.append( COLS[ID[c]] if AA[c][r]>=0 or c==r else "black" )
alphas.append( 1-AA[c,r] )
#Plot tweaks
plot = bokeh.plotting.figure(
plot_width=900,
plot_height=900,
min_border=100,
y_range=(N,0),
x_range=(0,N)
)
plot.toolbar.logo = None
plot.toolbar_location = None
plot.xgrid.grid_line_color = None
plot.ygrid.grid_line_color = None
plot.patches( xs, ys, color=cols, alpha=alphas )
return plot
[docs]def show( bokeh_plot ):
"""Print a plot to the screen.
Args:
bokeh_plot (bokeh plot object): bokeh plot object.
Return
null: Outputs a plot on the default plot device.
"""
bokeh.plotting.show( bokeh_plot )
return
[docs]def export( bokeh_plot, file_path, astype="png"):
"""Save a plot as picture file.
Args:
bokeh_plot (bokeh plot object): The plot object to be saved.
file_path (str): Where to save the picture.
astype (str, optional): The file type. Defaults to png, another option is svg.
Return
null: Outputs a plot to a file.
"""
if astype not in ["svg","png"]:
print("Export type not supported. Use 'svg' or 'png' only.")
return
if astype=="png":
bokeh.io.export_png( bokeh_plot, filename=file_path+"."+astype)
return
if astype=="svg":
bokeh_plot.output_backend = "svg"
bokeh.io.export_svgs( bokeh_plot, filename=file_path+"."+astype)
return
[docs]def profile_plot( ys, xlabel="", ylabel="", file_name="", figsize=(8,4), remove_zeros=False ):
"""Plots points on the plane and connects with a line.
Args:
ys (numpy.array(floats)): List of numeric values to be plotted.
xlabel (str, optional): The name for the x-axis. Defaults to emtpy.
ylabel (str, optional): The name for the y-axis. Defaults to emtpy.
file_name (str, optional): Outputs picture to this file_name. Defaults to empty.
figsize (tuple(float,float), optional): A tuple specifying (width, height) in inches of plot. Defaults to (8,4)
remove_zeros (bool, optional): Toggle for replacing 0s with NANs. Defaults to False.
Returns:
null : Saves an image to file_name else displays to default plot
"""
if remove_zeros:
ys[numpy.where(ys == 0)] = numpy.nan
#define plot size in inches (width, height) & resolution(DPI)
fig = plt.figure( figsize=figsize )
plt.plot( ys, 'm-o', ms=3 )
if ylabel:
plt.ylabel(ylabel)
if xlabel:
plt.xlabel(xlabel)
if file_name:
plt.savefig(file_name)
else:
plt.show()
return
[docs]def mfcc_plot( AA, file_name="", figsize=(16,4) ):
"""Plots points on the plane and connects with a line.
Args:
AA (numpy.array(floats)): 2D array containing the MFCC.
file_name (str, optional): Outputs picture to this file_name. Defaults to empty.
figsize (tuple(float,float), optional): A tuple specifying (width, height) in inches of plot. Defaults to (16,4)
Returns:
null : Saves an image to file_name else displays to default plot
"""
plt.figure(figsize=figsize)
plt.imshow( AA, cmap = 'hot', alpha = 1, aspect="auto")
plt.tight_layout()
plt.axis('off')
if file_name:
plt.savefig( file_name )
else:
plt.show()
return
def _heatmap_dist( xs, num_bins=7, num_chunks=10 ):
"""A helper function for all_profile_plot that returns a matrix that encodes a pitch distribution as a heatmap.
Args:
xs (numpy.array(float)) : A numpy array of floats.
num_bins : Number of bins for historgram. Defaults to 7.
num_chunks: Number of chunks of all_profile_plot. Defaults to 10.
Returns:
numpy.matrix : A matrix for heat mapping.
"""
AA = numpy.matrix([0 for k in range(num_bins)])
for k, chunk in enumerate( numpy.array_split(xs,num_chunks) ):
#hist, bin_edges = numpy.histogram( chunk, bins=num_bins )
hist, _ = numpy.histogram( chunk, density=True, range=(1,256), bins=num_bins )
AA = numpy.vstack( (AA, numpy.matrix(hist)) )
AA = AA.T
AA = numpy.delete( AA, 0, 1 )
return AA
def _add_lines( ax, num_chunks ):
"""A helper function for all_profile_plot that adds lines to indicate plot chunks.
Args:
ax (matplotlib axis): An axis.
num_chunks (int): Number of chunks to be illustrated.
Returns:
null: Adds lines to input axis.
"""
xmin, xmax = ax.get_xlim()
for L in numpy.arange(xmin+(xmax-xmin)/num_chunks, xmax, (xmax-xmin)/num_chunks):
ax.axvline(x=L, color='m', linewidth=2.0 )
return
[docs]def all_profile_plot( file_name, features=["waveform", "mfcc", "pitch", "intensity", "pitch_hist", "dB"], num_plots=200, num_chunks=10, scaling=4, print_status=False ):
'''Plots a multirow plot of various features.
Args:
filename (str): path to the audio file.
features (list(string)): list of features to be plotted. Ignores nonexistent features. Defaults to ["waveform", "mfcc", "pitch", "intensity", "pitch_hist", "dB"]
num_plots (int): divide the intial wavform into num_plots pieces and create one plot each. Defaults to 200.
num_chunks (int): number of subdivisions of one plot. Defaults to 10.
scaling (int): scales the size of the output plot.
Returns:
null: saves plots to a folder in current directory.
'''
available_features = ["waveform", "mfcc", "pitch", "intensity", "pitch_hist", "dB"]
features = [ feat for feat in features if feat in available_features ] #Remove features that do not exist.
fs, sound = utilities.read_wavfile(file_name)
file_name = os.path.splitext(os.path.basename(file_name))[0] #filename without path and extension
num_feat = len(features)
N = len(sound)
stride = N//num_plots
if not os.path.exists(file_name):
os.mkdir(file_name)
for k in range(0, num_plots):
fig = plt.figure( figsize=(8*scaling, num_feat*scaling) )
plot_num = 0
L, R = stride*k, stride*(k+1)
sound_chunk = sound[L:R]
xs = list( L/fs+n/fs for n in range(L,R) )
#feature: Waveform
if "waveform" in features:
plot_num += 1
ax = plt.subplot(num_feat, 1, plot_num)
ax.xaxis.tick_top()
ax.title.set_visible(False)
ax.plot( xs, sound_chunk )
ax.margins(0)
_add_lines( ax, num_chunks=num_chunks )
#feature: dB profile
if "dB" in features:
plot_num += 1
ax = plt.subplot(num_feat, 1, plot_num)
ax.xaxis.set_visible(False)
ax.title.set_visible(False)
IP = dsp.dB_profile( sound_chunk, fs )
ax.plot( IP, color='b', linestyle='--', marker='o' )
ax.margins(0)
_add_lines( ax, num_chunks=num_chunks )
#feature: Pitch profile
if "pitch" in features:
plot_num += 1
ax = plt.subplot(num_feat, 1, plot_num)
ax.xaxis.set_visible(False)
ax.title.set_visible(False)
ax.set_ylim(0,256)
PP = dsp.pitch_profile( sound_chunk, fs )
ax.plot( PP, color='r', linestyle='--', marker='o' )
ax.margins(0)
_add_lines( ax, num_chunks=num_chunks )
#feature: Pitch hist
if "pitch_hist" in features:
plot_num += 1
ax = plt.subplot(num_feat, 1, plot_num)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.title.set_visible(False)
AA = _add_lines( PP, num_chunks=num_chunks )
ax.imshow( AA, cmap=plt.cm.Blues, alpha = 1, aspect="auto")
_add_lines( ax, num_chunks )
#feature: MFCC
if "mfcc" in features:
plot_num += 1
ax = plt.subplot(num_feat, 1, plot_num)
ax.xaxis.set_visible(False)
ax.title.set_visible(False)
AA = dsp.mfcc_profile( sound_chunk, fs )
plt.imshow( AA, cmap = 'hot', alpha = 1, aspect="auto")
_add_lines( ax, num_chunks=num_chunks )
plt.subplots_adjust(hspace=.05)
#save plot
plt.savefig('{}/time{}_{}.png'.format(file_name, L, R),
bbox_inches='tight',
orientation="landscape",
dpi=200)
plt.close()
if print_status:
print("Plot {} of {} saved to {}".format(k+1, num_plots, '{}/{}/time{}_{}.png'.format(os.getcwd(),file_name, L, R)))
[docs]def feature_distribution(features, output_file, bins=100, showfig=False, savefig=True):
"""Plot histogram of features
Args:
features (numpy.array): 1D or 2D feature vector. If 2D, features are along axis 1.
output_file (string): path to output figure.
bins (int, optional): number of bins, defaults to 100.
showfig (bool, optional): True indicates to plot out the figure. Defaults to False.
savefig (bool, optional): True indicates to write the figure to disk. Defaults to True.
Returns:
None, save a figure to output_file or show a figure if showfig.
"""
assert showfig or savefig, "showfig and savefig parametres are both False, please set at least one to be True."
# number of features.
n = features.shape[0]
fig = plt.figure()
for i in range(n):
hist, bins = numpy.histogram(features[i,:], bins=bins, density=False)
plt.plot(bins[:-1], hist, '+-', label='feature' + str(i))
plt.title('Histogram of all features')
plt.legend()
if savefig:
fig.savefig(output_file, dpi=600)
if showfig:
fig.show()