I have a custom "waveform"-class which I use for tkinter-applications. Due to testing reasons, i would like to see a spectrogram via librosa.display.specshow() without calling any tkinter-app. Sadly, the following code does not produce an output:
from matplotlib.figure import Figure
from matplotlib.pyplot import show
import librosa as lr
import librosa.display as lrd
class waveform():
def __init__(self, fp):
self.sig, self.sr = lr.load(fp, sr=None, res_type="polyphase")
X = lr.stft(self.sig, n_fft=2**13)
Xdb = lr.amplitude_to_db(abs(X))
self.figure = Figure(figsize=(10, 8), dpi=80)
self.ax = self.figure.add_subplot()
lrd.specshow(Xdb, sr=self.sr, x_axis="time", y_axis="log", ax=self.ax, cmap='viridis')
if __name__ == "__main__":
wv = waveform("./noise.wav")
show()
Are calls to matplotlib (which is what specshow is doing in the background) not rendered when inside of a class constructor?
The problem seems to come from the fact that you use matplotlib.figure which is not managed by pyplot.
Changing the import works for me
from matplotlib.pyplot import figure
# instead of from matplotlib.figure import Figure
# ...
class waveform():
# ...
self.figure = figure(figsize=(10, 8), dpi=80)
# (Just replaced Figure by figure)
# The rest is the same
However, I am not sure if it fits with your use case, so probably a good read is: https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.show
Related
I'm running a script on a Raspberry Pi, where I pull some data from an accelerometer, and I want to continuously update a plot. I've read around and watched some youtube videos and I've decided for the Matplotlib animation.
Now everything kinda works but the plot doesn't really get drawn before I KeyboardInterrupt the script. And I see why, because the plot function is inside the loop. I've tried moving it out of the loop,but then it doesn't work at all.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
style.use("fivethirtyeight")
import time
import board
import busio
import adafruit_tca9548a
import adafruit_adxl34x
i2c = busio.I2C(board.SCL, board.SDA)
tca = adafruit_tca9548a.TCA9548A(i2c)
ad1 = adafruit_adxl34x.ADXL345(tca[0])
ad2 = adafruit_adxl34x.ADXL345(tca[2])
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
G = []
while True:
x,y,z = ad2.acceleration
acc = [x,y,z]
acc_abs = [abs(a) for a in acc]
i = acc_abs.index(max(acc_abs))
stor = acc[i]
if stor>0:
stor = stor-10.52
elif stor<0:
stor = stor+10.52
time.sleep(1)
G.append(stor)
ax1.clear()
ax1.plot(G)
ani = animation.FuncAnimation(fig,animate,interval =1000)
plt.show()
I wrote a program and would like to give to the user the opportunity to run it either on compute with a graphical environment or not.
Currently through hard coding it I can do either one or the other by changing the matplotlib import at the top of my program file, before importing pyplot.
with graphical environment
import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
...
without graphical environment
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
...
the remaining code would look something like that:
...
import os, sys, argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX")
params = parser.parse_args()
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
Is it possible to change the backend based on the noX parameter value?
You can set the backend o a condition:
import matplotlib
if not params.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
If you only plot inside main, move your import inside this function:
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
import matplotlib
if not params.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
If you want to do plotting everywhere, use special function to parse the
command line arguments and call it only once.
import argparse
def _parse_cmd_args():
"""Parse command line args.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
return params
PARAMS = _parse_cmd_args()
# Want to prevent any further call to `_parse_cmd_args()`?
# Un-comment the following line:
# del _parse_cmd_args
import matplotlib
if not PARAMS.noX:
matplotlib.use("agg")
else:
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
def main():
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not PARAMS.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
Did you try? As a general rule you can't change the backend. So you will have to pospone the decision until you know the value of noX. Keep in mind that you can import at any time and any place. It is adviced to import at the beginning of the file but that's not always possible.
Ok so combining the idea of Mike with the global import of plt I come up with something like that to do the trick. Sorry for bothering you, it was actually simpler than first envisaged.
import os, sys, argparse
import matplotlib
matplotlib.use("agg") if "--noX" in sys.argv else matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--noX", action="store_true", dest="noX", default=False)
params = parser.parse_args()
data = [0,1,2,3,4,5]
fig, ax = plt.subplots()
ax.plot(data)
if not params.noX:
plt.show()
plt.savefig("foo.png")
sys.exit(0)
if __name__ == "__main__":
main()
I am trying to plot real time data coming to the computer using python. Data comes in a ROS topic and I use 'rospy' to subscribe to the topic in order to get data.
This is the code I wrote
import rospy
from sensor_msgs.msg import ChannelFloat32
import matplotlib.pyplot as plt
N = 200
i = 0
topic = "chatter"
x = range(N)
lmotor = [0]*N
rmotor = [0]*N
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim([0,N])
ax.set_ylim([-1,1])
line1, = ax.plot(lmotor, 'r-')
line2, = ax.plot(rmotor, 'g')
def plotThrottle(data):
global x, lmotor, rmotor, i
[x[i],lmotor[i],rmotor[i], tmp] = data
line1.set_ydata(lmotor)
line1.set_xdata(x)
line2.set_ydata(rmotor)
line2.set_xdata(x)
fig.canvas.draw()
def callBack(packet):
data = list(packet.values)
plotThrottle(data)
def listner():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber(topic, ChannelFloat32, callBack)
rospy.spin()
if __name__ == '__main__':
listner()
My problem is when I call plotThrottle() with the data I got from rostopic, I get following error.
[ERROR]
[WallTime: 1454388985.317080] bad callback: <function callBack at 0x7f13d98ba6e0>
Traceback (most recent call last):
File "/opt/ros/indigo/lib/python2.7/dist-packages/rospy/topics.py", line 720, in _invoke_callback
cb(msg)
File "dummy2.py", line 41, in callBack
plotThrottle(data)
File "dummy2.py", line 37, in plotThrottle
fig.canvas.draw()
File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_tkagg.py", line 349, in draw
tkagg.blit(self._tkphoto, self.renderer._renderer, colormode=2)
File "/usr/lib/pymodules/python2.7/matplotlib/backends/tkagg.py", line 13, in blit
tk.call("PyAggImagePhoto", photoimage, id(aggimage), colormode, id(bbox_array))
RuntimeError: main thread is not in main loop
But if I use the same function and pass some data generated within the code (some random data) plot works fine.
I am an absolute beginner to python. I searched about this error and it says that this is because of some threading problem. But I don't understand how to fix this code. I am really grateful if someone can explain the problem and help fix this code.
Here you have two threads running, rospy.spin() and top.mainloop() (from Tkinter, backend of matplotlib in your case).
From this answer:
The problems stem from the fact that the _tkinter module attempts to
gain control of the main thread via a polling technique when
processing calls from other threads.
Your Tkinter code in Thread-1 is trying to peek into the main thread
to find the main loop, and it's not there.
From this answer:
If there is another blocking call that keeps your program running,
there is no need to call rospy.spin(). Unlike in C++ where spin() is
needed to process all the threads, in python all it does is block.
So you can use plt.show(block=True) to keep your program from closing, in that case you will use Tkinter mainloop, redrawing your canvas without problems.
The listener fuction should look like this:
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber(topic, ChannelFloat32, callBack)
# rospy.spin()
plt.show(block=True)
Anyway this seems a bit a workaround for other alternatives see again this answer or simply use separate node for plotting i.e. ros suggested tools like rqt_graph.
Since this is an old post and still seems to be active in the community, I am going to provide an example, in general, how can we do real-time plotting. Here I used matplotlib FuncAnimation function.
import matplotlib.pyplot as plt
import rospy
import tf
from nav_msgs.msg import Odometry
from tf.transformations import quaternion_matrix
import numpy as np
from matplotlib.animation import FuncAnimation
class Visualiser:
def __init__(self):
self.fig, self.ax = plt.subplots()
self.ln, = plt.plot([], [], 'ro')
self.x_data, self.y_data = [] , []
def plot_init(self):
self.ax.set_xlim(0, 10000)
self.ax.set_ylim(-7, 7)
return self.ln
def getYaw(self, pose):
quaternion = (pose.orientation.x, pose.orientation.y, pose.orientation.z,
pose.orientation.w)
euler = tf.transformations.euler_from_quaternion(quaternion)
yaw = euler[2]
return yaw
def odom_callback(self, msg):
yaw_angle = self.getYaw(msg.pose.pose)
self.y_data.append(yaw_angle)
x_index = len(self.x_data)
self.x_data.append(x_index+1)
def update_plot(self, frame):
self.ln.set_data(self.x_data, self.y_data)
return self.ln
rospy.init_node('lidar_visual_node')
vis = Visualiser()
sub = rospy.Subscriber('/dji_sdk/odometry', Odometry, vis.odom_callback)
ani = FuncAnimation(vis.fig, vis.update_plot, init_func=vis.plot_init)
plt.show(block=True)
Note: change the rospy.Subscriber('/dji_sdk/odometry', Odometry, vis.odom_callback) as you need and do necessary changes accordingly.
The Chaco plotting toolkit for Python includes examples that show how to dynamically update existing plots. However, my application requires that I dynamically create and destroy plots depending on the data. I am new to programming with Chaco and Traits, so a simple example that illustrates how to do this would be really helpful.
This is a bit late, but here's an example that creates and destroys Chaco plots. The main interface is PlotSelector, which defines some fake data and radio buttons to switch between two different plot styles (line and bar plots).
This example uses a Traits event to signal when to close a plot, and then handles that signal with PlotController. There may be a better way to close the window, but I couldn't find one.
Edit: Updated imports for newer versions of Traits, Chaco, and Enable (ETS 4 instead of 3).
import numpy as np
import traits.api as traits
import traitsui.api as ui
import chaco.api as chaco
from enable.api import ComponentEditor
class PlotController(ui.Controller):
view = ui.View(ui.Item('plot', editor=ComponentEditor(), show_label=False),
height=300, width=300, resizable=True)
def object_close_signal_changed(self, info):
info.ui.dispose()
class BasicPlot(traits.HasTraits):
close_signal = traits.Event()
plot = traits.Instance(chaco.Plot)
class LinePlot(BasicPlot):
def __init__(self, plotdata):
self.plot = chaco.Plot(plotdata)
self.plot.plot(('x', 'y'))
class BarPlot(BasicPlot):
def __init__(self, plotdata):
self.plot = chaco.Plot(plotdata)
self.plot.candle_plot(('x', 'ymin', 'ymax'))
available_plot_types = dict(line=LinePlot, bar=BarPlot)
class PlotSelector(traits.HasTraits):
plot_type = traits.Enum(['line', 'bar'])
traits_view = ui.View('plot_type', style='custom')
def __init__(self, x, y):
ymin = y - 1
ymax = y + 1
self.plotdata = chaco.ArrayPlotData(x=x, y=y, ymin=ymin, ymax=ymax)
self.figure = None
def _plot_type_changed(self):
plot_class = available_plot_types[self.plot_type]
if self.figure is not None:
self.figure.close_signal = True
self.figure = plot_class(self.plotdata)
controller = PlotController(model=self.figure)
controller.edit_traits()
N = 20
x = np.arange(N)
y = x + np.random.normal(size=N)
plot_selector = PlotSelector(x, y)
plot_selector.configure_traits()
Note that the main interface (PlotSelector) calls configure_traits (starts application), while the plots are viewed with edit_traits (called from within application). Also, note that this example calls edit_traits from PlotController instead of calling it from the model. You could instead move the view from PlotController to BasicPlot and set the handler method of that view to PlotController.
Finally, if you don't need to totally destroy the plot window, then you may want to look at the Plot object's delplot method, which destroys the *sub*plot (here the line plot or bar plot).
I hope that helps.
I have a module to be used in iPython.
I'd like a user to enter everything needed to make a plot- x, y, label, linewidth, etc.
So the user might do something like this:
In[1] import this_script
In[2] x=range(0,10)
In[3] y=x
In[4] magically_exposed_function plot(x,y,'r+', linewidth=2)
This means that my function gets the string plot(x,y,'r+', linewidth=2). This can be parsed and
the values of x and y found in the iPython namespace using ip.user_ns, but I'm still stuck on
what to do with 'r+' and linewidth=2. Ideally I'd like to be able to:
a) import the entire iPython namespace so that I have the values of x and y available and
b) throw the entire string into plot()
As for b), having something like:
plot_string = x, y, 'r+', linewidth = 2
plot(plot_string)
would be ideal, but this does not work as shown above.
Is this possible to do either of these things? Is there a more graceful solution?
Could the user perhaps do plot(x,y), and my code could grab ahold of that plot and edit it?
Any advice on how to handle this situation would be greatly appreciated :)
Thanks!
--Erin
[EDIT] A demo of what I'd like to be able to do:
import matplotlib
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanv
from matplotlib.figure import Figure
import IPython.ipapi
ip = IPython.ipapi.get()
import sys
class WrapperExample(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, None, -1)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args, **kwargs)
self.canvas = FigCanv(self, -1, self.figure)
def run_me(*args, **kwargs):
""" Plot graph from iPython
Example:
In[1] import script
In[2] x=range(0,10)
In[3] y=x
In[4] run_me x y
"""
app = wx.PySimpleApp()
wrap = WrapperExample(*args, **kwargs)
wrap.Show()
app.MainLoop()
ip.expose_magic("run_me", run_me)
[EDIT] The following is how I ended up using the wrapper suggested below:
import wx
import matplotlib
from pylab import *
import IPython.ipapi
ip = IPython.ipapi.get()
class MainCanvas(wx.Frame):
def __init__(self, *args):
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.axes.plot(*args)
show()
def run_this_plot(self, arg_s=''):
""" Run
Examples
In [1]: import demo
In [2]: rtp x y <z>
Where x, y, and z are numbers of any type
"""
args = []
for arg in arg_s.split():
try:
args.append(self.shell.user_ns[arg])
except KeyError:
raise ValueError("Invalid argument: %r" % arg)
mc = MainCanvas(*args)
# Activate the extension
ip.expose_magic("rtp", run_this_plot)
Parsing the actual string is better left to python. Maybe you want to create a wrapper:
real_plot = plot
def my_plot(*args, **kwargs):
x, y = args[0], args[1]
...your extra code here...
real_plot(*args, **kwargs)
plot = my_plot