8

I am trying to achieve waterfall graph of wav file. In my attempts I noticed that this is basically a spectogram in 3d (or as close to what I need). I am trying to do this in Python with numpy and matplotlib.

My main problem is I don't know how to change the plot of specgram from matplotlib into a 3d plot.

Sample of my "code":

sample ,data = wavfile.read('file.wav')
F = Figure()
a = F.add_subplot(111,projection='3d') 
Spec, t, freq, im = a.specgram(data,Fs=2)

I've got this far and have no clue what to do next. I wanna change already existing plot into 3d. I have no code of changing it to 3d, due to lack of my knowledge.

Is it possible to convert 2d plot to 3d ? If so how ? Am i better off constructing a new plot with data from specgram?

The desired outcome would be something like the following: desired result another desired result Thanks for any replies.

John Doe
  • 81
  • 1
  • 4
  • Okay. Sorry for being too broad. I improved my question/ – John Doe Feb 03 '18 at 15:39
  • Have you tried `plot_surface`? The actual problem is still not clear to me. – ImportanceOfBeingErnest Feb 03 '18 at 15:52
  • While the images will probably help a mathematician to understand the higher order problem you are trying to solve, they don't add that much to understanding what specific *programming* question you have. The best way to get specific answers on Stack Overflow is to include actual code that you wrote trying to solve your problem. – anothernode Feb 03 '18 at 15:57
  • ... and to add, of course code needs to be a [mcve]. – ImportanceOfBeingErnest Feb 03 '18 at 16:22
  • No, you cannot "convert" a 2D matplotlib plot to a 3D matplotlib plot. Those are different things. You need to *create* a 3D plot and plot some data to it using any of the methods provided by the Axes3D class. As said in a previous comment, I'd use `plot_surface` for that. – ImportanceOfBeingErnest Feb 03 '18 at 16:27
  • From my understanding of the question you're looking to plot a heightmap. This question suggests that Mayavi has a solution for this https://stackoverflow.com/questions/30706919/how-to-create-a-3d-height-map-in-python – stuartgm Feb 03 '18 at 16:30
  • Thanks for the answer ! @ImportanceOfBeingErnest i should be able to take output of `specgram` and use it to plot surface, right ? – John Doe Feb 03 '18 at 16:41
  • 2
    You may use `spec, freqs, t = matplotlib.mlab.specgram(...)` to generate the data without automatically creating a plot. – ImportanceOfBeingErnest Feb 03 '18 at 17:40

1 Answers1

3

Here are 3D & 2D spectrogram plots of an example signal from scipy that you can find at the end of this page.

enter image description here

enter image description here

from matplotlib import mlab
import matplotlib.pyplot as plt
import numpy as np

# Fixing random state for reproducibility
np.random.seed(666)

title = ('2 Vrms sine wave with modulated frequency around 3kHz, '
         'corrupted by white noise of exponentially decreasing '
         'magnitude sampled at 10 kHz.')

fs = 10e3
N = 1e5
amp = 2 * np.sqrt(2)
noise_power = 0.01 * fs / 2
t = np.arange(N) / float(fs)
mod = 500*np.cos(2*np.pi*0.25*t)
carrier = amp * np.sin(2*np.pi*3e3*t + mod)
noise = np.random.normal(scale=np.sqrt(noise_power), size=t.shape)
noise *= np.exp(-t/5)
y = carrier + noise

def specgram3d(y, srate=44100, ax=None, title=None):
  if not ax:
    ax = plt.axes(projection='3d')
  ax.set_title(title, loc='center', wrap=True)
  spec, freqs, t = mlab.specgram(y, Fs=srate)
  X, Y, Z = t[None, :], freqs[:, None],  20.0 * np.log10(spec)
  ax.plot_surface(X, Y, Z, cmap='viridis')
  ax.set_xlabel('time (s)')
  ax.set_ylabel('frequencies (Hz)')
  ax.set_zlabel('amplitude (dB)')
  ax.set_zlim(-140, 0)
  return X, Y, Z

def specgram2d(y, srate=44100, ax=None, title=None):
  if not ax:
    ax = plt.axes()
  ax.set_title(title, loc='center', wrap=True)
  spec, freqs, t, im = ax.specgram(y, Fs=fs, scale='dB', vmax=0)
  ax.set_xlabel('time (s)')
  ax.set_ylabel('frequencies (Hz)')
  cbar = plt.colorbar(im, ax=ax)
  cbar.set_label('Amplitude (dB)')
  cbar.minorticks_on()
  return spec, freqs, t, im

fig1, ax1 = plt.subplots()
specgram2d(y, srate=fs, title=title, ax=ax1)

fig2, ax2 = plt.subplots(subplot_kw={'projection': '3d'})
specgram3d(y, srate=fs, title=title, ax=ax2)
  
plt.show()

BONUS:

You can listen to the signal by creating a wav file using scipy:

from scipy.io import wavfile
wavfile.write('sig.wav', int(fs), y)
OpSocket
  • 997
  • 11
  • 19