I'm trying to create a GUI in TraitsUI that includes two Mayavi figures. I have implemented these figures as per the multiple engines example in the Mayavi documentation.
However, when I add a colorbar to one of the figures and run the GUI script it sometimes opens a blank Mayavi Scene Editor window in addition to the desired TraitsUI window. This blank window doesn't always appear, never on the first run after restarting the python kernel, and sometimes only after running the script a few times in succession and closing the windows that appear each time.
Running the much-reduced code below produces the same behaviour, and removing the mlab.colorbar(s) line stops the problem. How can I get a colorbar without opening blank windows? There doesn't seem to be an obvious way to assign a colorbar to a specific figure as for the surface plot. I am running Python 3.5 on Windows 7 (but get the same issues on Ubuntu).
from traits.api import HasTraits, Instance, on_trait_change
from traitsui.api import View, Item
import numpy as np
from mayavi.core.api import Engine
from mayavi.core.ui.api import SceneEditor, MlabSceneModel
from mayavi import mlab
#Generate a test surface to display
def test_surf():
x, y = np.mgrid[-7.:7.05:0.1, -5.:5.05:0.05]
z = np.sin(x + y) + np.sin(2 * x - y) + np.cos(3 * x + 4 * y)
return x, y, z
class MyApp(HasTraits):
#Create a mayavi scene with a specified engine
engine = Instance(Engine, ())
scene = Instance(MlabSceneModel)
def _scene_default(self):
self.engine.start()
return MlabSceneModel(engine=self.engine)
#Plot the surface when the scene is activated
#on_trait_change('scene.activated')
def populate_scene(self):
s = mlab.surf(*test_surf(), figure=self.scene.mayavi_scene)
mlab.colorbar(s)
view = View(Item('scene', editor=SceneEditor()))
if __name__ == '__main__':
MyApp().configure_traits()
You may add something that closes/quits the windows you invoke.
For example you could close the figure self.scene.mayavi_scene using function mayavi.mlab.close.
Related
I am having problems trying to make matplotlib plot a function without blocking execution.
I have tried using show(block=False) as some people suggest, but all I get is a frozen window. If I simply call show(), the result is plotted properly but execution is blocked until the window is closed. From other threads I've read, I suspect that whether show(block=False) works or not depends on the backend. Is this correct? My backend is Qt4Agg. Could you have a look at my code and tell me if you see something wrong? Here is my code.
from math import *
from matplotlib import pyplot as plt
print(plt.get_backend())
def main():
x = range(-50, 51, 1)
for pow in range(1,5): # plot x^1, x^2, ..., x^4
y = [Xi**pow for Xi in x]
print(y)
plt.plot(x, y)
plt.draw()
#plt.show() #this plots correctly, but blocks execution.
plt.show(block=False) #this creates an empty frozen window.
_ = raw_input("Press [enter] to continue.")
if __name__ == '__main__':
main()
PS. I forgot to say that I would like to update the existing window every time I plot something, instead of creating a new one.
I spent a long time looking for solutions, and found this answer.
It looks like, in order to get what you (and I) want, you need the combination of plt.ion(), plt.show() (not with block=False) and, most importantly, plt.pause(.001) (or whatever time you want). The pause is needed because the GUI events happen while the main code is sleeping, including drawing. It's possible that this is implemented by picking up time from a sleeping thread, so maybe IDEs mess with that—I don't know.
Here's an implementation that works for me on python 3.5:
import numpy as np
from matplotlib import pyplot as plt
def main():
plt.axis([-50,50,0,10000])
plt.ion()
plt.show()
x = np.arange(-50, 51)
for pow in range(1,5): # plot x^1, x^2, ..., x^4
y = [Xi**pow for Xi in x]
plt.plot(x, y)
plt.draw()
plt.pause(0.001)
input("Press [enter] to continue.")
if __name__ == '__main__':
main()
A simple trick that works for me is the following:
Use the block = False argument inside show: plt.show(block = False)
Use another plt.show() at the end of the .py script.
Example:
import matplotlib.pyplot as plt
plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")
plt.show(block=False)
#more code here (e.g. do calculations and use print to see them on the screen
plt.show()
Note: plt.show() is the last line of my script.
You can avoid blocking execution by writing the plot to an array, then displaying the array in a different thread. Here is an example of generating and displaying plots simultaneously using pf.screen from pyformulas 0.2.8:
import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time
fig = plt.figure()
canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')
start = time.time()
while True:
now = time.time() - start
x = np.linspace(now-2, now, 100)
y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
plt.xlim(now-2,now+1)
plt.ylim(-3,3)
plt.plot(x, y, c='black')
# If we haven't already shown or saved the plot, then we need to draw the figure first...
fig.canvas.draw()
image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))
screen.update(image)
#screen.close()
Result:
Disclaimer: I'm the maintainer for pyformulas.
Reference: Matplotlib: save plot to numpy array
Live Plotting
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1]) # disable autoscaling
for point in x:
plt.plot(point, np.sin(2 * point), '.', color='b')
plt.draw()
plt.pause(0.01)
# plt.clf() # clear the current figure
if the amount of data is too much you can lower the update rate with a simple counter
cnt += 1
if (cnt == 10): # update plot each 10 points
plt.draw()
plt.pause(0.01)
cnt = 0
Holding Plot after Program Exit
This was my actual problem that couldn't find satisfactory answer for, I wanted plotting that didn't close after the script was finished (like MATLAB),
If you think about it, after the script is finished, the program is terminated and there is no logical way to hold the plot this way, so there are two options
block the script from exiting (that's plt.show() and not what I want)
run the plot on a separate thread (too complicated)
this wasn't satisfactory for me so I found another solution outside of the box
SaveToFile and View in external viewer
For this the saving and viewing should be both fast and the viewer shouldn't lock the file and should update the content automatically
Selecting Format for Saving
vector based formats are both small and fast
SVG is good but coudn't find good viewer for it except the web browser which by default needs manual refresh
PDF can support vector formats and there are lightweight viewers which support live updating
Fast Lightweight Viewer with Live Update
For PDF there are several good options
On Windows I use SumatraPDF which is free, fast and light (only uses 1.8MB RAM for my case)
On Linux there are several options such as Evince (GNOME) and Ocular (KDE)
Sample Code & Results
Sample code for outputing plot to a file
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")
after first run, open the output file in one of the viewers mentioned above and enjoy.
Here is a screenshot of VSCode alongside SumatraPDF, also the process is fast enough to get semi-live update rate (I can get near 10Hz on my setup just use time.sleep() between intervals)
A lot of these answers are super inflated and from what I can find, the answer isn't all that difficult to understand.
You can use plt.ion() if you want, but I found using plt.draw() just as effective
For my specific project I'm plotting images, but you can use plot() or scatter() or whatever instead of figimage(), it doesn't matter.
plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)
Or
fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)
If you're using an actual figure.
I used #krs013, and #Default Picture's answers to figure this out
Hopefully this saves someone from having launch every single figure on a separate thread, or from having to read these novels just to figure this out
I figured out that the plt.pause(0.001) command is the only thing needed and nothing else.
plt.show() and plt.draw() are unnecessary and / or blocking in one way or the other. So here is a code that draws and updates a figure and keeps going. Essentially plt.pause(0.001) seems to be the closest equivalent to matlab's drawnow.
Unfortunately those plots will not be interactive (they freeze), except you insert an input() command, but then the code will stop.
The documentation of the plt.pause(interval) command states:
If there is an active figure, it will be updated and displayed before the pause......
This can be used for crude animation.
and this is pretty much exactly what we want. Try this code:
import numpy as np
from matplotlib import pyplot as plt
x = np.arange(0, 51) # x coordinates
for z in range(10, 50):
y = np.power(x, z/10) # y coordinates of plot for animation
plt.cla() # delete previous plot
plt.axis([-50, 50, 0, 10000]) # set axis limits, to avoid rescaling
plt.plot(x, y) # generate new plot
plt.pause(0.1) # pause 0.1 sec, to force a plot redraw
Iggy's answer was the easiest for me to follow, but I got the following error when doing a subsequent subplot command that was not there when I was just doing show:
MatplotlibDeprecationWarning: Adding an axes using the same arguments
as a previous axes currently reuses the earlier instance. In a future
version, a new instance will always be created and returned.
Meanwhile, this warning can be suppressed, and the future behavior
ensured, by passing a unique label to each axes instance.
In order to avoid this error, it helps to close (or clear) the plot after the user hits enter.
Here's the code that worked for me:
def plt_show():
'''Text-blocking version of plt.show()
Use this instead of plt.show()'''
plt.draw()
plt.pause(0.001)
input("Press enter to continue...")
plt.close()
The Python package drawnow allows to update a plot in real time in a non blocking way.
It also works with a webcam and OpenCV for example to plot measures for each frame.
See the original post.
Substitute the backend of matplotlib can solve my problem.
Write the bellow command before import matplotlib.pyplot as plt.
Substitute backend command should run first.
import matplotlib
matplotlib.use('TkAgg')
My answer come from Pycharm does not show plot
Before plotting using matplotlib, you must specify your display's DPI if you have a high DPI display, since otherwise the image is too small. I have a 4K display, so I definitely need to do this. (I think that matplotlib should automatically do this for you, but that is another topic...)
As a first attempt to specify the DPI, consider the code below. It manually specifies the display's DPI and then creates and plots a test DataFrame:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sys
# method #1: manually specify my display's DPI:
dpi = 163 # this value is valid for my Dell U2718Q 4K (3840 x 2160) display
plt.rcParams["figure.dpi"] = dpi
print("plt.matplotlib.rcParams[\"figure.dpi\"] = " + str(plt.matplotlib.rcParams["figure.dpi"]))
# define a test DataFrame (here, chose to calculate sin and cos over their range of 2 pi):
n = 100
x = (2 * np.pi / n) * np.arange(n)
df = pd.DataFrame( {
"sin(x)" : np.sin(x),
"cos(x)": np.cos(x),
}
)
# plot the DataFrame:
df.plot(figsize = (12, 8), title = "sin and cos", grid = True, color = ["red", "green"])
When I put the code above into a file and run it all at once in PyCharm, everything behaves exactly as expected: the script completes without error, the plot is generated at the correct size, and the plot remains open in a window after the script ends.
So far, so good.
But the code above is brittle: run it on a computer with a different display DPI, and the image will not be sized correctly.
Doing a web search, I found this link which has code claims to automatically determine your display's DPI. My (slight) adaptation of the code is this
# method #2: call code to determine my display's DPI (only works if the backend is Qt)
if plt.get_backend() == "Qt5Agg":
from matplotlib.backends.qt_compat import QtWidgets
qApp = QtWidgets.QApplication(sys.argv)
plt.matplotlib.rcParams["figure.dpi"] = qApp.desktop().physicalDpiX()
If I modify my file to use the code above ("method #2") instead of the manual DPI setting ("method #1"), I find that the script completes without error, but the plot only comes up for a brief instant before being automatically closed!
By successively commenting out lines in the "method #2" code, starting with the last and working backwards, I have determined that the culprit is the call to QtWidgets.QApplication(sys.argv).
In particular, if I reduce the "method #2" code to just this
if plt.get_backend() == "Qt5Agg":
from matplotlib.backends.qt_compat import QtWidgets
QtWidgets.QApplication(sys.argv)
I get this plot auto close behavior.
Another defect, is that the original "method #2" code calculates the DPI of my monitor, a Dell U2718Q, to be 160, when it really is 163: in this link go to p. 3 / 4 and look at the Pixels per inch (PPI) spec.
Does anyone know of a solution to this?
Better code to determine the DPI?
A modification of the "method #2" code which will not cause plots to auto close?
Is this a bug that needs to be reported to matplotlib or Qt?
I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add points to it while seeing what's behind it.
https://stackoverflow.com/a/45505906/13650485
The answer I've listed above is EXACTLY what I want to do, except my interactive system doesn't work with TK. I'd like to use Qt5Agg. When I run the code above, the system won't accept it -- it says QT5 is currently running. If I run it without QT already loaded, it creates a blank transparent window (yay!) but if I move it or click on the icon it turns opaque black without any plot. If I change tk to Qt5 it complains on lift. If I remove the "win" code, it has no transparency(obviously). I've tried adding everything I can think of to make the canvas transparent and I can change the color but not make it transparent.
import matplotlib
# make sure Tk backend is used
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4,2))
ax.plot([2,3,5,1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.lift()
win.attributes("-topmost", True)
win.attributes("-transparentcolor", "white")
plt.show()
When I made the changes suggested by: eyllanesc
I found within a vanilla Spyder 4.1.3 | Python 3.7.7 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10
In order to import QtCore I had to first
conda install pyqt
not enough, so then conda install pyqt5
and also conda update --all
When I did that, the code ran without errors. This is a better first result!, but I still only get the frozen mpl.fig window. This time, however, it is white. . . The console returns, but the mpl window hangs. Run again, a new frozen window. Restart and run again: same result.
I hope that this is a simple error; please teach this newby.
#eyllanesc
Revised: Python screen tracing application – needs a mostly transparent plot window.
I need the whole plot window to be transparent so that a chrome window, for example, on my desktop could be seen through the plot, so that I can add plot (x, y) points to it while seeing what's behind it.
Adding the command win.setWindowFlags(QtCore.Qt.FramelessWindowHint) did indeed make the window transparent, but it made the tool bar transparent, got rid of the title bar, and removed the ability to move or resize the window. It also made it so that the graph area was not sensitive to the mouse unless I was over the line. I added the facecolor attribute to the subplots command so I could see what was going on. As long as I put a non-zero value for either the fig-alpha or the ax-alpha, the graph is sensitive to the mouse over the whole area.
I need to be able to move and resize the window and would like to have the toolbar be opaque or at least sensitive to the mouse over the whole toolbar. Can you help with this? Thanks for past help!
## Python Code Fragment by Helen for Windows 10
## to test sequence creating plot with transparent
## background (to be used to trace and record xy pairs)
from PyQt5 import QtCore
import matplotlib
matplotlib.use("Qt5Agg") #define backend, must be before pyplot is imported
import matplotlib.pyplot as plt
# create a figure and a subplot
fig,ax = plt.subplots(figsize=(4, 2),facecolor=(1.,1.,0.,0.1)) #facecolor of figure
fig.patch.set_alpha(0.1)
ax.patch.set_alpha(0.1)
# plot some fixed points
ax.plot([2, 3, 5, 1])
fig.tight_layout()
#make window transparent to the desktop
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
win.setWindowFlags(QtCore.Qt.FramelessWindowHint)
win.setWindowTitle("My App")
plt.show()
You have to use the Qt flags, tested on Linux:
from PyQt5 import QtCore
import matplotlib
# make sure Tk backend is used
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
# create a figure and some subplots
fig, ax = plt.subplots(figsize=(4, 2))
fig.patch.set_alpha(0.0)
ax.patch.set_alpha(0.0)
ax.plot([2, 3, 5, 1])
fig.tight_layout()
win = plt.gcf().canvas.manager.window
win.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
win.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
win.setStyleSheet("background:transparent")
plt.show()
I have a script as follows that executes on my windows machine
import matplotlib.pyplot as plt
import numpy as np
import tkinter
class main(tkinter.Frame): #main window
def __init__(self, root): # initialise
tkinter.Frame.__init__(self)
self.root = root
tkinter.Button(self, text='New spots', command=self.newSpots).grid()
def newSpots(self):
x = np.random.rand(10)
y = np.random.rand(10)
plt.scatter(x,y)
plt.show()
if __name__=='__main__':
root = tkinter.Tk()
app = main(root).grid()
root.mainloop()
When running on windows, it opens a window with a simple button, and clicking this button opens a matplotlib viewer with 10 dots plotted in random positions. Each subsequent press of the button adds a further ten dots.
Executing this code on a mac produces the same initial window, and the first press of the button generates the plot and opens the viewer as expected. However, it then becomes impossible to interact with the original window (only the controls on the viewer work) until the viewer window is closed. How do I make the behaviour on the mac mirror that on the windows machine?
I found a solution to this issue -- it seems matplotlib defaults to the TkAgg backend on Windows (I'm unsure whether this is a general Windows thing, or specific to whatever particular install is on the machine).
Adding the following lines to the top of the script forces the TkAgg backend and leads to the same behaviour on both machines.
import matplotlib
matplotlib.use("TkAgg")
I am new to Python and wxPython I am trying to simulate particles in a box with random velocities in random directions.
I created a simple GUI in wxFormBuilder where I have a panel to show a plot of the paricles. The particles are set to a position and plotted onto the panel, then I start the simulation and update the x and y positons of the particles. When redrawing the positions The axes appear 'thicker' as before, it looks like if there are several axes ontop of eachother.
I cant find anything about this problem, I hope somebody could help me with this?
This is the code that creates the Plot:
import wx
import particles
import random
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
matplotlib.rcParams.update({'font.size': 8})
class MyFrameSub( particles.GUI_MainFrame ):
def __init__( self, parent ):
particles.GUI_MainFrame.__init__( self, parent )
def InitData(self):
self.npart = int(self.m_npart.GetValue())
self.nsteps = int(self.m_steps.GetValue())
self.ndt = float(self.m_dt.GetValue())
self.x= [random.random() for I in range(self.npart)]
self.y= [2*random.random()-1 for I in range(self.npart)]
self.vx= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
self.vy= [self.ndt*(2*random.random()-1) for I in range(self.npart)]
return
def CreatePlot(self):
panelsize = self.m_PlotPanel.GetClientSize()
self.figure = Figure(figsize=(panelsize[0]/100.0,panelsize[1]/100.0), dpi=100, frameon=False)
self.canvas = FigCanvas(self.m_PlotPanel, wx.ID_ANY, self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.axis((-1,1,-1,1))
self.partplot, = self.axes.plot(self.x, self.y, 'ro')
self.canvas.draw()
return
def UpdateData(self):
for i in range(self.nsteps):
for j in range(self.npart):
self.x[j]=self.x[j]+self.vx[j]
self.y[j]=self.y[j]+self.vy[j]
if abs(self.x[j])>1:
self.vx[j]=-self.vx[j]
if abs(self.y[j])>1:
self.vy[j]=-self.vy[j]
self.partplot.set_xdata(self.x)
self.partplot.set_ydata(self.y)
self.canvas.draw()
return
followed by the button definitions, it looks like this:
Before running the simulation: www.merlinvs.de/before.jpg
and after running the simulation: www.merlinvs.de/after.jpg
As you see the axes got ugly and I have no idea why.
Another question I was thinking about is the following:
When I run a loop that takes a while the UI is unresponsive, is it possible to have the UI active to cancel a loop if desired?
As for the unresponsive UI, I used greenlets for my Matplotlib stuff while updating it
from gevent.greenlet import Greenlet
from gevent import sleep
import matplotlib.plot as plt
# Plot stuff
def event_handler():
# Can update plot
sleep(1) # Simulate handling or number crunching (numpy WILL block)
g = Greenlet(event_handler)
g.start()
plt.plot(0,0) # Works immediately and updates
Some things of note is that for serious applications you need to add some protection against race coditions with the plot. Numpy and external science libraries typically cause the entire application to become unresponsive (in my experience) because they are blocking system calls outside of the greenlet context switcher's reach. For something simple though the above pattern works well.