Python kernel crash in Jupyter notebook when calling fig.add_axes() - python

So I have Anaconda on Windows running an environment with a bunch of stuff and:
Python 3.6.6
matplotlib 2.2.3
And when I run following code, my Python kernel crashes:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,10))
fig = plt.figure(figsize=(10,10))
fig.add_axes([10,200,50,50])
Does anyone know why that happens or perhaps can try to reproduce so that I can know it's not just my pc dying on me?

Short answer: Use reasonable coordinates to place your axes, i.e. numbers between 0 and 1, for example
fig.add_axes([0.1 ,0.2, 0.5, 0.5])
I suppose the numbers are just too big. The figure ranges between 0 and 1. So while there are certainly cases where you may want to add an axes outside that range e.g. [-0.1,0.7,0.3,0.5], creating an axes at a coordinate 200 times the figure height is not very useful.
Well, "not useful" does not mean it should crash. So what's probably happening is that you are trying to show this figure on screen in a version which expands the figure size to the figure content. This might happen by default in a jupyter notebook, which shows the figure that is saved via fig.savefig(..., bbox_inches="tight"). So assuming a figure size of 10 by 10 inch, [10,200,50,50] leads to a figure tried to be saved of dimension 60*10=600 inch in width and 250*10=2500 inch in height. With a dpi of 72, this leads to a png image of (43200 x 180000) pixels. This seems to be to much to be handled by the renderer.
When running the code as script (adding fig.savefig(..., bbox_inches="tight")) you actually do get an error using the usual Agg-based renderer,
RuntimeError: Unknown exception in RendererAgg
which means that the renderer is not capable of producing the figure. I'm not sure why in Ipython/Juypter no such error is raised.
A more useful errormessage is shown with the "cairo" renderer,
import matplotlib
matplotlib.use("Qt4Cairo")
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,10))
fig = plt.figure(figsize=(10,10))
fig.add_axes([10,200,50,50])
fig.savefig("hugeaxesposition.png", bbox_inches="tight")
shows
cairocffi.CairoError: cairo returned CAIRO_STATUS_INVALID_SIZE:
invalid value (typically too big) for the size of the input (surface, pattern, etc.)
I would agree that seeing such error would be more useful from a user's perspective than a crash.
While the reason for no error being shown in IPython/Jupyter might be interesting to investigate further, it sure has no high priority, since matplotlib is not designed to produce huge figures anyways.

Looking at the documentation of Figure.add_axes, the first argument is:
rect : sequence of float
The dimensions [left, bottom, width, height]
of the new axes. All quantities are in fractions of figure width and
height.
The values are fractions of the figure height and width and so should be between 0 and 1

Related

Why does matplotlib / Qt auto close plots?

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?

Flickering and jumping output in jupyter using interact widget

I'm trying to make use of Interact in a Jupyter Notebook. A bit down on that page, it's stated that
On occasion, you may notice interact output flickering and jumping, causing the notebook scroll position to change as the output is updated. The interactive control has a layout, so we can set its height to an appropriate value (currently chosen manually) so that it will not change size as it is updated.
And I'm experiencing exactly that problem when I'm trying to replicate the example.
The following snippet...
%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
def f(m, b):
plt.figure(2)
x = np.linspace(-10, 10, num=1000)
plt.plot(x, m * x + b)
plt.ylim(-5, 5)
plt.show()
interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot
... should produce this output:
But I'm only getting this:
To be precise, the sliders do pop up for half a second, but seem to be overwritten by the graph. I'm guessing that this is exactly the problem they are adressing. So I thought I could remedy the problem using different values than 350 in output.layout.height = '350px', but with absolutley no success so far. I've tried 100, 200, 250, 300, 750 and 1400.
So could there be something else causing the problems?
Thank you for any suggestions!
I just checked that the solution proposed here:
Jupyter Notebook: interactive plot with widgets
works with recent classic Jupyter.
To avoid flickering you could switch to using
https://github.com/AaronWatters/jp_doodle -- look at the examples
under "Features" named interactive*. The jp_doodle dual_canvas
has a context manager which suppresses intermediate updates.

Changing the order of axes in Mayavi

