使 matplotlib.pyplot 颜色条与波形和频谱图一起跨越两行
Make matplotlib.pyplot color bar span two rows alongside waveform and specgram
使用matplotlib.pyplot,我有两个地块。一种是音频文件的波形。第二个是同一音频的频谱图。我希望波形直接位于频谱图上方(相同的 x 轴,并对齐在一起)。我还想要频谱图的颜色条。
问题 - 当我放入颜色条时,它附加到频谱图行并且波形在颜色条上延伸(即不再与频谱图时间对齐并且比频谱图更宽)。
我想我已经接近解决方案了,但我只是不太清楚我做错了什么或要改变什么才能让它按我想要的方式工作。希望有人能给我指出正确的方向!
使用以下 python 代码(我尽可能将代码设为 MWE):
import matplotlib
matplotlib.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
####
#Waveform
####
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(13.6, 7.68))
plt.subplot(211)
plt.plot(times, data, color='k')
plt.xlabel('time (s)')
plt.xlim(times[0], times[-1])
max_amp = max(abs(np.amin(data)), abs(np.amax(data)))
min_amp = (max_amp * -1) - abs(np.amin(data) - np.amax(data))/50
max_amp = max_amp + abs(np.amin(data) - np.amax(data))/50
plt.ylim(min_amp, max_amp)
ax = plt.gca()
ax.set_yticks(np.array([min_amp, min_amp/2, 0, max_amp/2, max_amp]))
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_tick_params(pad=115)
####
#Spectrogram
####
Fs = 5000*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
plt.subplot(212)
Pxx, freqs, times, cax = plt.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
axes_spec = plt.gca()
axes_spec.set_xlim([0., max(times)])
axes_spec.set_ylim([0, 5000])
plt.xlabel("Time (s)")
plt.ylabel("Frequency (hz)")
plt.colorbar(cax, label='(dB)').ax.yaxis.set_label_position('left')
plt.tight_layout()
plt.show()
我可以得到如下图:
在下面进行这些细微的修改,我可以让情节看起来几乎我想要的样子。问题是,它在颜色条旁边创建了一个空白图形。这个版本,减去空白图,就是我要创建的。
#Replace this for waveform
plt.subplot(221)
#Replace this for spectrogram
plt.subplot(223)
#Add this before colorbar
plt.subplot(122)
新版剧情:
编辑:还有另一种可能性,我也可以接受(或者可能两者都接受,为了更好的衡量标准!)
这是一个基于 matplotlib-2-subplots-1-colorbar 中的答案之一的颜色条示例。 fig.colorbar
中的参数pad
用于指定plots和colorbar之间的space,aspect
用于指定plots的高宽比彩条。 Specgram 将图像作为第四个输出参数输出,因此我将其用于颜色条。
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-');
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs.ravel().tolist(), pad=0.04, aspect = 30)
请务必注意,当使用 ax
参数调用 fig.colorbar
函数时,将调整原始图的大小以为 colorbar
腾出空间。如果它仅应用于其中一个图,则只会调整该轴的大小。下面是:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs[1], pad=0.04, aspect = 30)
下面显示了一种控制原始轴大小调整的方法,以便使用 fig.colorbar
和 cax
参数为颜色条腾出空间,不会进一步调整原始图的大小。这种方法需要在函数 fig.subplots_adjust
:
中指定 right
参数,手动为颜色栏腾出一些空间
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y11-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
并通过读取原始两个地块的坐标来执行相同操作以跨越两行:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x00,y00],[x01,y01]] = axs[0].get_position().get_points()
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
我想到的最佳解决方案是 subplot2grid()
函数。这需要使用我最初没有使用的 subplots
。按照这种方法,我需要将所有内容从使用 plt
(matplotlib.pyplot
) 更改为对每个 .plot()
或 .specgram()
调用使用给定绘图的轴。此处包含相关更改:
#No rows or columns need to be specified, because this is handled within a the `subplot2grid()` details
fig, axes = plt.subplots(figsize=(13.6, 7.68))
#Setup for waveform
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
####WAVEFORM PLOTTING
#Setup for spectrogram
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
####SPECTROGRAM PLOTTING
#Setup for colorbar
ax3 = plt.subplot2grid((2, 60), (0, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
还有一个 MWE 将它们整合在一起:
import matplotlib as mpl
mpl.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
fig, axes = plt.subplots(figsize=(13.6, 7.68))#nrows=2, ncols=2,
gs = gridspec.GridSpec(2, 60)
####
#Waveform
####
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
ax1.plot(times, data, color='k')
ax1.xaxis.set_ticks_position('none')
ax1.yaxis.set_ticks_position('none')
####
#Spectrogram
####
maxFrequency = 5000
Fs = maxFrequency*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
Pxx, freqs, times, cax = ax2.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
ax2.set_ylim([0, maxFrequency])
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('none')
####
#Colorbar (for spectrogram)
####
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
cbar.ax.yaxis.set_tick_params(pad=3, left='off', right='off', labelleft='on', labelright='off')
plt.show()
这是此 MWE 的输出示例:
最好的部分!您只需要在此行中将 0
更改为 1
并将 rowspan
更改为 1
(即 :)
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
使颜色条仅跨越频谱图的高度。这意味着在两个选项之间进行更改非常简单。这是此更改的输出示例:
编辑:GridSpec 实际上未被使用,所以我将其编辑掉。我需要的唯一相关细节涉及调用 subplot2grid()
来设置子图。
使用matplotlib.pyplot,我有两个地块。一种是音频文件的波形。第二个是同一音频的频谱图。我希望波形直接位于频谱图上方(相同的 x 轴,并对齐在一起)。我还想要频谱图的颜色条。
问题 - 当我放入颜色条时,它附加到频谱图行并且波形在颜色条上延伸(即不再与频谱图时间对齐并且比频谱图更宽)。
我想我已经接近解决方案了,但我只是不太清楚我做错了什么或要改变什么才能让它按我想要的方式工作。希望有人能给我指出正确的方向!
使用以下 python 代码(我尽可能将代码设为 MWE):
import matplotlib
matplotlib.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
####
#Waveform
####
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(13.6, 7.68))
plt.subplot(211)
plt.plot(times, data, color='k')
plt.xlabel('time (s)')
plt.xlim(times[0], times[-1])
max_amp = max(abs(np.amin(data)), abs(np.amax(data)))
min_amp = (max_amp * -1) - abs(np.amin(data) - np.amax(data))/50
max_amp = max_amp + abs(np.amin(data) - np.amax(data))/50
plt.ylim(min_amp, max_amp)
ax = plt.gca()
ax.set_yticks(np.array([min_amp, min_amp/2, 0, max_amp/2, max_amp]))
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_tick_params(pad=115)
####
#Spectrogram
####
Fs = 5000*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
plt.subplot(212)
Pxx, freqs, times, cax = plt.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
axes_spec = plt.gca()
axes_spec.set_xlim([0., max(times)])
axes_spec.set_ylim([0, 5000])
plt.xlabel("Time (s)")
plt.ylabel("Frequency (hz)")
plt.colorbar(cax, label='(dB)').ax.yaxis.set_label_position('left')
plt.tight_layout()
plt.show()
我可以得到如下图:
在下面进行这些细微的修改,我可以让情节看起来几乎我想要的样子。问题是,它在颜色条旁边创建了一个空白图形。这个版本,减去空白图,就是我要创建的。
#Replace this for waveform
plt.subplot(221)
#Replace this for spectrogram
plt.subplot(223)
#Add this before colorbar
plt.subplot(122)
新版剧情:
编辑:还有另一种可能性,我也可以接受(或者可能两者都接受,为了更好的衡量标准!)
这是一个基于 matplotlib-2-subplots-1-colorbar 中的答案之一的颜色条示例。 fig.colorbar
中的参数pad
用于指定plots和colorbar之间的space,aspect
用于指定plots的高宽比彩条。 Specgram 将图像作为第四个输出参数输出,因此我将其用于颜色条。
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-');
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs.ravel().tolist(), pad=0.04, aspect = 30)
请务必注意,当使用 ax
参数调用 fig.colorbar
函数时,将调整原始图的大小以为 colorbar
腾出空间。如果它仅应用于其中一个图,则只会调整该轴的大小。下面是:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs[1], pad=0.04, aspect = 30)
下面显示了一种控制原始轴大小调整的方法,以便使用 fig.colorbar
和 cax
参数为颜色条腾出空间,不会进一步调整原始图的大小。这种方法需要在函数 fig.subplots_adjust
:
right
参数,手动为颜色栏腾出一些空间
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y11-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
并通过读取原始两个地块的坐标来执行相同操作以跨越两行:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x00,y00],[x01,y01]] = axs[0].get_position().get_points()
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
我想到的最佳解决方案是 subplot2grid()
函数。这需要使用我最初没有使用的 subplots
。按照这种方法,我需要将所有内容从使用 plt
(matplotlib.pyplot
) 更改为对每个 .plot()
或 .specgram()
调用使用给定绘图的轴。此处包含相关更改:
#No rows or columns need to be specified, because this is handled within a the `subplot2grid()` details
fig, axes = plt.subplots(figsize=(13.6, 7.68))
#Setup for waveform
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
####WAVEFORM PLOTTING
#Setup for spectrogram
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
####SPECTROGRAM PLOTTING
#Setup for colorbar
ax3 = plt.subplot2grid((2, 60), (0, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
还有一个 MWE 将它们整合在一起:
import matplotlib as mpl
mpl.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
fig, axes = plt.subplots(figsize=(13.6, 7.68))#nrows=2, ncols=2,
gs = gridspec.GridSpec(2, 60)
####
#Waveform
####
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
ax1.plot(times, data, color='k')
ax1.xaxis.set_ticks_position('none')
ax1.yaxis.set_ticks_position('none')
####
#Spectrogram
####
maxFrequency = 5000
Fs = maxFrequency*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
Pxx, freqs, times, cax = ax2.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
ax2.set_ylim([0, maxFrequency])
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('none')
####
#Colorbar (for spectrogram)
####
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
cbar.ax.yaxis.set_tick_params(pad=3, left='off', right='off', labelleft='on', labelright='off')
plt.show()
这是此 MWE 的输出示例:
最好的部分!您只需要在此行中将 0
更改为 1
并将 rowspan
更改为 1
(即 :)
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
使颜色条仅跨越频谱图的高度。这意味着在两个选项之间进行更改非常简单。这是此更改的输出示例:
编辑:GridSpec 实际上未被使用,所以我将其编辑掉。我需要的唯一相关细节涉及调用 subplot2grid()
来设置子图。