I have written a code with librosa module, to generate spectrogram of an audio file and other works. Apparently it works fine with Spyder where the output is generated after taking a while, but for PyCharm, it just gets executed. No output spectrogram.
I am fairly new to PyCharm environment please help.
My code is:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
audio_name = 'C:\\Users\\USER\\Desktop\\Songs\\abc.wav'
hop_length = 512
window_size = 2048
import librosa
y, sr = librosa.load(audio_name) #y=time series(one-dimensional NumPy floating point array), sr= sampling rate of y, that is, the number of samples per second of audio. By default, all audio is mixed to mono and resampled to 22050 Hz at load time
window = np.hanning(window_size)
out = librosa.core.spectrum.stft(y, n_fft = window_size, hop_length = hop_length, window=window)
out = 2 * np.abs(out) / np.sum(window)
import librosa.display
librosa.display.specshow(librosa.amplitude_to_db(out, ref=np.max), y_axis='log', x_axis='time')
''' To store spectrogram
from matplotlib.backends.backend_agg import FigureCanvasAgg
fig = plt.figure()
canvas = FigureCanvasAgg(fig)
ax = fig.add_subplot(111)
p = librosa.display.specshow(librosa.amplitude_to_db(out, ref=np.max), ax=ax, y_axis='log', x_axis='time')
fig.savefig('C:\\Users\\USER\\Desktop\\Songs\\spec.png')
'''
onset_env = librosa.onset.onset_strength(y=y, sr=sr, aggregate=np.median)
tempo, beats = librosa.beat.beat_track(onset_envelope=onset_env, sr=sr)
fig, ax = plt.subplots(nrows=2, sharex=True)
times = librosa.times_like(onset_env, sr=sr, hop_length=hop_length)
M = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length)
librosa.display.specshow(librosa.power_to_db(M, ref=np.max), y_axis='mel', x_axis='time', hop_length=hop_length, ax=ax[0])
ax[0].label_outer()
ax[0].set(title='Mel spectrogram')
ax[1].plot(times, librosa.util.normalize(onset_env), label='Onset strength')
ax[1].vlines(times[beats], 0, 1, alpha=0.5, color='r', linestyle='--', label='Beats')
ax[1].legend()
The output in Spyder (After taking a bit of time):
The output of PyCharm after execution:
Update: Apparently it works when I give a plt.show() at the end as referred by #Mandera. But can someone tell me why it works in Spyder even without this?
Related
I am trying to plot two imshow and one plot above each other sharing their x-axis. The figure layout is set up using gridspec.
Here is a MWE:
import matplotlib as mpl
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,8))
gs = fig.add_gridspec(3,2,width_ratios=(1,2),height_ratios=(1,2,2), left=0.1,right=0.9,bottom=0.1,top=0.99, wspace=0.1, hspace=0.1)
ax=fig.add_subplot(gs[2,1])
ax2=fig.add_subplot(gs[2,0], sharey=ax)
ax3=fig.add_subplot(gs[1,0])
ax4=fig.add_subplot(gs[1,1], sharex=ax, sharey=ax3)
ax5=fig.add_subplot(gs[0,1], sharex=ax)
dates = pd.date_range("2020-01-01","2020-01-10 23:00", freq="H")
xs = mpl.dates.date2num(dates)
ys = np.random.random(xs.size)
N = 10
arr = np.random.random((N, N))
arr2 = np.random.random((N, N))
norm=mpl.colors.Normalize(0, arr.max()) # change the min to stretch the color spectrum
pcm = ax.imshow(arr, extent=[xs[0],xs[-1],10,0],norm=norm,aspect='auto')
cax = fig.colorbar(pcm, ax=ax, extend='max') # , location='left'
ax.set_xlabel('date')
cax.set_label('fraction [-]')
# ax.xaxis_date()
myFmt = mpl.dates.DateFormatter('%d.%m')
ax.xaxis.set_major_formatter(myFmt)
norm=mpl.colors.Normalize(0, arr2.max()) # change the min to stretch the color spectrum
pcm = ax4.imshow(arr2, extent=[xs[0],xs[-1],1,0],norm=norm,aspect='auto')
cax4 = fig.colorbar(pcm, ax=ax4, extend='max')
cax4.set_label('fraction [-]')
ax5.plot(xs,ys)
con1 = ConnectionPatch(xyA=(ax2.get_xlim()[0],1), xyB=(ax2.get_xlim()[0],1),
coordsA="data", coordsB="data", connectionstyle=mpl.patches.ConnectionStyle("Bar", fraction=-0.05),
axesA=ax2, axesB=ax3, arrowstyle="-", color='r')
con2 = ConnectionPatch(xyA=(ax2.get_xlim()[0],0), xyB=(ax2.get_xlim()[0],0),
coordsA="data", coordsB="data", connectionstyle=mpl.patches.ConnectionStyle("Bar", fraction=-0.02),
axesA=ax2, axesB=ax3, arrowstyle="-", color='r')
fig.add_artist(con1)
fig.add_artist(con2)
The plot ends up like this:
While the axes seem to be linked (date format applied to all of them), they do not have the same extent.
NOTE: The two left axes must not share the same x-axis.
EDIT: Added ConnectionPatch connections which break when using constrained_layout.
Constrained_layout was specifically designed with this case in mind. It will work with your gridspec solution above, but more idiomatically:
import datetime as dt
import matplotlib as mpl
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
fig, axs = plt.subplot_mosaic([['.', 'plot'], ['empty1', 'imtop'],
['empty2', 'imbottom']],
constrained_layout=True,
gridspec_kw={'width_ratios':(1,2),'height_ratios':(1,2,2)})
axs['imtop'].sharex(axs['imbottom'])
axs['plot'].sharex(axs['imtop'])
dates = pd.date_range("2020-01-01","2020-01-10 23:00", freq="H")
xs = mpl.dates.date2num(dates)
ys = np.random.random(xs.size)
N = 10
arr = np.random.random((N, N))
arr2 = np.random.random((N, N))
norm=mpl.colors.Normalize(0, arr.max()) # change the min to stretch the color spectrum
pcm = axs['imtop'].imshow(arr, extent=[xs[0],xs[-1],10,0],norm=norm,aspect='auto')
cax = fig.colorbar(pcm, ax=axs['imtop'], extend='max')
norm=mpl.colors.Normalize(0, arr2.max()) # change the min to stretch the color spectrum
pcm = axs['imbottom'].imshow(arr2, extent=[xs[0],xs[-1],1,0],norm=norm,aspect='auto')
cax4 = fig.colorbar(pcm, ax=axs['imbottom'], extend='max')
axs['plot'].plot(xs,ys)
I'm working on a custom interactive figure for electrophysiology data, anywhere from 10-400 lines (EEG or MEG data channels) plotted as a LineCollection with offsets. It is often useful to have a vertical line to assess how signal features on different channels align temporally, so I have a button_press_event listener that creates an axvline (or updates the xdata of the line, if it already exists). Redrawing the axvline is expensive if there are lots of channels in the LineCollection, but the supposedly more efficient redraw method (ax.draw_artist(my_vline)) doesn't work at all (it is quite possible that I am simply misunderstanding how draw_artist is supposed to work).
Code for reproduction
import matplotlib.pyplot as plt
plt.ion()
def make_vline(event):
ax = event.inaxes
if getattr(ax, 'my_vline', None) is None:
ax.my_vline = ax.axvline(event.xdata, linewidth=4, color='r')
else:
ax.my_vline.set_xdata(event.xdata)
# I thought any 1 of these 3 lines would move the vline to the click location:
ax.draw_artist(ax.my_vline) # this has no visible effect
ax.redraw_in_frame() # TypeError (see below for traceback)
ax.figure.canvas.draw_idle() # works, but slow when figure has many lines
fig, ax = plt.subplots()
callback_id = fig.canvas.mpl_connect('button_press_event', make_vline)
Actual outcome
If I use the ax.draw_artist(ax.my_vline) line, outcome is a blank axes no matter where I click (unless I then resize the figure, which triggers a redraw and then the line appears).
If i use the ax.redraw_in_frame() line, I get:
Traceback (most recent call last):
File "/opt/miniconda3/envs/mnedev/lib/python3.8/site-packages/matplotlib/cbook/__init__.py", line 224, in process
func(*args, **kwargs)
File "<ipython-input-1-08572d18e6b3>", line 11, in make_vline
ax.redraw_in_frame()
File "/opt/miniconda3/envs/mnedev/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 2778, in redraw_in_frame
stack.push(artist.set_visible, artist.get_visible())
TypeError: push() takes 2 positional arguments but 3 were given
if I use ax.figure.canvas.draw_idle() it works as expected, but is really slow once the figure has actual data in it. Here is a longer code snippet you can run locally to see the slowness:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
plt.ion()
rng = np.random.default_rng()
def make_vline(event):
ax = event.inaxes
if getattr(ax, 'my_vline', None) is None:
ax.my_vline = ax.axvline(event.xdata, linewidth=4, color='r', zorder=3)
else:
ax.my_vline.set_xdata(event.xdata)
ax.figure.canvas.draw_idle() # works, but slow when figure has many lines
def add_line_collection(ax):
n_chans = 400
n_times = 10001
xs = np.linspace(0, 10, n_times)
ys = rng.normal(size=(n_chans, n_times)) * 1e-6
segments = [np.vstack((xs, ys[n])).T for n in range(n_chans)]
yoffsets = np.arange(n_chans)
offsets = np.vstack((np.zeros_like(yoffsets), yoffsets)).T
lc = LineCollection(segments, offsets=offsets, linewidths=0.5, colors='k')
ax.add_collection(lc)
ax.set_xlim(xs[0], xs[-1])
ax.set_ylim(yoffsets[0] - 0.5, yoffsets[-1] + 0.5)
ax.set_yticks(yoffsets)
fig, ax = plt.subplots()
add_line_collection(ax)
callback_id = fig.canvas.mpl_connect('button_press_event', make_vline)
Questions
when would ax.draw_artist(my_artist) actually work / what is it supposed to do?
Is my example a case where blitting would be beneficial?
Any other ideas for how to speed up (re)drawing here?
Matplotlib version
Operating system: Xubuntu 20.04
Matplotlib version: 3.3.1 (conda-forge)
Matplotlib backend: Qt5Agg
Python version: 3.8.5
Jupyter version (if applicable): n/a
Other libraries: numpy 1.19.1 (conda-forge)
I solved this with blitting, based on the MPL blitting tutorial:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
plt.ion()
rng = np.random.default_rng()
def make_vline(event):
fig.canvas.restore_region(fig.my_bg)
ax = event.inaxes
if getattr(ax, 'my_vline', None) is None:
ax.my_vline = ax.axvline(event.xdata, linewidth=4, color='r', zorder=3)
else:
ax.my_vline.set_xdata(event.xdata)
ax.draw_artist(ax.my_vline)
ax.figure.canvas.blit()
ax.figure.canvas.flush_events()
def add_line_collection(ax):
n_chans = 400
n_times = 10001
xs = np.linspace(0, 10, n_times)
ys = rng.normal(size=(n_chans, n_times)) * 1e-6
segments = [np.vstack((xs, ys[n])).T for n in range(n_chans)]
yoffsets = np.arange(n_chans)
offsets = np.vstack((np.zeros_like(yoffsets), yoffsets)).T
lc = LineCollection(segments, offsets=offsets, linewidths=0.5, colors='k')
ax.add_collection(lc)
ax.set_xlim(xs[0], xs[-1])
ax.set_ylim(yoffsets[0] - 0.5, yoffsets[-1] + 0.5)
ax.set_yticks(yoffsets)
fig, ax = plt.subplots()
add_line_collection(ax)
callback_id = fig.canvas.mpl_connect('button_press_event', make_vline)
plt.pause(0.1)
fig.my_bg = fig.canvas.copy_from_bbox(fig.bbox)
Note that this will not work if the figure is resized, you would need to rerun the copy_from_bbox line in a resize listener.
The question, in brief, is: is it possible (with the tools of matplotlib.animation or other modules for python) to obtain a slow-motion on certain frames of the animation?
Some context:
I have a matplotlib animated plot in which I am varying one variable and showing a contour plot over two other ones. My idea was to slow down the animation while I am near the maximum of the function, so that I can more clearly pinpoint it, while accelerate far from it where there is not much interest.
At the moment, my best idea is to double the frames closest to the maximum, but can someone have a better idea?
Thank you everyone!
Code snippet:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
X = np.linspace(1,10, 100)
Y = np.linspace(1,10, 100)
R = np.linspace(-1, 1, 100)
ims = []
for r in R:
z = func(X, Y, r)
im = plt.imshow(z)
ims.append(im)
if check_r(r):
ims.append(im)
where func() is a function that return a (len(X), len(Y)) array that depends on r (for instance Z[i,j] = X[i]**r * Y[j]**(1-r) or whatever, while check_r() test if r is within the range of the values that need to be maximized.
Your idea is the best, I think. And I've found another way using matplotlib animation. The idea is that use frames as slow delay, by making same points.
In this example just sin curve is plotted but it will be applied other functions.
(most of code is took from here)
import numpy as np
import matplotlib.animation as animation
import matplotlib.pylab as plt
import pandas as pd
TWOPI = 2*np.pi
fig, ax = plt.subplots()
# making frames "delay"
frames = np.arange(0.0, TWOPI, 0.1)
frames = np.insert(frames, 17, [1.7]*5)
frames = np.insert(frames, 16, [1.6]*5)
frames = np.insert(frames, 15, [1.5]*5)
t = np.arange(0.0, TWOPI, 0.001)
s = np.sin(t)
l = plt.plot(t, s)
ax = plt.axis([0,TWOPI,-1,1])
redDot, = plt.plot([0], [np.sin(0)], 'ro')
def animate(i):
redDot.set_data(i, np.sin(i))
return redDot,
myAnimation = animation.FuncAnimation(fig, animate, frames=frames,
interval=100, blit=True, repeat=True)
I've been trying to plot an spectogram based on a wav file of 15 minutes lenght. I think I managed to do this, but I can't remove the microseconds from my x axis ( time axis). Any help with this, please?
This is the spectrogram obtained:
This is my code:
import matplotlib.pyplot as plt
import scipy.io.wavfile as wavfile
import matplotlib.ticker as ticker
from matplotlib.dates import DateFormatter, MinuteLocator
import time
# Prettify
import matplotlib
import datetime
matplotlib.rc('figure', figsize=(17, 5))
cmap = plt.get_cmap('plasma') # this may fail on older versions of matplotlib
vmin = -40 # hide anything below -40 dB
cmap.set_under(color='k', alpha=None)
rate, frames = wavfile.read("audio_test.wav")
fig, ax = plt.subplots()
pxx, freq, t, cax = ax.specgram(frames[:, 0], # first channel
Fs=rate, # to get frequency axis in Hz
cmap=cmap, vmin=vmin)
cbar = fig.colorbar(cax)
cbar.set_label('Intensity dB')
ax.axis("tight")
ax.set_xlabel('time h:mm:ss')
ax.set_ylabel('frequency kHz')
scale = 1e3 # KHz
ticks = matplotlib.ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale))
ax.yaxis.set_major_formatter(ticks)
def timeTicks(x, pos):
d = datetime.timedelta(seconds=x)
return str(d)
#formatter = matplotlib.ticker.FuncFormatter(timeTicks)
#ax.xaxis.set_major_formatter(formatter)
majorFormatter = matplotlib.dates.DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(majorFormatter)
ax.xaxis.set_major_locator(ticker.IndexLocator(base=120, offset=60))
#ax.text(0.0, 0.1, "IndexLocator(base=0.5, offset=0.25)",
# fontsize=14, transform=ax.transAxes)
plt.show()
Using the code before your edit, you can change the return of def timeTicks(x, pos) in:
return str(d)[:7]
I have a script that makes a basic map and then exports the figure to a png file:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def basic_map():
##geographic boudning/projection info
lon_0 = -119.610686
lat_0 = 37.246066
llcrnrlon = -129.409591
llcrnrlat = 30.034156
urcrnrlon = -109.131211
urcrnrlat = 43.009518
fig = plt.figure(figsize=(7205 / 600, 4308 / 600), dpi=600)
ax = fig.add_axes([0.001, 0.001, .999, .999])
m = Basemap(llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat, urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat, \
resolution='l', lon_0=lon_0, lat_0=lat_0, projection='aea' \
,ellps='GRS80', lat_1=llcrnrlat, lat_2=urcrnrlat,ax=ax)
m.drawcountries()
m.drawstates()
m.drawcoastlines()
ax.axis('off')
f = 'somewhere.png'
plt.savefig(f, dpi=600, bbox_inches='tight', pad_inches=0)
basic_map()
When I run the script above it returns a png file with dimensions of 5358 × 4310 pixels (600 dpi). However, if import the nearly identical file (see below) as a module and then run the basic_map() function it returns a png file of size 1685 × 1356 pixels (600 dpi), i.e. ~3x smaller than original dimensions:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def basic_map():
##geographic boudning/projection info
lon_0 = -119.610686
lat_0 = 37.246066
llcrnrlon = -129.409591
llcrnrlat = 30.034156
urcrnrlon = -109.131211
urcrnrlat = 43.009518
fig = plt.figure(figsize=(7205 / 600, 4308 / 600), dpi=600)
ax = fig.add_axes([0.001, 0.001, .999, .999])
m = Basemap(llcrnrlon=llcrnrlon, llcrnrlat=llcrnrlat, urcrnrlon=urcrnrlon, urcrnrlat=urcrnrlat, \
resolution='l', lon_0=lon_0, lat_0=lat_0, projection='aea' \
,ellps='GRS80', lat_1=llcrnrlat, lat_2=urcrnrlat,ax=ax)
m.drawcountries()
m.drawstates()
m.drawcoastlines()
ax.axis('off')
f = 'somewhere.png'
plt.savefig(f, dpi=600, bbox_inches='tight', pad_inches=0)
I then import this as a module and run the function:
import File as F
F.basic_map()
Neither the script or the imported module/function report errors. The only difference that I have noticed is that when I run after importing the module/function there is a blank figure window that pops up.
I am running Python 3.5.4 (packaged by condo-forge) on a Mac running High Sierra (10.13).
Anyway, I am creating a larger mapping application and I need this to be a function that returns the larger sized file (from the script). Any help would be appreciated!