python: How to monitor a variable ? Execute command when variable change - python

I have a class like this
class WaveData(object):
def __init__(self, data):
self.data = data
and create a data object, plot a figure
wave = WaveData([[1, 2, 3],
[7, 5, 6]])
import matplotlib.pyplot as plt
fig=plt.figure()
plot1, = fig.canvas.figure.subplots().plot(wave.data[0])
plot2, = fig.canvas.figure.subplots().plot(wave.data[1])
I hope when I change the wave value , plot will change synchronously
wave.data[1]=[5,6,7] # hope figure change together
I try to add method changedata for WaveData class, but:
it need use global variable fig , maybe not reasonale (I can put fig as self attribute , but in fact , the fig also link other class object which not written in here)
I cannot change fig by changing data directly: wave.data[1] =[5,6,7]
class WaveData(object):
def __init__(self, data):
self.data = data
def changedata(self,value,index):
self.data[index]=value
#-- change the plot index th plot data--#
global plot1,plot2,fig
plot1.set_ydata(self.data[1])
plot2.set_ydata(self.data[2])
fig.canvas.draw_idle()
#-- change the plot index th plot data--#
I want to create a watcher to monitor wave.data value . When detecte the value change , execute some action
How to do?

Right: plotting is not a dynamic or interactive process. You started the right way, with an access method to change the wave form. Now you have to re-plot and re-show the result ... which will possibly require closing the first plot manually, depending on the interface of your chosen drawing package (e.g. matplotlib).
To get a fully interactive experience, you may want to use an animation package, such as PyGame, where changes in the visual display are part of the package assumptions.

Related

Can I show a plot created in another file on JupyterLab?

I wish to have an interactive map that you can click where, once clicked, a SkewT and Hodograph will be plotted showing the information for that location. I have thus created a class where I add all the necessary informations using the metpy library and I am able to successfully create these graphs:
SkewT and Hodograph plotted
The problem comes when I'm trying to import the classes I've created to generate these plots into jupyterlab. Since the code to actually make these plots is quite cumbersome, I'd rather
keep the code in a separate file and import my SoundingGraphs class, but it's not working. The graphs never get plotted inside a cell, they instead appear in the logs as a Warning and as an Info and I have no idea why:
Graphs appearing inside logs
Tried to use plt.show() inside my file, tried returning plt to then use plt.show() inside a cell of the notebook, tried using %matplotlib widget, %matplotlib notebook and %matplotlib inline, tried changing jupyterlab versions, none of these changed anything.
I have found one solution that I disliked, but that does work, which is rather than doing a plt.show(), to instead do this inside my class:
buffer = BytesIO()
plt.savefig(buffer, format='png')
return buffer
And in the notebook I would do:
image = Image()
display(image)
def on_generate_button_clicked(b):
buffer = SoundingGraphs(infos)
buffer.seek(0)
image.value=buffer.read()
image.format='png'
generate_button.on_click(on_generate_button_clicked)
I don't quite like this approach because further down the line I would like to add interactivity to my plots, like show values of plot when hovered and things like that, thus I don't just want to show an image. So I'd like to know if it is indeed possible to plt.show() a plot created inside another file in a cell.
Using:
Python 3.6.9
jupyterlab==3.2.9
jupyterlab-pygments==0.1.2
jupyterlab-server==2.10.3
jupyterlab-widgets==1.1.0
ipykernel==5.5.6
ipyleaflet==0.14.0
ipympl==0.8.8
ipython==7.16.3
ipython-genutils==0.2.0
ipywidgets==7.7.0
matplotlib==3.3.4
Thanks!
Yes, it is possible after all!
%matplotlib widget needs to be used at the start of the notebook and since the class method will be called from another function (on a button.on_click event), it is possible to use the #out.capture() decorator above it so that the plt.show() gets displayed. It's also possible to make the figure a class attribute to be able to have more control.
So here's a bit of working code if someone would like to replicate:
Notebook
%matplotlib widget
from ipywidgets import Button, Output
from myfile import MyClass
out = Output()
example_button = Button(
description='Example',
disabled=False,
button_style='',
tooltip='Click me'
)
#out.capture()
def on_example_button_clicked(b):
example_button.disabled = True
myclass = MyClass()
myclass.create_plot()
out.clear_output(wait=True)
display(myclass.fig.canvas)
example_button.disabled = False
example_button.on_click(on_example_button_clicked)
display(example_button)
display(out)
myfile.py
import matplotlib.pyplot as plt
class MyClass():
def __init__(self):
plt.ioff() # otherwise it'll also show inside logs
plt.clf()
self.fig = plt.figure()
def create_plot(self):
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')

