Automated Updating Matplotlib Plot in PySimpleGUI Window - python-3.x

I'm creating a GUI to allow users to see a "live view" of a spectrometer where data is taken from the spectrometer and plotted in Matplotlib to be displayed in the GUI window. The GUI also has a few other buttons which allow the user to go through other functions (irrelevant but just background).
I've gotten the live view to work in matplotlib using a while loop and clearing the data to re-plot:
while True:
data = ccs.take_data(num_avg=3) # spectrometer function
norm = (data[0]-dark[0])/(light[0]-dark[0]) # some calcs.
plt.plot(data[1],norm)
plt.axis([400,740,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
plt.pause(0.1)
plt.cla()
Next step was to show this figure in PySimpleGUI. Harder than expexted... I was able to use a few demo codes from PySimpleGUI to get a single figure to appear and update if user presses 'update' button:
from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
def fig_maker(ccs, dark, sub):
plt.clf()
plt.close()
data = ccs.take_data(num_avg=3)
norm = (data[0]-dark[0])/(sub[0]-dark[0])
plt.plot(data[1],norm,c='r')
plt.axis([400,750,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
... some code ...
# define the window layout
layout = [[sg.Button('update')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')] ]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None: # if user closes window
break
if event == "update":
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(ccs,dark,sub)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.close()
Now for the fun part (I can't seem to get it to work). I would like the plot to always be updating similar to how I did it using just matplotlib so that the user doesn't have to press 'update'. Using PySimpleGUI long_task threaded example is where my program starts to fail. I don't actually get any errors thrown except for a print to the Debug I/O stating *** Faking Timeout *** before Python closes the script.
I even just tried to do a for loop of 10 iterations instead of continuous while loop:
from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
def long_function_thread(window, ccs, dark, sub):
for i in range(10):
fig = fig_maker(ccs, dark, sub)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.write_event_value('-THREAD PROGRESS-', i)
time.sleep(1)
delete_fig_agg(fig_agg)
time.sleep(0.1)
window.write_event_value('-THREAD DONE-', '')
def long_function(window, ccs, dark, sub):
print('In long_function')
threading.Thread(target=long_function_thread, args=(window, ccs, dark, sub), daemon=True).start()
def fig_maker(ccs, dark, sub):
plt.clf()
plt.close()
data = ccs.take_data(num_avg=3)
norm = (data[0]-dark[0])/(sub[0]-dark[0])
plt.plot(data[1],norm,c='r')
plt.axis([400,750,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
... some code ...
# define the window layout
layout = [[sg.Button('Go')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')] ]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
if event == 'Go':
print('Calling plotter')
long_function(window, ccs, dark, sub)
print('Long function has returned from starting')
elif event == '-THREAD DONE-':
print('Your long operation completed')
window.close()
Appologies on the long description and code dump but I thought this is the easiest way to explain. Any help or links on this issue would be greatly appreciated.
If someone wants to try and run my script this should just produce a random plot instead
def random_fig_maker():
plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
return plt.gcf()

You need to use two additional PySimpleGUI features: window.Refresh() and window.write_event_value(). When you deleted figg_agg and the new plot is ready, call window.Refresh(). This will redraw the window, but also introduces a problem: the main event (while) loop will keep running forever. To address this, you also need to add window.write_event_value('-THREAD-', 'some message.') to one of the functions that is called from within the event loop. This will act as an external trigger for the event loop to keep running, but this will also keep the window responsive, so you can change some other window element (here I used a radio switch) to stop the loop.
For bonus points, you can also run the "trigger function" as a separate thread. Then, time.sleep() in that function will not affect GUI responsiveness. Because of this, I would use some data gathering function that only returns some lists or tuples as the trigger for restarting the loop. In this case, matplotlib was unhappy about being called from an external thread, so I just added a delay in the event loop to keep the plot visible.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
def fig_maker(window): # this should be called as a thread, then time.sleep() here would not freeze the GUI
plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
window.write_event_value('-THREAD-', 'done.')
time.sleep(1)
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
# define the window layout
layout = [[sg.Button('update'), sg.Button('Stop', key="-STOP-"), sg.Button('Exit', key="-EXIT-")],
[sg.Radio('Keep looping', "RADIO1", default=True, size=(12,3),key="-LOOP-"),sg.Radio('Stop looping', "RADIO1", size=(12,3), key='-NOLOOP-')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')]]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None: # if user closes window
break
if event == "update":
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(window)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
if event == "-THREAD-":
print('Acquisition: ', values[event])
time.sleep(1)
if values['-LOOP-'] == True:
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(window)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.Refresh()
if event == "-STOP-":
window['-NOLOOP-'].update(True)
if event == "-EXIT-":
break
window.close()

It's not exactly connected but I had a similar problem. Does this help....
import PySimpleGUI as sg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class updateable_matplotlib_plot():
def __init__(self, canvas) -> None:
self.fig_agg = None
self.figure = None
self.canvas = canvas
def plot(self, data):
self.data = data
self.figure_controller()
self.figure_drawer()
#put all of your normal matplotlib stuff in here
def figure_controller(self):
#first run....
if self.figure is None:
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.line, = self.axes.plot(self.data)
self.axes.set_title("Example of a Matplotlib plot updating in PySimpleGUI")
#all other runs
else:
self.line.set_ydata(self.data)#update data
self.axes.relim() #scale the y scale
self.axes.autoscale_view() #scale the y scale
#finally draw the figure on a canvas
def figure_drawer(self):
if self.fig_agg is not None: self.fig_agg.get_tk_widget().forget()
self.fig_agg = FigureCanvasTkAgg(self.figure, self.canvas.TKCanvas)
self.fig_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
self.fig_agg.draw()
def getGUI():
# All the stuff inside your window.
layout = [ [sg.Canvas(size=(500,500), key='canvas')],
[sg.Button('Update', key='update'), sg.Button('Close')] ]
# Create the Window
window = sg.Window('Updating a plot example....', layout)
return window
if __name__ == '__main__':
window = getGUI()
spectraPlot = updateable_matplotlib_plot(window['canvas']) #what canvas are you plotting it on
window.finalize() #show the window
spectraPlot.plot(np.zeros(1024)) # plot an empty plot
while True:
event, values = window.read()
if event == "update":
some_spectrum = np.random.random(1024) # data to be plotted
spectraPlot.plot(some_spectrum) #plot the data
if event == sg.WIN_CLOSED or event == 'Close': break # if user closes window or clicks cancel
window.close()

Related

Select points from a scattr plot using mouse cross hair - PyQt

I am new to PyQt and Im developing a utility where a user can import data from an excel file and plot its X and Y in a 2d scatter plot using below code:
def plot_2d_scatter(graphWidget,x,z,color=(66, 245, 72)):
graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
Now I want a functionality which will allow the user to move his mouse over the scatter plot points using a cross hair / pointer / etc and select points on the scatter plot.
Whenever the user does a left click on the crosshair / marker on the scatter plot, I want its x,y coordinates to be saved for further use.
I have already tried the below snippet from somewhere on internet for using mouse events and getting my scatter points , but this didnt give me a cross hair that falls on my scatter points
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.plotx])
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
Any guidance is thankfully appreciated
my best fast effort, never used pg untill today:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDesktopWidget, QWidget
from PyQt5.QtCore import Qt
from PyQt5 import QtGui, QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
class MyApp(QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.resize(781, 523)
self.graphWidget = pg.PlotWidget()
self.setCentralWidget(self.graphWidget)
self.show()
self.x = [1,2,3,4,5,6,7,8,9,5,6,7,8]
self.y = [1,2,3,4,5,6,7,8,9,5,6,7,8]
# self.y.reverse()
self.plot_2d_scatter(self.graphWidget, self.x, self.y)
self.cursor = Qt.CrossCursor
# self.cursor = Qt.BlankCursor
self.graphWidget.setCursor(self.cursor)
# Add crosshair lines.
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)
self.cursorlabel = pg.TextItem()
self.graphWidget.addItem(self.cursorlabel)
self.proxy = pg.SignalProxy(self.graphWidget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)
self.mouse_x = None
self.mouse_y = None
def plot_2d_scatter(self,graphWidget,x,z,color=(66, 245, 72)):
# graphWidget.clear()
brush = pg.mkBrush(color)
scatter = pg.ScatterPlotItem(size=5, brush=brush)
scatter.addPoints(x,z)
graphWidget.addItem(scatter)
def update_crosshair(self, e):
pos = e[0]
if self.graphWidget.sceneBoundingRect().contains(pos):
mousePoint = self.graphWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(float(i) - float(mousePoint.x())) for i in self.x])
index = mx.argmin()
if index >= 0 and index < len(self.x):
self.cursorlabel.setText(
str((self.x[index], self.y[index])))
self.crosshair_v.setPos(self.x[index])
self.crosshair_h.setPos(self.y[index])
self.mouse_x = self.crosshair_v.setPos(self.x[index])
self.mouse_y = self.crosshair_h.setPos(self.y[index])
self.mouse_x = (self.x[index])
self.mouse_y = (self.y[index])
def mousePressEvent(self, e):
if e.buttons() & QtCore.Qt.LeftButton:
print('pressed')
# if self.mouse_x in self.x and self.mouse_y in self.y:
print(self.mouse_x, self.mouse_y)
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
# myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
it just prints out the coordinate of pressed point in graph
copied from https://www.pythonguis.com/faq/pyqt-show-custom-cursor-pyqtgraph/ and your piece of code result looks like:
there are other examples on SO like Trying to get cursor with coordinate display within a pyqtgraph plotwidget in PyQt5 and others

Matplotlib Navigation Toolbar in wxPython Panel

I am working on a GUI (developed with wxPython) where you can plot graphs on different panels. At the moment I have this:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib import pyplot as plt
import numpy as np
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,-1,'Plot',size=(1000,800))
# Main Panel (It will contain other elements besides the plotting panel)
self.mainPanel = wx.Panel(self,-1,size=(1000,800))
self.mainPanel.SetBackgroundColour('gray')
# Plotting panel
self.plottingPanel = wx.Panel(self,-1,pos=(50,20),size=(500,400))
self.plottingPanel.SetBackgroundColour('white')
# Plot example
figure = plt.figure()
axes = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01); s = np.cos(2 * np.pi * t);axes.plot(t,s)
plt.title('Cosine')
plt.xlabel('x');plt.ylabel('y')
# Canvas
canvas = FigureCanvas(self.plottingPanel,-1,figure)
# Navegation toolbar
navToolbar = NavigationToolbar2Wx(canvas)
navToolbar.DeleteToolByPos(6);navToolbar.DeleteToolByPos(2);navToolbar.DeleteToolByPos(1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(canvas)
sizer.Add(navToolbar)
class App(wx.App):
def OnInit(self):
self.Frame = Frame()
self.Frame.Show()
self.SetTopWindow(self.Frame)
return True
def main():
app = App()
app.MainLoop()
if __name__ == '__main__':
main()
When I run the script get this:
I have colored the plotting panel white to highlight it. How can the plot size be adapted to the panel size?
I want to get something like this (this is a montage):
On the other hand, I managed to eliminate from the bar some buttons that are unnecessary for what I need but the bar does not work, that is, when pressing the buttons nothing happens :(
Thanks for your help
It is possible to set parameters when the container for the plot elements (matplotlib.figure.Figure) is created.
e.g. figsize sets the figure dimension in inches and tight_layout adjust the sub plots in tight layout.
figure = plt.figure(figsize = (4, 3), tight_layout=True)
Alternatively you can set the position of the matplotlib.axes.Axes object by .set_position:
figure = plt.figure()
axes = figure.add_subplot(111)
axes.set_position(pos = [0.15,0.3,0.55,0.55], which='both')

Python code activated with checkbox/button

I want to call function doit when Checkbutton is ON and stop it when it's OFF.
I tried to do it with button and it kinda works, but when I have my CheckButton in ON and I click my button my window freeze and I can't turn it off again.
from tkinter import *
import PIL.ImageGrab
from PIL import ImageGrab
import time
import cv2
import numpy as np
import pyautogui
import random
def doit():
time.clock()
while label_text.get()=="ON":
rgb = PIL.ImageGrab.grab().load()[1857,307]
print(rgb)
print(time.clock())
else:
print('module is turned OFF')
window = Tk()
label_text = StringVar()
label = Label(window, textvariable=label_text)
label_text.set("OFF")
check=Checkbutton(window, text=label_text.get(), variable=label_text,
onvalue="ON", offvalue="OFF")
label.pack()
check.pack(side="left")
b = Button(window, text="OK", command=doit)
b.pack()
window.mainloop()
When you run long-runnign process - your while loop - then mainloop can't work and it can't get mouse/keyboard events from system, sends events to widgets, updates widgets, redraws window.
You can run doit once - without while - and then use after(time, doit) to run it after some time. This way mainloop will have time to do its job.
def doit():
time.clock()
if label_text.get() == "ON":
rgb = PIL.ImageGrab.grab().load()[1857,307]
print(rgb)
print(time.clock())
after(50, doit)
else:
print('module is turned OFF')
Or use window.update() in while to give mainloop time to update elements.
def doit():
time.clock()
while label_text.get() == "ON":
rgb = PIL.ImageGrab.grab().load()[1857,307]
print(rgb)
print(time.clock())
window.update()
else:
print('module is turned OFF')
If PIL.ImageGrab.grab() runs longer then you may have to run it in separated thread.

matplotlib - matplotlib not plot at the second time running in a thread

I have a small program that plots a figure with 4 subplots. I make a little GUI and call that plotting function from a thread, so each time I click the button, it should recollect data and redraw that figure (with 4 subplot), without closing the script. The script can plot for the first time, however at the second time (click button without closing the script), it stops at initialize the subplots. I have tried plt.close('all'), plt.close(fig), plt.clf(),... but it doesnt help. I really run out of idea why it stops at the second time.
Here is my full little script. Much appreciate for any inputs
import sys, os
import time
import wx
import traceback
from textwrap import wrap
import shutil
import itertools
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import threading
def test():
Do = Data()
Do.PlotGraph()
class Data(object):
def __init__(self):
self.SavePath = "C:\\Plots\\"
def f(self, t):
return np.exp(-t) * np.cos(2*np.pi*t)
def PlotGraph(self):
#Plotting
print "***** Generating plot"
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
print "Initialize subplots"
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows = 2, ncols = 2, frameon=False, figsize=(20, 10))
fontSize = 13
print "Creating ax1"
ax1title = "ax1"
ax1.set_title(ax1title, fontsize = fontSize)
ax1.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax2"
ax2title = "ax2"
ax2.set_title(ax2title, fontsize = fontSize)
ax2.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax3"
ax3title = "ax3"
ax3.set_title(ax3title, fontsize = fontSize)
ax3.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax4"
ax4title = "ax4"
ax4.set_title(ax4title, fontsize = fontSize)
ax4.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
fig.subplots_adjust(hspace = 0.35) #make room for axes title and x-axis label
fig.subplots_adjust(bottom = 0.07) #make room for axes title and x-axis label
fig.subplots_adjust(wspace = 0.30)
fig.subplots_adjust(top = .86)
filename= "Test"
if not os.path.exists(self.SavePath):
os.makedirs(self.SavePath)
savefilename = self.unique_file(self.SavePath, filename, "png")
print "***** Saving plot to: " + self.SavePath + savefilename
fig.savefig(self.SavePath + savefilename, dpi = 200)
plt.close(fig)
def unique_file(self, path, basename, ext):
actualname = "%s.%s" % (basename, ext)
c = itertools.count()
while os.path.exists(path + actualname):
actualname = "%s_[%d].%s" % (basename, next(c), ext)
#print "actualname: " + actualname
return actualname
################## THREAD UPDATE GUI ######################
#1. Create new custom event to update the display
DisplayEventType = wx.NewEventType();
EVT_DISPLAY = wx.PyEventBinder(DisplayEventType, 1);
def GetDataThreadStart(window):
GetDataThread(window)
class GetDataThread(threading.Thread):
def __init__(self, output_window):
threading.Thread.__init__(self)
self.output_window = output_window
print "Thread started"
self.start()
def run(self):
test()
print "Test Done\n\n"
self.UpdateFunction("Enable Go Button")
def UpdateFunction(self, msg):
evt = UpdateDisplayEvent(DisplayEventType, -1) #initialize update display event
evt.UpdateText(str(msg)); #update display event
wx.PostEvent(self.output_window, evt)
#Define event
class UpdateDisplayEvent(wx.PyCommandEvent):
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.msg = ""
def UpdateText(self,text):
self.msg = text
def GetText(self):
return self.msg
######## Define GUI ###########
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
class MyFrame(wx.Frame):
def __init__(self, parent):
self.title = 'Testing'
wx.Frame.__init__(self, parent, -1, self.title, size = (300,350))
pnl = MyPanel(self)
self.Go_Button = wx.Button(pnl, -1, "Goooooo!")
self.Bind(wx.EVT_BUTTON, self.ClickGo, self.Go_Button)
BoxSizerMain = wx.BoxSizer(wx.VERTICAL)
BoxSizerMain.Add(self.Go_Button, 0, wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(EVT_DISPLAY, self.OnThreadUpdate)
def ClickGo(self, event):
self.Go_Button.Disable()
GetDataThreadStart(self)
def OnThreadUpdate(self, event):
msg = event.GetText()
if msg == "Enable Go Button":
self.Go_Button.Enable()
def invokeGUI():
app = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
frame.Iconize(True)
frame.Iconize(False)
frame.CenterOnScreen()
app.MainLoop()
if __name__ == '__main__':
invokeGUI()
The result
I believe you need to change the matplotlib backend to wx via the matplotlib.use() function in order to embed matplotlib plots within wx graphics.
Here's what I changed in your script:
import matplotlib
matplotlib.use('WX')
import matplotlib.pyplot as plt
This produced the following image after testing:
Console output:
Initialize subplots
Creating ax1
Creating ax2
Creating ax3
Creating ax4
***** Saving plot to: /Users/luccary/Downloads/foo/Test_[0].png
/Users/luccary/.virtualenvs/numpy/lib/python2.7/site-packages/matplotlib/cbook.py:136: MatplotlibDeprecationWarning: The WX backend is deprecated. It's untested and will be removed in Matplotlib 2.2. Use the WXAgg backend instead. See Matplotlib usage FAQ for more info on backends.
warnings.warn(message, mplDeprecation, stacklevel=1)
Test Done
Please note the deprecation warning, since wx will be removed as a backend in Matplotlib 2.2, and you should likely use wxagg instead.
References: See this matplotlib tutorial which I found after reading a Stack Overflow answer to a similar question here.
Hope that answers your question. Good luck!

Plot mouse clicks over an image

I writing a code in Python 3 to plot some markers over a DICOM image. for this, I wrote a very short program:
In the main program, I read the DICOM filename from the terminal and plot the image.
main_prog.py:
import sys
import dicom as dcm
import numpy as np
from matplotlib import pyplot as plt
from dicomplot import dicomplot as dcmplot
filename = sys.argv[1]
dicomfile = dcm.read_file(filename)
dicomimg = dicomfile.pixel_array
fig = plt.figure(dpi = 300)
ax = fig.add_subplot(1, 1, 1)
plt.set_cmap(plt.gray())
plt.pcolormesh(np.flipud(dicomimg))
dcm = dcmplot(ax)
plt.show()
Then, I define a class to store the coordinates clicked by the user and plot each of them at a time over the image:
dicomplot.py
from matplotlib import pyplot as plt
class dicomplot():
def __init__(self, img):
self.img = img
self.fig = plt.figure(dpi = 300)
self.xcoord = list()
self.ycoord = list()
self.cid = img.figure.canvas.mpl_connect('button_press_event', self)
def __call__(self, event):
if event.button == 1:
self.xcoord.append(event.x)
self.ycoord.append(event.y)
self.img.plot(self.ycoord, self.xcoord, 'r*')
self.img.figure.canvas.draw()
elif event.button == 2:
self.img.figure.canvas.mpl_disconnect(self.cid)
elif event.button == 3:
self.xcoord.append(-1)
self.ycoord.append(-1)
The problem is that when I click over the image, the markers appear in a different scale, and not over the image as they are supposed to.
How can I modify my code so when I click on the image, all the mouse clicks are stored and ploted in the desired position?
The MouseEvent objects carry both a x/y andxdata/ydata attributes (docs). The first set is in screen coordinates (ex pixels from the lower left) and the second set (*data) are in the data coordinates.
You might also be interested in mpldatacursor.

Resources