Plot a Wave files Audio Visually In Python - python

I am trying to figure out how to plot the audio visually of a wav file. In my code if I do a wavefile.readframe(-1) I get the whole wav file plotted, the way my code works now is I just get a silver (one frame!) I'd like to show 24 frames of audio on each image plot from the wave file so I can animate it. Hopefully this is clear.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import wave , sys , os , struct
waveFile = wave.open('mono.wav','r')
length = waveFile.getnframes()
for i in range(0,length):
print i # so we know where we are at.
waveData = waveFile.readframes(i)
fs = waveFile.getframerate()
signal = np.fromstring(waveData, 'Int16')
Time=np.linspace(0, len(signal)/fs, num=len(signal))
plt.axis('off')
plt.plot(Time,signal , 'w')
plt.savefig('signal' + str(i) + '.png' , facecolor='none', edgecolor='none', transparent=True, frameon=False)
plt.close

From the documentation:
Wave_read.readframes(n)
Reads and returns at most n frames of audio, as a string of bytes.
So to read a chunk of 24 frames you simply call
waveData = waveFile.readframes(24)
When you open the file in read ('r') mode, the file pointer starts out at the 0th frame. As you read frames from the file, you will advance the file pointer by the same number of frames. This means that calling waveFile.readframes(24) repeatedly will yield consecutive chunks of 24 frames until you hit the end of the file - there's no need to pass a changing index i.
To keep track of where you are within the file, you can call waveFile.tell(), and to skip forwards or backwards to the kth frame you can use waveFile.setpos(k).
By the way, this behaviour is very consistent with how standard file objects work in Python.

I recoded a bit , the above answered help, but I needed to do more massaging. So if you have a need to plot audio this way in realtime just adjust the readframes for as many frames you would like to take in. To plot each frame I wound up having to make seperate plt.figure id's This code snip will get you where you want to go
wave_file = wave.open('mono.wav', 'r')
data_size = wave_file.getnframes()
sample_rate = wave_file.getframerate()
while True:
waveData = wave_file.readframes(10000)
signal = np.fromstring(waveData , 'Int16')
Time=np.linspace(0, len(signal), num=len(signal))
plt.figure()
fig = plt.figure(figsize=(xinch,yinch) , frameon=False)
#fig = plt.figure(frameon=False)
ax = fig.add_axes([0, 0, 1, 1])
#ax.axis('off')
plt.axis('off')
line = plt.plot(Time,signal , 'w')
plt.setp(line, linewidth=10)
plt.savefig('signal' + str(x) + '.png')
plt.close
x+= 1
if wave_file.tell() == data_size:
break
Will result in frames like this:

Related

Isolating audio foreground and converting back to audio stream using librosa

I'm trying to isolate the foreground of an audio stream and then save it as a standalone audio stream using librosa.
Starting with this seemingly relevant example.
I have the full, foreground and background data isolated as the example does in S_full, S_foreground and S_background but I'm unsure as to what to do to use those as audio.
I attempted to use librosa.istft(...) to convert those and then save that as a .wav file using soundfile.write(...) but I'm left with a file of roughly the right size but unusable(?) data.
Can anyone describe or point me at an example?
Thanks.
in putting together the minimal example,
istft() with the original sampling rate does in fact work.
I'll find my bug, somewhere.
FWIW here's the working code
import numpy as np
import librosa
from librosa import display
import soundfile
import matplotlib.pyplot as plt
y, sr = librosa.load('audio/rb-testspeech.mp3', duration=5)
S_full, phase = librosa.magphase(librosa.stft(y))
S_filter = librosa.decompose.nn_filter(S_full,
aggregate=np.median,
metric='cosine',
width=int(librosa.time_to_frames(2, sr=sr)))
S_filter = np.minimum(S_full, S_filter)
margin_i, margin_v = 2, 10
power = 2
mask_v = librosa.util.softmask(S_full - S_filter,
margin_v * S_filter,
power=power)
S_foreground = mask_v * S_full
full = librosa.amplitude_to_db(S_full, ref=np.max)
librosa.display.specshow(full, y_axis='log', sr=sr)
plt.title('Full spectrum')
plt.colorbar()
plt.tight_layout()
plt.show()
print("y({}): {}".format(len(y),y))
print("sr: {}".format(sr))
full_audio = librosa.istft(S_full)
foreground_audio = librosa.istft(S_foreground)
print("full({}): {}".format(len(full_audio), full_audio))
soundfile.write('orig.WAV', y, sr)
soundfile.write('full.WAV', full_audio, sr)
soundfile.write('foreground.WAV', foreground_audio, sr)

