NagivationToolbar fails when updating in Tkinter canvas - user-interface

I am trying to update a two-panel plot in Tkinter canvas using matplotlib. Here is the minimum code that shows my current understanding. The main problem is while the navigation toolbar works for the initial plots (y = sin(x), y = cos(x)), however it fails when I press the update button to update it. For example if I zoom in a curve, I cannot use home button to return to its original state. I have been trying different ways, but to no avail. I would appreciate anyone's suggestions.
One minor issue I notice is that if I want to kill the plot, I should go to menubar and select python/quit Python, otherwise if I just click the X at the top left of the plot window, the terminal freezes (I have to kill the terminal).
I am using Python 2.7.14 and matplotlob 2.1.0.
from Tkinter import *
import Tkinter as tk
import ttk
from math import exp
import os # for loading files or exporting files
import tkFileDialog
##loading matplotlib modules
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
import numpy as np
top = tk.Tk()
top.title("Intermolecular PDFs")
top_frame = ttk.Frame(top, padding = (10, 10))
top_frame.pack()
fig = plt.figure(figsize=(10, 6), dpi=100) ##create a figure; modify the size here
x = np.linspace(0,1)
y = np.sin(x)
z = np.cos(x)
fig.add_subplot(211)
plt.title("Individual PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(x,y, "r-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)
fig.add_subplot(212)
plt.title("Difference PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(x,z,"g-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)
fig.tight_layout()
canvas = FigureCanvasTkAgg(fig, master = top_frame)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
#self.canvas.draw()
toolbar = NavigationToolbar2TkAgg(canvas, top_frame)
#self.toolbar.pack()
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def update():
fig.clf()
new_x = np.linspace(1,100)
new_y = new_x**2
new_z = new_x**3
fig.add_subplot(211)
plt.title("Individual PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(new_x,new_y, "r-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)
fig.add_subplot(212)
plt.title("Difference PDFs")
plt.xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
plt.ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
plt.plot(new_x,new_z,"g-", lw=2)
plt.xticks(fontsize = 11)
plt.yticks(fontsize = 11)
fig.tight_layout()
canvas.show()
ttk.Button(top_frame, text = "update",command = update).pack()
top.mainloop()