I am generating STL files for 3D printing and then using mlab/mayavi to display them. I would like the Z axis to remain vertical as I rotate the image. According to the mayavi documentation, this can be achieved using the following incantation:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
Unfortunately, as you can see from this screenshot of my app, it is not the Z axis but the Y axis that gets maintained vertical.
This is an issue because 3D printers always think of Z as the vertical axis, so I really need Z and not Y to be oriented vertically. Is there a way in which this can be achieved?
I have experienced the same problem, vertically aligned along the y-axis.
To get the scene to align vertically along the z-axis (which you want), you can simply add a scene.mlab.view() call before setting the interactor.
The scene.mlab.view() call aligns the camera correctly (with z up), before the interactor is set. I have found this solution by simply testing a bunch of stuff, I could not find this "hack" in the documentation.
New code:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.mlab.view(0, 90)
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
You can rename them using the Mayavi button to access the pipeline, then selecting the appropiate axes object and changing the labels.
With code, you can add the xlabel, ylabel and zlabel keyword arguments to specify them. This is specified in the axes API

Animating a Quadmesh from pcolormesh with matplotlib

As a result of a full day of trial and error, I'm posting my findings as a help to anyone else who may come across this problem.
For the last couple days, I've been trying to simulate a real-time plot of some radar data from a netCDF file to work with a GUI I'm building for a school project. The first thing I tried was a simple redrawing of the data using the 'interactive mode' of matplotlib, as follows:
import matplotlib.pylab as plt
fig = plt.figure()
plt.ion() #Interactive mode on
for i in range(2,155): #Set to the number of rows in your quadmesh, start at 2 for overlap
plt.hold(True)
print i
#Please note: To use this example you must compute X, Y, and C previously.
#Here I take a slice of the data I'm plotting - if this were a real-time
#plot, you would insert the new data to be plotted here.
temp = plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
plt.draw()
plt.pause(.001) #You must use plt.pause or the figure will freeze
plt.hold(False)
plt.ioff() #Interactive mode off
While this technically works, it also disables the zoom functions, as well as pan, and well, everything!
For a radar display plot, this was unacceptable. See my solution to this below.
So I started looking into the matplotlib animation API, hoping to find a solution. The animation did turn out to be exactly what I was looking for, although its use with a QuadMesh object in slices was not exactly documented. This is what I eventually came up with:
import matplotlib.pylab as plt
from matplotlib import animation
fig = plt.figure()
plt.hold(True)
#We need to prime the pump, so to speak and create a quadmesh for plt to work with
plt.pcolormesh(X[0:1], Y[0:1], C[0:1])
anim = animation.FuncAnimation(fig, animate, frames = range(2,155), blit = False)
plt.show()
plt.hold(False)
def animate( self, i):
plt.title('Ray: %.2f'%i)
#This is where new data is inserted into the plot.
plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
Note that blit must be False! Otherwise it will yell at you about a QuadMesh object not being 'iterable'.
I don't have access to the radar yet, so I haven't been able to test this against live data streams, but for a static file, it has worked great thus far. While the data is being plotted, I can zoom and pan with the animation.
Good luck with your own animation/plotting ambitions!

Passing pyplot points as arguments

So I initialised a pyplot figure
import ... ## import all relevent modules
f = plt.figure(figsize=(8,3),dpi(100)
a = plt.subplot(111)
a.set_xlim(left=0,right=25,auto=False)
a.set_ylim(bottom=0,top=250,auto=False)
a.plot([5,10,15],[80,150,210])
plt.show()
This works fine... What I want to be able to do is to write a function that can update the scatter plot dynamically... Something like:
def plot_point(x_coord,y_coord):
a.plot([x_coord],[y_coord])
a.draw() ## I thought this would work... :(
No error, but the point doesn't get plotted. How can I get around this? The reason I've done it using figures is so I can embed it in Tkinter.
Thanks for your help!
plot is perfectly fine to use for plotting individual points (it is even recommend over scatter, if you don't wanna add additional information through color or size of the dots). What is missing in the initial example is setting the right linestyle; obviously, a line consisting of a single point doesn't show up. Changing the line style to '+' or something similar fixes the problem:
def plot_point(x_coord,y_coord):
a.plot([x_coord],[y_coord], '+')

Categories