Store multiple matplotlib figures in a dictionary and use keys to display them / modify them at will?

I have a function that generates a plot from data I have read in. The goal is to be able to open multiple plot windows, each with a unique name, and then allow other functions to alter them at will (updating the data in them, changing colors, etc.), or to re-open plots that have been closed.
What I have tried to do is this:
1) Define a class, workspace, that has an attribute Figures that is a dictionary. I intend to assign my figures to unique keys in this dictionary.
2) Now, I generate a figure, and then save it into the WS.Figures dictionary.
3) Simplest use case, the user closes the figure and wants to re-open it, without having to go through make_plot() again. (My actual function has far, far, more complexity in the plotting - it's a pain to re-create each time manually). So I tried to write the following, which does absolutely nothing, apparently.
class WorkSpace(object):
def __init__(self):
self.Figures = {}
return
WS = WorkSpace()
def make_plot(x_data,y_data,name):
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x_data,y_data)
fig.canvas.draw()
WS.Figures[name] = fig
return
def replot(name):
WS.Figures[name].canvas.draw()
return
My thinking here was that WS.Figures[name] would hold an identical copy of fig from make_plot() and so I'd just need to issue the same command to make it appear.
I was hoping this would be the absolute easiest thing to do, and that I could then use that dictionary key to refer to the plot, update axes, etc. whenever I needed. But if I can't even replot it...

matplotlib animation: write to png files without third party module