The main problem is that the home button does not know which state it should refer to when being pressed. The original state it would refer to does not even exist any more, because the figure had been cleared in the meantime. The solution to this is to call
toolbar.update()
which will, amongst other things, create a new home state for the button to revert to when being pressed.
There are some other minor issues with the code:
Instead of clearing the figure, you may just update the data of the lines drawn in it. This would eliminate a lot of redundant code.
I would strongly advise not to use pyplot at all when embedding a figure in Tk. Instead, use the object oriented approach, creating objects like figure and axes and then call their respective methods. (I'm not sure if this is the reason for the freezing though, because even the initial code did not freeze for me when running it.)
There were some unnecessary commands floating in the code.
The following is a clean version with all the above being taken care of:
import Tkinter as tk
import ttk
##loading matplotlib modules
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import numpy as np
top = tk.Tk()
top.title("Intermolecular PDFs")
top_frame = ttk.Frame(top, padding = (10, 10))
top_frame.pack()
matplotlib.rcParams["xtick.labelsize"] = 11
matplotlib.rcParams["ytick.labelsize"] = 11
fig = Figure(figsize=(10, 6), dpi=100) ##create a figure; modify the size here
x = np.linspace(0,1)
y = np.sin(x)
z = np.cos(x)
ax = fig.add_subplot(211)
ax.set_title("Individual PDFs")
ax.set_xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
ax.set_ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
line, = ax.plot(x,y, "r-", lw=2)
ax2 = fig.add_subplot(212)
ax2.set_title("Difference PDFs")
ax2.set_xlabel(ur"r (\u00c5)", labelpad = 3, fontsize = 15)
ax2.set_ylabel(ur"PDF, G (\u00c5$^{-2})$", labelpad = 10, fontsize = 15)
line2, = ax2.plot(x,z,"g-", lw=2)
canvas = FigureCanvasTkAgg(fig, master = top_frame)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
fig.tight_layout()
toolbar = NavigationToolbar2TkAgg(canvas, top_frame)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def update():
new_x = np.linspace(1,100)
new_y = new_x**2
new_z = new_x**3
line.set_data(new_x,new_y)
line2.set_data(new_x,new_z)
ax.relim()
ax.autoscale()
ax2.relim()
ax2.autoscale()
fig.tight_layout()
canvas.draw_idle()
toolbar.update()
ttk.Button(top_frame, text = "update",command = update).pack()
top.mainloop()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.

Related

Trying to get a plotly graph object including both scatter and image to change xtick labels

I am creating a plotly figure, overlapping rectangles on an image and I want to change the xticks.
Example Code:
a = 255*np.random.random((28,28))
pil_img = Image.fromarray(a).convert('RGB')
fig2 = go.Figure(data = [go.Scatter(x=[0,10,10,0], y=[0,0,10,10], fill="toself"),go.Image(z=pil_img)])
fig2.show()
Instead of the ticks being the number of pixels (0-28) I want them to be let's say from 0.2 to 3 in increments of 0.1 [0.2,0.3,...3] so that the length is still 28 but the ticks aren't [0,1,2] but rather [0.2,0.3,..3]
Thanks!
Following this documentation page, one way to achieve it is:
import numpy as np
from PIL import Image
import plotly.graph_objects as go
N = 28
a = 255 * np.random.random((N, N))
pil_img = Image.fromarray(a).convert('RGB')
fig = go.Figure(data = [go.Scatter(x=[0,10,10,0], y=[0,0,10,10], fill="toself"),go.Image(z=pil_img)])
fig.update_layout(
xaxis = dict(
tickmode = 'array',
tickvals = np.arange(N),
ticktext = ["{:.1f}".format(t) for t in np.linspace(0.3, 3, N)]
),
yaxis = dict(
tickmode = 'array',
tickvals = np.arange(N),
ticktext = ["{:.1f}".format(t) for t in np.linspace(0.3, 3, N)]
)
)
fig

Animated Polar Plot

I am trying to make an animated plot of planetary motion using python. I can get the correct path to show up when not animated but once I try to animate it, it just shows up blank even though r and i are being output as the correct values when I try to print each value in the console.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig = plt.figure(figsize=(6,6))
ax = plt.subplot(111, polar=True)
ax.set_ylim(0,1)
line, = ax.plot([],[])
semi_major_axis = 1
eccentricity = 0.1
theta = np.linspace(0,2*np.pi, num = 50)
point, = ax.plot(0,1, marker="o")
def frame(i):
r=(semi_major_axis*(1-eccentricity**2)/(1-eccentricity*np.cos(i)))
line.set_xdata(i)
line.set_ydata(r)
return line,
ax.set_rmax(semi_major_axis+1)
ax.set_rticks(np.linspace(0,semi_major_axis+1, num = 5))
ax.set_rlabel_position(-22.5)
animation = FuncAnimation(fig, func=frame, frames=theta, interval=10)
plt.show()
I think the problem is you are plotting a point and not a line. The following code worked for me:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig = plt.figure(figsize=(6, 6))
ax = plt.subplot(111, polar=True)
ax.set_ylim(0, 1)
semi_major_axis = 1
eccentricity = 0.1
theta = np.linspace(0, 2 * np.pi, num=50)
def frame(i):
r = (semi_major_axis * (1 - eccentricity ** 2) / (1 - eccentricity * np.cos(i)))
ax.plot(i,r,'b', marker='o')
ax.set_rmax(semi_major_axis + 1)
ax.set_rticks(np.linspace(0, semi_major_axis + 1, num=5))
ax.set_rlabel_position(-22.5)
animation = FuncAnimation(fig, func=frame, frames=theta, interval=10)
plt.show()

Python spyder + tensorflow cross validation freezes on Windows 10

On Windows 10, I have installed Anaconda and launched Spyder. I have also successfully installed Theano, Tensorflow and Keras, since when I execute
import keras
the console outputs
Using Tensorflow Backend
When I compile and fit the neural network it runs fine. But when I try to run k-fold cross validation, combining the scikit-learn via a keras wrapper and using the parameter n_jobs = -1 (and generally n_jobs with whatever value, thus having multiprocessing), the console just freezes forever until restarting kernel manually or terminating Spyder.
Another problem, when I try to run some parameter tuning using GridSearchCV, for i.e. 100 epochs, it doesn't freeze but it outputs epoch 1/1 instead of 1/100 and generally it gives bad results, not logical (i.e. it runs only for a couple of minutes, while normally it would take hours!).
My code is:
# Part 1 - Data Preprocessing
# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# Importing the dataset
dataset = pd.read_csv('Churn_Modelling.csv')
X = dataset.iloc[:, 3:13].values
y = dataset.iloc[:, 13].values
# Encoding categorical data
# Encoding the Independent Variable
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder_X_1 = LabelEncoder()
X[:, 1] = labelencoder_X_1.fit_transform(X[:, 1])
labelencoder_X_2 = LabelEncoder()
X[:, 2] = labelencoder_X_2.fit_transform(X[:, 2])
onehotencoder = OneHotEncoder(categorical_features = [1])
X = onehotencoder.fit_transform(X).toarray()
# Avoiding the dummy variable trap
X = X[:, 1:]
# Splitting the dataset into the Training set and Test set
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
# Feature Scaling
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
# Part 2 - Now let's make the ANN!
# Importing the Keras libraries and packages
import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
# Initialising the ANN
classifier = Sequential()
# Adding the input layer and the first hidden layer with dropout
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu', input_dim = 11))
classifier.add(Dropout(rate = 0.1)) # p should vary from 0.1 to 0.4, NOT HIGHER, because then we will have under-fitting.
# Adding the second hidden layer with dropout
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu'))
classifier.add(Dropout(rate = 0.1))
# Adding the output layer
classifier.add(Dense(units = 1, kernel_initializer = 'uniform', activation = 'sigmoid'))
# Compiling the ANN
classifier.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])
# Fitting the ANN to the Training set
classifier.fit(X_train, y_train, batch_size = 10, epochs = 100)
# Part 3 - Making predictions and evaluating the model
# Predicting the Test set results
y_pred = classifier.predict(X_test)
y_pred = (y_pred > 0.5)
# Making the Confusion Matrix
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
new_prediction = classifier.predict(sc.transform(np.array([[0, 0, 600, 1, 40, 3, 60000, 2, 1, 1, 50000]])))
new_prediction = (new_prediction > 0.5)
#Part 4 = Evaluating, Improving and Tuning the ANN
# Evaluating the ANN
from keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import cross_val_score
from keras.models import Sequential
from keras.layers import Dense
def build_classifier():
classifier = Sequential()
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu', input_dim = 11))
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu'))
classifier.add(Dense(units = 1, kernel_initializer = 'uniform', activation = 'sigmoid'))
classifier.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])
return classifier
classifier = KerasClassifier(build_fn = build_classifier, batch_size = 10, nb_epoch = 100)
accuracies = cross_val_score(estimator = classifier, X = X_train, y = y_train, cv = 10, n_jobs = -1)
mean = accuracies.mean()
variance = accuracies.std()
# Improving the ANN
# Tuning the ANN
from keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import GridSearchCV
from keras.models import Sequential
from keras.layers import Dense
def build_classifier(optimizer):
classifier = Sequential()
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu', input_dim = 11))
classifier.add(Dense(units = 6, kernel_initializer = 'uniform', activation = 'relu'))
classifier.add(Dense(units = 1, kernel_initializer = 'uniform', activation = 'sigmoid'))
classifier.compile(optimizer = optimizer, loss = 'binary_crossentropy', metrics = ['accuracy'])
return classifier
classifier = KerasClassifier(build_fn = build_classifier)
parameters = {"batch_size": [25, 32],
"nb_epoch": [100, 500],
"optimizer": ["adam", "rmsprop"]}
grid_search = GridSearchCV(estimator = classifier,
param_grid = parameters,
scoring = "accuracy",
cv = 10)
grid_search = grid_search.fit(X_train, y_train)
best_parameters = grid_search.best_params_
best_accuracy = grid_search.best_score_
Also, for n_jobs = 1, it runs but says epoch 1/1 and runs 10 times, which is the k-fold value. That means that it recognizes nb_epoch = 1 and not 100 for some reason.
Finally, I tried enclosing the cross_val_score() into a class:
class run():
def __init__(self):
cross_val_score(estimator = classifier, X = X_train, y = y_train, cv = 10, n_jobs = -1)
if __name__ == '__main__':
run()
or have it only with the if condition:
if __name__ == '__main__':
cross_val_score(estimator = classifier, X = X_train, y = y_train, cv = 10, n_jobs = -1)
but it doesn't work either, it freezes again.
Can anyone help me out solving these issues? What is going on, what can I do to solve these so everything runs properly?
Thank you in advance.
it seems Windows has an issue with "n_jobs", remove it in your "accuracies=" code and it will work, downside is it may take a while but at least it will work.

