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()
Related
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
I would like to continue my python script after the schedule.every(x).seconds.until(x).do(job), but my program end the function until the x time and doesn't keep going after.
Here is my program :
import requests
import json
import pickle
import schedule
import sys
import time
import pandas as pd
from datetime import datetime, timedelta
from threading import Timer
import matplotlib
from datetime import datetime
import matplotlib.pyplot as plt
from colorama import Fore, Back, Style
import math
import numpy as np
import os
os.environ["PATH"] += os.pathsep + '/Library/TeX/texbin'
key = 'MY_API_KEY'
adress = 'https://api.openweathermap.org/data/2.5/weather'
params = {'appid':key, 'q': 'Lausanne', 'units':'metric'}
def somme(tab):
s=0
for i in range(len(tab)):
s=s+tab[i]
return s
def moyenne(tab):
return somme(tab)/len(tab)
tab=[]
tab2=[]
def function(tab,tab2):
response = requests.get(adress, params=params)
weather = response.json()
temp = weather['main']['temp']
print(temp)
tab.append(temp)
now = datetime.now()
time = now.strftime("%H:%M:%S")
tab2.append(time)
print(tab)
print(tab2)
print(moyenne(tab))
function(tab,tab2)
schedule.every(10).seconds.until("20:00").do(function, tab, tab2)
while True :
schedule.run_pending()
time.sleep(1)
fig = plt.figure(1,figsize=(9, 7))
ax = fig.add_subplot()
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.plot(tab2,tab, 'b.')
plt.show()
I would like that my program plot the graph after it had done the schedule. Is it possible ?
Thank you for your help !
EDIT :
Thank you to Tim Roberts for the answer in the comments !
I just have to change my loop like this while datetime.now().hour < 20:
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 want Bokeh to update periodically and arbitrarily when the results from a separate algorithm running in python returns results, not based on any input from the Bokeh interface.
I've tried various solutions but they all depend on a callback to a some UI event or a periodic callback as in the code below.
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid
from bokeh.models.glyphs import MultiLine
from time import sleep
from random import randint
def getData(): # simulate data acquisition
# run slow algorith
sleep(randint(2,7)) #simulate slowness of algorithm
return dict(xs=np.random.rand(50, 2).tolist(), ys=np.random.rand(50, 2).tolist())
# init plot
source = ColumnDataSource(data=getData())
plot = Plot(
title=None, plot_width=600, plot_height=600,
min_border=0, toolbar_location=None)
glyph = MultiLine(xs="xs", ys="ys", line_color="#8073ac", line_width=0.1)
plot.add_glyph(source, glyph)
xaxis = LinearAxis()
plot.add_layout(xaxis, 'below')
yaxis = LinearAxis()
plot.add_layout(yaxis, 'left')
plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
curdoc().add_root(plot)
# update plot
def update():
bokeh_source = getData()
source.stream(bokeh_source, rollover=50)
curdoc().add_periodic_callback(update, 100)
This does seem to work, but is this the best way to go about things? Rather than having Bokeh try to update every 100 milliseconds can I just push new data to it when it becomes available?
Thanks
You can use zmq and asyncio to do it. Here is the code for the bokeh server, it wait data in an async coroutine:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from functools import partial
from tornado.ioloop import IOLoop
import zmq.asyncio
doc = curdoc()
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:1234")
socket.setsockopt(zmq.SUBSCRIBE, b"")
def update(new_data):
source.stream(new_data, rollover=50)
async def loop():
while True:
new_data = await socket.recv_pyobj()
doc.add_next_tick_callback(partial(update, new_data))
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure(height=300)
plot.line(x='x', y='y', source=source)
doc.add_root(plot)
IOLoop.current().spawn_callback(loop)
to send the data just run following code in another python process:
import time
import random
import zmq
context = zmq.Context.instance()
pub_socket = context.socket(zmq.PUB)
pub_socket.bind("tcp://127.0.0.1:1234")
t = 0
y = 0
while True:
time.sleep(1.0)
t += 1
y += random.normalvariate(0, 1)
pub_socket.send_pyobj(dict(x=[t], y=[y]))
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