How to add matplotlib output to a video

I'm trying to create a video with annotation (currently) formed from matplotlib, with the original video on the left and a FFT of some parameters on the right.
The only way I've gotten it to work is by saving a .png file for each frame, which seems tedious. I was hoping someone could point out the 'correct' method.
import cv2
import numpy as np
import matplotlib
import scipy.fftpack
from moviepy.editor import VideoFileClip
from moviepy.editor import AudioFileClip
vid = VideoFileClip('VID.mp4')
aud = AudioFileClip('VID.mp4')
out = cv2.VideoWriter('1234.avi',cv2.VideoWriter_fourcc('M','J','P','G'), vid.fps, (vid.w*2, vid.h))
audIndex = 0
vidIndex = 0
numberOfSamples = 600
sampleRate = 800;
T = 1.0 / sampleRate;
x = np.linspace(0.0, numberOfSamples*T, numberOfSamples)
for frame in vid.iter_frames():
# Put the recorded movie on the left side of the video frame
frame2 = np.zeros((frame.shape[0], 2*frame.shape[1], 3)).astype('uint8')
frame2[:720, :1280,:] = frame
# Put, say, a graph of the FFT on the right side of the video frame
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), numberOfSamples/2)
fig, ax = matplotlib.pyplot.subplots()
ax.plot(xf, 2.0/numberOfSamples * np.abs(yf[:numberOfSamples//2]))
matFigureForThisFrame = ????????
# Put the FFT graph on the left side of this video frame
frame2[720:, 1280:, :] = matFigureForThisFrame
out.write(frame2)
vidIndex = vidIndex+1;
out.release()
#cv2.destroyAllWindows()
You could try to take the path of writing to a video file directly, but I wouldn't recommend it (see here why). Writing to video file is more complicated than just changing the frames, you need to need to get the right coders and other painful issues. Personally, I would settle. Some options:
1) Generate the pngs, and afterwards concatenating them to a video file using ffmpeg
2) Save each frame to a buffer, and generate .gif file afterwards, directly in python (so you don't have to run multiple things). See this stackoverflow question on how to do that.

loop over many files and plot them

Thanks for the time spending reading it, maybe it is a simple question.
I have a file like this (they are like 200 files):
Output of SMC2FS2: FAS for file 20123427.CB2A.BHE.sac.smc
Nfreq_out =
8192
freq fas
0.0000000E+00 6.6406252E-03
2.4414062E-03 1.3868844E+04
4.8828125E-03 3.0740834E+04
7.3242188E-03 2.7857139E+04
9.7656250E-03 1.6535047E+04
1.2207031E-02 9.7825762E+03
1.4648438E-02 6.1421987E+03
1.7089844E-02 6.5783145E+03
1.9531250E-02 5.6137949E+03
2.1972656E-02 3.5297178E+03
To read them, to skip the header and to start the processing:
#define the path where I have the 200 files
pato='D:\\Seismic_Inves\\flc_grant\\120427\\smc2fs\\smooth'
os.chdir(pato)
lista=[]
#list all files with "kono_"
for a in glob.glob('*kono_*'):
lista.append(a)
#read and skip the header for all files
for archis in lista:
with open(archis,'r') as leo:
for _ in range(4):
next(leo)
#start the proccesing
for line in leo:
leo=[x.strip() for x in leo if x.strip()]
leos=[tuple(map(float,x.split())) for x in leo[1:]]
f=[x[0] for x in leos]
fas=[x[1] for x in leos]
plt.figure(1)
plt.plot(f,fas,'r')
plt.yscale('log')
plt.xscale('log')
plt.show()
As you can imagine it is a plot of Frequency vs Amplitude (FAS plot)
The code works well, but open a figure and plot just one file, then I need to close the figure and it will plot the second file and so on.
The question is:
How can I plot all the data (the 200 fcsv iles) in just one figure.
to #GlobalTraveler, this is the result using your suggestion:
FAS Konoomachi_smooth_data
Add the block argument to show -> plt.show(block = False) or move show outside the for loop
However in the grandscheme of things I would suggest moving the code to more OO approach. For example:
#define the path where I have the 200 files
from matplotlib.pyplot import subplots, show
pato='D:\\Seismic_Inves\\flc_grant\\120427\\smc2fs\\smooth'
os.chdir(pato)
lista=[]
#list all files with "kono_"
for a in glob.glob('*kono_*'):
lista.append(a)
#read and skip the header for all files
fig, ax = subplots() # open figure and create axis
for archis in lista:
with open(archis,'r') as leo:
for _ in range(4):
next(leo)
#start the proccesing
for line in leo:
leo=[x.strip() for x in leo if x.strip()]
leos=[tuple(map(float,x.split())) for x in leo[1:]]
f=[x[0] for x in leos]
fas=[x[1] for x in leos]
ax.plot(f,fas,'r') # plot on this axis
ax.set(**dict(xscale = 'log', yscale = 'log')) # format the axis
show() # show
it is the result with your suggestion
FAS_konoomachi_smooth

Save contour images generated in a loop as a single pdf file (2 images per page preferably)

I have written this code which will generate a number of contour plots, each of which corresponds to a single text file. I have multiple text files. Currently, I am able to generate all of the images separately in png format without any issues.
When I try to save the images as a pdf file, it is saving only the last image generated in a loop.I tried using the PdfPages package. This question is similar to the one that I posted before but with a different question. Similar
Issue: I want to able to generate all of the images into a single pdf file automatically from python. So for eg. if I have 100 text files, then I want to save all of the 100 images onto a single pdf file.Also ideally I want to save 2 images in a single page in the pdf file. There are some questions in SO about this, but I couldn't find an appropriate solution for my issue. Since I have many case for which I have to generate the images, I want to save them as a single pdf file as it is more easier to analyze them. I would appreciate any suggestions/advice to help me with this.
This is link for the sample text file Sample Text
ges
from __future__ import print_function
import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import griddata
from matplotlib.backends.backend_pdf import PdfPages
path = 'location of the text files'
FT_init = 5.4311
delt = 0.15
TS_init = 140
dj_length = 2.4384
def streamfunction2d(y,x,Si_f,q):
with PdfPages('location of the generated pdf') as pdf:
Stf= plt.contour(x,y,Si_f,20)
Stf1 = plt.colorbar(Stf)
plt.clabel(Stf,fmt='%.0f',inline=True)
plt.figtext(0.37,0.02,'Flowtime(s)',style= 'normal',alpha=1.0)
plt.figtext(0.5,0.02,str(q[p]),style= 'normal',alpha=1.0)
plt.title('Streamfunction_test1')
plt.hold(True)
plt.tight_layout()
pdf.savefig()
path1 = 'location where the image is saved'
image = path1+'test_'+'Stream1_'+str((timestep[p]))+'.png'
plt.savefig(image)
plt.close()
timestep = np.linspace(500,600,2)
flowtime = np.zeros(len(timestep))
timestep = np.array(np.round(timestep),dtype = 'int')
###############################################################################
for p in range(len(timestep)):
if timestep[p]<TS_init:
flowtime[p] = 1.1111e-01
else:
flowtime[p] = (timestep[p]-TS_init)*delt+FT_init
q = np.array(flowtime)
timestepstring=str(timestep[p]).zfill(4)
fname = path+"ddn150AE-"+timestepstring+".txt"
f = open(fname,'r')
data = np.loadtxt(f,skiprows=1)
data = data[data[:, 1].argsort()]
data = data[np.logical_not(data[:,11]== 0)]
Y = data[:,2] # Assigning Y to column 2 from the text file
limit = np.nonzero(Y==dj_length)[0][0]
Y = Y[limit:]
Vf = data[:,11]
Vf = Vf[limit:]
Tr = data[:,9]
Tr = Tr[limit:]
X = data[:,1]
X = X[limit:]
Y = data[:,2]
Y = Y[limit:]
U = data[:,3]
U = U[limit:]
V = data[:,4]
V = V[limit:]
St = data[:,5]
St = St[limit:]
###########################################################################
## Using griddata for interpolation from Unstructured to Structured data
# resample onto a 300x300 grid
nx, ny = 300,300
# (N, 2) arrays of input x,y coords and dependent values
pts = np.vstack((X,Y )).T
vals = np.vstack((Tr))
vals1 = np.vstack((St))
# The new x and y coordinates for the grid
x = np.linspace(X.min(), X.max(), nx)
y = np.linspace(Y.min(), Y.max(), ny)
r = np.meshgrid(y,x)[::-1]
# An (nx * ny, 2) array of x,y coordinates to interpolate at
ipts = np.vstack(a.ravel() for a in r).T
Si = griddata(pts, vals1, ipts, method='linear')
print(Ti.shape,"Ti_Shape")
Si_f = np.reshape(Si,(len(y),len(x)))
print(Si_f.shape,"Streamfunction Shape")
Si_f = np.transpose(Si_f)
streamfunction2d(y,x,Si_f,q)
Edit : As you mentioned matplotlib is probably able to handle everything by itself using PdfPages function. See this related answer. My original answer is a hack.
I think the error in your code is that you are creating another PdfPage object each time you go through the loop. My advice would be to add the PdfPage object as an argument to your streamfunction2d function and create the PdfPage object once and for all before the loop (using a with statement as in the documentation seems a good idea).
Example:
def streamfunction2d(y,x,Si_f,q,pdf):
# (...)
pdf.savefig(plt.gcf())
with PdfPages('output.pdf') as pdf:
for p in range(len(timestep)):
# (...)
streamfunction2d(y,x,Si_f,q,pdf)
Original answer:
Here is a quick and dirty solution using the pdfunite software.
from matplotlib import pyplot as plt
import numpy as np
import subprocess
import os
X = np.linspace(0,1,100)
for i in range(10):
# random plot
plt.plot(X,np.cos(i*X))
# Save each figure as a pdf file.
plt.savefig("page_{:0}.pdf".format(i))
plt.clf()
# Calling pdfunite to merge all the pages
subprocess.call("pdfunite page_*.pdf united.pdf",shell=True)
# Removing temporary files
for i in range(10):
os.remove("page_{:0}.pdf".format(i))
It uses two things:
You can save your figures as pdf using matplotlib's savefig command.
You can call other programs using the subprocess library. I used pdfunite to merge all the pages. Be sure it is available on your machine !
If you want to have several graph by page, you can use subplots.
Alternatively, you could use another python library (such as pyPDF) to merge the pages, but it would require slightly more code. Here is an (untested) example:
from matplotlib import pyplot as plt
import numpy as np
from pyPdf import PdfFileWriter, PdfFileReader
# create an empty pdf file
output = PdfFileWriter()
X = np.linspace(0,1,100)
for i in range(10):
# random plot
plt.plot(X,np.cos(i*X))
# Save each figure as a pdf file.
fi = "page_{:0}.pdf".format(i)
plt.savefig(fi)
plt.clf()
# add it to the end of the output
input = PdfFileReader(file(fi, "rb"))
output.addPage(input.getPage(0))
# Save the resulting pdf file.
outputStream = file("document-output.pdf", "wb")
output.write(outputStream)

Quitting matplotlib.pyplot animation gracefully

I have a script that plots data of some photometry apertures, and I want to plot them in an xy plot. I am using matplotlib.pyplot with python 2.5.
The input data is stored in around 500 files and read. I am aware that this is not the most efficient way of inputting the data but that's another issue...
Example code:
import matplotlib.pyplot as plt
xcoords = []
ycoords = []
# lists are populated with data from first file
pltline, = plt.plot(xcoords, ycoords, 'rx')
# then loop populating the data from each file
for file in filelist:
xcoords = [...]
ycoords = [...]
pltline.set_xdata(xcoords)
pltline.set_ydata(ycoords)
plt.draw()
As there are over 500 files, I will occasionally want to close the animation window in the middle of the plotting. My code to plot works but it doesn't exit very gracefully. The plot window does not respond to clicking the close button and I have to Ctrl+C out of it.
Can anyone help me find a way to close the animation window while the script is running whilst looking graceful (well more graceful than a series of python traceback errors)?
If you update the data and do the draw in a loop, you should be able to interrupt it. Here's an example (that draws a stationary circle and then moves a line around the perimeter):
from pylab import *
import time
data = [] # make the data
for i in range(1000):
a = .01*pi*i+.0007
m = -1./tan(a)
x = arange(-3, 3, .1)
y = m*x
data.append((clip(x+cos(a), -3, 3),clip(y+sin(a), -3, 3)))
for x, y in data: # make a dynamic plot from the data
try:
plotdata.set_data(x, y)
except NameError:
ion()
fig = figure()
plot(cos(arange(0, 2.21*pi, .2)), sin(arange(0, 2.21*pi, .2)))
plotdata = plot(x, y)[0]
xlim(-2, 2)
ylim(-2, 2)
draw()
time.sleep(.01)
I put in the time.sleep(.01) command to be extra sure that I could break the run, but in my tests (running Linux) it wasn't necessary.

Categories