Updating matplotlib live graph in wxPython panel with scrolling x-axis

I am trying to animate a live graph in a wx.Panel. I would like to have the x-axis update like this example. Many of the examples I see are basic and don't take into consideration other controls and functions in the class. Others have so many extras that I get lost in the weeds. I can't get the animation command in the right place or update the x-axis. Here is the code:
import wx
import logging
import numpy as np
import matplotlib
import time
import matplotlib.animation as animation
matplotlib.use('WXAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
fTemp = ""
x = 0
class TempClass(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title="", size=(600,500))
panel = wx.Panel(self)
self.fig = Figure(figsize=(6,4), dpi=75, facecolor='lightskyblue', edgecolor='r')
self.canvas = FigureCanvas(self, -1, self.fig)
self.ax = self.fig.add_subplot(111)
self.ax2 = self.ax.twinx()
self.ax.set_ylim(60,90)
self.ax.set_xlim(0,24)
self.ax2.set_ylim(0,100)
# major ticks every 5, minor ticks every 1
xmajor_ticks = np.arange(0, 24, 5)
xminor_ticks = np.arange(0, 24, 1)
self.ax.set_xticks(xmajor_ticks)
self.ax.set_xticks(xminor_ticks, minor=True)
self.ax.grid()
self.ax.set_xlabel('Hour')
self.ax.set_ylabel('Temp')
self.ax2.set_ylabel('Humidity')
self.ax.set_title('Temperature')
# The graph does not show in the panel when this in uncommented
#self.ani = animation.FuncAnimation(self.fig, self.onPlotTemp, interval=1000)
self.fanSensorTimer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onPlotTemp, self.fanSensorTimer)
self.fanSensorBtn = wx.Button(self, -1, "Start Sensor")
self.Bind(wx.EVT_BUTTON, self.onStartTempPlot, self.fanSensorBtn)
font1 = wx.Font(18, wx.DEFAULT,wx.NORMAL,wx.BOLD)
self.displayTemp = wx.StaticText(self, -1, "Current Tempurature")
self.curTempTxt = wx.TextCtrl(self, -1, "0",size=(100,40), style=wx.TE_READONLY|wx.TE_CENTRE|wx.BORDER_NONE)
self.curTempTxt.SetFont(font1)
self.displayHum = wx.StaticText(self, -1, "Current Humidity")
self.curHumTxt = wx.TextCtrl(self, -1,"0", size=(100,40), style=wx.TE_READONLY|wx.TE_CENTRE|wx.BORDER_NONE)
self.curHumTxt.SetFont(font1)
self.displayBox = wx.GridBagSizer(hgap=5,vgap=5)
self.displayBox.Add(self.displayTemp, pos=(0,0), flag=wx.TOP|wx.LEFT, border=5)
self.displayBox.Add(self.displayHum, pos=(0,1), flag=wx.TOP, border=5)
self.displayBox.Add(self.curTempTxt, pos=(1,0), flag=wx.ALL, border=5)
self.displayBox.Add(self.curHumTxt, pos=(1,1), flag=wx.ALL, border=5)
#---------
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, wx.ALIGN_CENTER|wx.ALL, 1)
self.vbox.Add(self.fanSensorBtn)
self.vbox.Add(self.displayBox, wx.ALIGN_CENTER|wx.ALL, 1)
self.SetSizer(self.vbox)
self.vbox.Fit(self)
def start(self):
# get temp/humidity reading from node
pass
def readTemp(self, data1, data2):
"Populates Current Temp"
global fTemp
self.curTempTxt.Clear()
a = format(data1, '08b')
b = format(data2, '08b')
x = a+b
y = int(x, base=2)
cTemp = ((175.72 * y)/65536)-46.85
fTemp = cTemp *1.8+32
cel = format(cTemp,'.1f')
far = format(fTemp,'.1f')
self.curTempTxt.WriteText(far + (u'\u00b0')+"F")
def rh1(self, data1, data2):
"Populates Current RH"
global relhum
self.curHumTxt.Clear()
a = format(data1, '08b')
b = format(data2, '08b')
x = a+b
y = int(x, base=2)
rh = ((125 * y)/65536)-6
relhum = format(rh,'.1f')
self.curHumTxt.WriteText(relhum + " %")
def onStartTempPlot(self,event):
#set for a short time period for testing purposes
self.fanSensorTimer.Start(5000)
print "Timer Started"
def onPlotTemp(self,event):
global fTemp, x, relhum
x +=1
y = int(fTemp)
y2 = float(relhum)
self.ax.plot(x,y,'r.')
self.ax2.plot(x,y2,'k.')
self.fig.canvas.draw()
# send message to node for another reading of temp/humidity
if __name__ == "__main__":
app = wx.App(False)
frame = TempClass()
frame.Show()
frame.start()
logging.basicConfig(level=logging.DEBUG)
app.MainLoop()
I would like to see the x axis increment as the data is plotted beyond the 24 hour point on the graph; when data for point 25 appears, the first point is dropped and the x axis shows '25'. The animation is commented out because it causes the graph to disappear until a point is plotted.
Here is a runnable example of what I am trying to achieve with the x axis:
import numpy
from matplotlib.pylab import *
from mpl_toolkits.axes_grid1 import host_subplot
import matplotlib.animation as animation
# Sent for figure
font = {'size' : 9}
matplotlib.rc('font', **font)
# Setup figure and subplots
f0 = figure(num = 0, figsize = (6, 4))#, dpi = 100)
f0.suptitle("Oscillation decay", fontsize=12)
ax01 = subplot2grid((2, 2), (0, 0))
# Set titles of subplots
ax01.set_title('Position vs Time')
# set y-limits
ax01.set_ylim(0,2)
# sex x-limits
ax01.set_xlim(0,1)
# Turn on grids
ax01.grid(True)
# set label names
ax01.set_xlabel("x")
ax01.set_ylabel("py")
# Data Placeholders
yp1=zeros(0)
yv1=zeros(0)
yp2=zeros(0)
yv2=zeros(0)
t=zeros(0)
# set plots
p011, = ax01.plot(t,yp1,'b-', label="yp1")
p012, = ax01.plot(t,yp2,'g-', label="yp2")
# set lagends
ax01.legend([p011,p012], [p011.get_label(),p012.get_label()])
# Data Update
xmin = 0
xmax = 24
x = 0
def updateData(self):
global x
global yp1
global yv1
global yp2
global yv2
global t
tmpp1 = 1 + exp(-x) *sin(2 * pi * x)
tmpv1 = - exp(-x) * sin(2 * pi * x) + exp(-x) * cos(2 * pi * x) * 2 * pi
yp1=append(yp1,tmpp1)
yv1=append(yv1,tmpv1)
yp2=append(yp2,0.5*tmpp1)
yv2=append(yv2,0.5*tmpv1)
t=append(t,x)
x += 1
p011.set_data(t,yp1)
p012.set_data(t,yp2)
if x >= xmax-1:
p011.axes.set_xlim(x-xmax+1,x+1)
return p011
# interval: draw new frame every 'interval' ms
# frames: number of frames to draw
simulation = animation.FuncAnimation(f0, updateData, blit=False, frames=200, interval=20, repeat=False)
plt.show()
You are not incrementing the X axis limit or the ticks.
def onPlotTemp(self,event):
global fTemp, x, relhum
x +=1
y = int(fTemp)
y2 = float(relhum)
if x >= 24-1:
self.ax.set_xlim(x-24+1,x+1)
xmajor_ticks = np.arange(x-24+1,x+5, 5)
xminor_ticks = np.arange(x-24+1, x+1,1)
self.ax.set_xticks(xmajor_ticks)
self.ax.set_xticks(xminor_ticks, minor=True)
self.ax.plot(x,y,'r.')
self.ax2.plot(x,y2,'k.')
self.fig.canvas.draw()
I'm not sure if the above resets the ticks the way you want them but you get the idea. Obviously I have hard-coded 24 as your limit, you may want to create a variable to sort that out.