The animation module in matplotlib usually requires third party modules like FFmpeg, mencoder or imagemagik to be able to save the animation to a file (e.g. here: https://stackoverflow.com/a/25143651/5082048).
Even the MovieWriter class in matplotlib seems to be build in a way that third party modules will be incorporated (starting and closing processes, cummunicating via pipe): http://matplotlib.org/api/animation_api.html#matplotlib.animation.MovieWriter.
I am looking for a way, how I can save a matplotlib.animation.FuncAnimation object frame to frame to png - directly, within python. Afterwards, I want to show the .png files as animation in an iPython notebook using this approach: https://github.com/PBrockmann/ipython_animation_javascript_tool/
Therefore my questions are:
How can I save an matplotlib.animation.FuncAnimation object directly to .png files without the need to use third party modules?
Is there a writer class implemented for this usecase?
How can I get figure objects frame by frame out of the FuncAnimation object (so that I could save them myself)?
Edit: The matplotlib.animation.FuncAnimation object is given, the task is to save it's frames using pure Python. Unfortunately, I cannot change the underlying animation function as ImportanceOfBeingErnest suggested.
Althoough this may seem a bit complicated, saving the frames of an animation may be easily done within the animation itself.
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
def animate(i):
line.set_ydata(np.sin(2*np.pi*i / 50)*np.sin(x))
#fig.canvas.draw() not needed see comment by #tacaswell
plt.savefig(str(i)+".png")
return line,
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1,1)
x = np.linspace(0, 2*np.pi, 200)
line, = ax.plot(x, np.zeros_like(x))
plt.draw()
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=5, repeat=False)
plt.show()
Mind the repeat = False argument, which will prevent the animation to run continuously and repeat writing the same files to disk.
Note that if you're willing to loosen the restriction of "no external package" you may use imagemagick to save the pngs
ani.save("anim.png", writer="imagemagick")
which will save files anim-1.png, anim-2.png etc.
Finally note that there are of course easier methods to show an animation in a jupyter notebook.
You want to look at the FileMovieWriter sub-classes (See http://matplotlib.org/2.0.0rc2/api/animation_api.html#writer-classes) You probably want to sub-class FileMoveWriter, something like
import matplotlib.animation as ma
class BunchOFiles(ma.FileMovieWriter):
def setup(self, fig, dpi, frame_prefix):
super().setup(fig, dpi, frame_prefix, clear_temp=False)
def _run(self):
# Uses subprocess to call the program for assembling frames into a
# movie file. *args* returns the sequence of command line arguments
# from a few configuration options.
pass
def grab_frame(self, **savefig_kwargs):
'''
Grab the image information from the figure and save as a movie frame.
All keyword arguments in savefig_kwargs are passed on to the 'savefig'
command that saves the figure.
'''
# Tell the figure to save its data to the sink, using the
# frame format and dpi.
with self._frame_sink() as myframesink:
self.fig.savefig(myframesink, format=self.frame_format,
dpi=self.dpi, **savefig_kwargs)
def cleanup(self):
# explictily skip a step in the mro
ma.MovieWriter.cleanup(self)
(this is not tested, might be better to just implement a class that implements saving, grab_frame, finished, and setup)
I could not get tacaswell's answer to work without modification. So, here is my take at it.
from matplotlib.animation import FileMovieWriter
class BunchOFiles(FileMovieWriter):
supported_formats = ['png', 'jpeg', 'bmp', 'svg', 'pdf']
def __init__(self, *args, extra_args=None, **kwargs):
# extra_args aren't used but we need to stop None from being passed
super().__init__(*args, extra_args=(), **kwargs)
def setup(self, fig, dpi, frame_prefix):
super().setup(fig, dpi, frame_prefix, clear_temp=False)
self.fname_format_str = '%s%%d.%s'
self.temp_prefix, self.frame_format = self.outfile.split('.')
def grab_frame(self, **savefig_kwargs):
'''
Grab the image information from the figure and save as a movie frame.
All keyword arguments in savefig_kwargs are passed on to the 'savefig'
command that saves the figure.
'''
# Tell the figure to save its data to the sink, using the
# frame format and dpi.
with self._frame_sink() as myframesink:
self.fig.savefig(myframesink, format=self.frame_format,
dpi=self.dpi, **savefig_kwargs)
def finish(self):
self._frame_sink().close()
We can save a set of files with:
anim.save('filename.format', writer=BunchOFiles())
and it will save the files in the form 'filename{number}.format'.

Matplotlib unexpectedly hidden polygons

I am using matplotlib 1.4.3 with python 3.3. I would like to draw multiple figures with multiples sub-plot in it. I am facing some kind of bug that is really boring me:
When I use fill() and boxplot() methods whithin a figure, results of those functions are hidden if the figure is not the first one created.
This bug seems to be related somehow to polygon display and matplotlib environment state.
When a parse only one single figure, everything is working fine. When I parse multiple figures, the first one is ok. But, in every other subsequent figures, everything is all-right except wiskerbox and polygons that are hidden.
Each plot code is wrapped into a function, which accepts positional arguments, *args and **kwargs. Lets say signature are:
def myplot(t, x, *args, *kwargs):
# [...]
hFig = plt.figure()
# [...]
return hFig
As far as I understand python mechanisms, after the function call is resolved, there must be nothing alive (I do not use global variables) except what matplotlib environment has stored into its global namespace variables.
In every call, I close() my figure, I also have tried hFig.clf() in addition before leaving function, but it does not solve the problem.
Each plot is wrapped into printer (decorator) to add generic functionalities:
def myprint(func):
def inner(*args, **kwargs)
# [...]
hFig = func(*args, **kwargs)
# [...]
return inner
What I have tried so far:
Increased zscore of wiskerbox and polygons, not working;
Execute plot generation in different threads, not working;
Execute plot generation in different processes, working but I have to change my function signature because it can be pickled.
I do not want use dill and pathos, even if I would I cannot.
It looks like it is a matplotlib environment bug, because when I run different processes, this environment is recreated from scratch and it works the way it should. I would like to know if there is a way to reset matplotlib environment state within a python script. If not, what can I do for solving this issue.
Obs.: I am using GridSpecs object and subplot() method to create my figures. The problem was not present when I computed boxes myself and used add_axes() method.
Update: Here you can find a MCVE of my problem. By doing this simple example, I found the line which makes my bug happens (looks like I have old bad Matlab behaviours). It seems that plt.hold(False) alters the way of polygons and boxplot are displayed. And, as I pointed out, it was related to matplotlib global namespace variable. I just misunderstood the way this method works, and in each sub-process, it was reset.
import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
def bloodyplotit(t_, x_):
hFig = plt.figure()
gs = gspec.GridSpec(1, 4, height_ratios=[1], width_ratios=[15, 2, 3, 1])
gs.update(left=0.10, right=0.90, top=0.90, bottom=0.25, hspace=0.05, wspace=0.05)
plt.hold(True)
hAxe = plt.subplot(gs[0,0])
hAxe.plot(t_, x_)
#plt.hold(False) # <------------------------ This line make the scirpt bug
hAxe = plt.subplot(gs[0,1])
hAxe.hist(x_, orientation='horizontal')
hAxe = plt.subplot(gs[0,3])
hAxe.boxplot(x_)
plt.show()
n = 1000
t = datetime.datetime.utcnow() + np.arange(n)*datetime.timedelta(minutes=1)
x = np.random.randn(1000,1)
for i in range(10):
bloodyplotit(t, x)
Here's an even more minimal script that produces the error:
x = np.random.randn(1000)
fig, ax = plt.subplots(1, 2)
ax[0].hold(True)
ax[0].boxplot(x);
ax[1].hold(False)
ax[1].boxplot(x);
As far as I can tell, this is expected behavior. According to the documentation of plt.hold,
When hold is True, subsequent plot commands will be added to the current axes. When hold is False, the current axes and figure will be cleared on the next plot command.
Boxplot is a compound object: it is created by calling multiple plotting commands. If hold is False, the axes are cleared between each of those commands, so parts of the boxplot don't show up.
Personally, I've never found a reason to toggle the hold state in 10+ years of using matplotlib. My experience is that doing it (especially globally) just causes confusion, and I'd recommend avoiding it.

Real-time plot in matplotlib - python

I'm trying to get real-time spectrum analyzer type plot in matplotlib. I've got some code working (with help from other posts on StackOverflow) as follows:
import time
import numpy as np
import matplotlib.pyplot as plt
plt.axis([0, 1000, 0, 1])
plt.ion()
plt.show()
i=0
np.zeros([1,500],'float')
lines=plt.plot(y[0])
while 1:
i=i+1
lines.pop(0).remove()
y = np.random.rand(1,100)
lines=plt.plot(y[0])
plt.draw()
The code works and I'm getting what I want, but there is a serious problem. The plot window would freeze after some time. I know the program is still running by inspecting the i variable (I'm running the code in Anaconda/Spyder so I can see the variables). However the plot window would show "Non responding" and if I terminate the python program in Spyder by ctrl+c, the plot window comes back to life and show the latest plot.
I'm out of wits here as how to further debug the issue. Anyone to help?
Thanks
I am not sure that adding plt.pause will entirely solve your issue. It may just take longer before the application crash. The memory used by your application seems to constantly increase over time (even after adding plt.pause). Below are two suggestions that may help you with your current issue:
Instead of removing/recreating the lines artists with each iteration with remove and plot, I would use the same artist throughout the whole animation and simply update its ydata.
I'll use explicit handlers for the axe and figure and call show and draw explicitly on the figure manager and canvas instead of going with implicit calls through pyplot, following the advices given in a post by tcaswell.
Following the above, the code would look something like this:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.axis([0, 100, 0, 1])
y = np.random.rand(100)
lines = ax.plot(y)
fig.canvas.manager.show()
i=0
while 1:
i=i+1
y = np.random.rand(100)
lines[0].set_ydata(y)
fig.canvas.draw()
fig.canvas.flush_events()
I've run the above code for a good 10 minutes and the memory used by the application remained stable the whole time, while the memory used by your current code (without plt.pause) increased by about 30MiB over the same period.
To answer myself, I solved the issue by adding
plt.pause(0.01)
after the
plt.draw()
This probably allows the GUI to finish the drawing and clear the buffer somewhere (my guess) before the new data comes in.
I know I'm late to answer this question, but for your issue you could look into the "joystick" package. It is based on the line.set_data() and canvas.draw() methods, with optional axes re-scaling, hence most probably faster than removing a line and adding a new one. It also allows for interactive text logging or image plotting (in addition to graph plotting).
No need to do your own loops in a separate thread, the package takes care of it, just give the update frequency you wish. Plus the terminal remains available for more monitoring commands while live plotting, which is not possible with a "while True" loop.
See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
try:
import joystick as jk
import numpy as np
import time
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the doc
"""
self._t0 = time.time() # initialize time
self.xdata = np.array([self._t0]) # time x-axis
self.ydata = np.array([0.0]) # fake data y-axis
# create a graph frame
self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=100, xnptsmax=1000, xylim=(None, None, 0, 1)))
#_infinite_loop(wait_time=0.2)
def _generate_data(self): # function looped every 0.2 second to read or produce data
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.2 seconds
"""
# concatenate data on the time x-axis
self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
# concatenate data on the fake data y-axis
self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
self.mygraph.set_xydata(t, self.ydata)
t = test()
t.start()
t.stop()

Categories

Resources