matplotlib: jpg image special effect/transformation as if its on a page of an open book

I wish to appear a figure (and certain text) as if they are printed on a page of an open book. Is it possible to transform an jpg image programmatically or in matplotlib to have such an effect?
You can use a background axis along with an open source book image to do something like this,
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax2 = fig.add_axes([0.2, 0.3, 0.25, 0.3])
#Plot page from a book
im = plt.imread("./book_page.jpg")
implot = ax1.imshow(im, origin='lower')
# Plot a graph and set background to transparent
x = np.linspace(0,4.*np.pi,40)
y = np.sin(x)
ax2.plot(x,y,'-ro',alpha=0.5)
ax2.set_ylim([-1.1,1.1])
ax2.patch.set_alpha(0.0)
from matplotlib import rc
rc('text', usetex=True)
margin = im.shape[0]*0.075
ytext = im.shape[1]/2.+10
ax1.text(margin, ytext, "The following text is an example")
ax1.text(margin, 90, "Figure 1. Showing a sine function")
plt.show()
Which looks like this,
where I used the following book image.
UPDATE: Added non-affine transformation based on scikit-image warp example, but with Maxwell distribution. The solution saves the matplotlib line as an image in order to apply a pointwise transform. Mapping for vector graphics may be possible but I think this will be more complicated...
import numpy as np
import matplotlib.pyplot as plt
def maxwellian_transform_image(image):
from skimage.transform import PiecewiseAffineTransform, warp
rows, cols = image.shape[0], image.shape[1]
src_cols = np.linspace(0, cols, 20)
src_rows = np.linspace(0, rows, 10)
src_rows, src_cols = np.meshgrid(src_rows, src_cols)
src = np.dstack([src_cols.flat, src_rows.flat])[0]
# add maxwellian to row coordinates
x = np.linspace(0, 3., src.shape[0])
dst_rows = src[:, 1] + (np.sqrt(2/np.pi)*x**2 * np.exp(-x**2/2)) * 50
dst_cols = src[:, 0]
dst_rows *= 1.5
dst_rows -= 1.0 * 50
dst = np.vstack([dst_cols, dst_rows]).T
tform = PiecewiseAffineTransform()
tform.estimate(src, dst)
out_rows = image.shape[0] - 1.5 * 50
out_cols = cols
out = warp(image, tform, output_shape=(out_rows, out_cols))
return out
#Create the new figure
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
#Plot page from a book
im = plt.imread("./book_page.jpg")
implot = ax.imshow(im, origin='lower')
# Plot and save graph as image, will need some manipulation of location
temp, at = plt.subplots()
margin = im.shape[0]*0.1
x = np.linspace(margin,im.shape[0]/2.,40)
y = im.shape[1]/3. + 0.1*im.shape[1]*np.sin(12.*np.pi*x/im.shape[0])
at.plot(x,y,'-ro',alpha=0.5)
temp.savefig("lineplot.png",transparent=True)
#Read in plot as an image and apply transform
plot = plt.imread("./lineplot.png")
out = maxwellian_transform_image(plot)
ax.imshow(out, extent=[0,im.shape[1],0,im.shape[0]])
plt.show()
The figure now looks like,

Resources