-
Notifications
You must be signed in to change notification settings - Fork 948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Idea: Debounce and throttle options for observing traits #663
Comments
IIRC it's already debounced/throttle, if there are more that 5 (IIRC) query pendings. |
From what I understand from the code, it will not send 10 events at once after the kernel is finished, but just 1 (as is the default now). This kind of throttling and debouncing is different (and debounce!=throttle), it is based on elapsed time since the first event. It will also be useful in combination with multithreading, as discussed here #642 |
According to @jasongrout:
Related: #584 |
I should also add that every widget has a |
I currently have an 'ok' method to deal with this: def debounced(delay_seconds=0.5, method=False):
def wrapped(f):
counters = collections.defaultdict(int)
@functools.wraps(f)
def execute(*args, **kwargs):
if method: # if it is a method, we want to have a counter per instance
key = args[0]
else:
key = None
counters[key] += 1
def debounced_execute(counter=counters[key]):
if counter == counters[key]: # only execute if the counter wasn't changed in the meantime
f(*args, **kwargs)
ioloop = get_ioloop()
def thread_safe():
ioloop.add_timeout(time.time() + delay_seconds, debounced_execute)
ioloop.add_callback(thread_safe)
return execute
return wrapped Use it like this: @widgets.interact
@debounced(0.5)
def f(i=1):
print(i) Can also be used for methods, using from IPython.display import clear_output
@widgets.interact
@debounced(1)
def f(i=1, f=0.1):
clear_output(wait=True)
print(i,f) |
Function or method can be auto detected. https://docs.python.org/3/library/inspect.html#inspect.ismethod inspect.ismethod(object) inspect.isfunction(object) |
Hi all, I'd like to investigate this further, particularly in the context of using the Because matplotlib is slow, this basically renders using interact with matplotlib pointless, see this video: This is based on this tiny piece of code:
I think that my desires for interaction in this case could be solved with say a 100 ms debouncing, and the solution above from @maartenbreddels seems like it should work. But, so far, I haven't got it to work. And, it's an awful lot of boilerplate for what I suspect might be a common problem with interact(), and involves a bit of painful digging into the gory details of python threading (gory to me at least...) Does anyone have a suggestion on a simple, minimalist bit of code I could inject into my 3 lines of code above to achieve the debounce? Also, I suspect that this might be a common desire of users of the interact function, who want to quickly create an interaction with their code without needing to understand / dig into the details of the python mulithreading behind it. Maybe it would make sense for |
Did the discussion here occur prior to https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html?highlight=throttle#Throttling, or are these somewhat orthogonal issues?
Hi @gsteele13, I have some specific advice for how you can speed up interactive matplotlib without throttling or debouncing In general using the inline backend with savefig is going to be least performant option to display an interactive maptlotlib plot. This is because when combining interact with the inline backend what happens every time the slider value updates is:
So instead I always recommend using the ipympl backend which embeds a true interactive matplotlib figure in the notebook. In contrast the inline backend is constantly creating and saving and destroying new figures. Doing that allows you to use commands like So for example I get what I would argue is acceptable performance without any throttling or debouncing: %matplotlib ipympl
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
x = np.linspace(0,1,100)
fig, ax = plt.subplots()
line, = ax.plot(x, x**.5)
slider = widgets.FloatSlider(value=0.5, min=0, max=1, step=0.01)
def update_plot(change['new']):
# you can use either change['new']
# or slider.value. But one or the other may work better with
# throttling and debouncing
line.set_ydata(x**slider.value)
ax.set_ylim(0,1)
slider.observe(update_plot, names='value')
display(slider) You can of course still take the throttle or debounce functions from the docs: https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html?highlight=throttle#Throttling and apply them to update function if you want to debounce it even more. There is however a downside here, which is that ipympl and interact don't tend to play nice together (see matplotlib/ipympl#36). So it is better to manually create the sliders and attach them to the callback as I did above. This can be kind of a hassle and as you note:
boilerplate can indeed make it tricky to create an interaction. So I ended up making a library mpl-interactions that makes it easy to hook up widgets to matplotlib plots in the most performant ways possible. It was directly inspired by the issue you are having - the name is based on a combination of matplotlib and interact. Using that library you should get all the benefits of not creating new figures and your code simplifies to: %matplotlib ipympl
import mpl_interactions.ipyplot as iplt
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, np.pi, 100)
def f(x, p):
return x**p
fig, ax = plt.subplots()
controls = iplt.plot(x, f, p=(0,1,1000)) There's also this SO answer that I wrote for a related question https://stackoverflow.com/questions/65264185/slow-matplotlib-and-ipywidgets-image-refresh/65573875#65573875 |
Thanks @ianhi! I'll definitely try it out. I had some earlier hacks based on the notebook driver: The code is not quite as transparent as my three lines of code with the But I was unware of the ipympl driver, I'll definitely try out your mpl_interactions library (and see if I can get in into the containers for my teaching maybe). But say taking this out to a broader scope: Although changing the backend to a more responsive one is of course a good fix for my matplotlib problem above (we're looking right now as well at plotly for another specific application), I could imagine that the desire for a simple workflow for debouncing interact may also be highly useful for other applications: it's a pretty generic UI interaction feature that would be highly valuable for preventing over stimulating generic backend code you want to interact with. And being able to implement it without having to build that debouncing into the higher level code your wrapping which ideally does not even know it is being embedded in a UI I think could be very useful |
and maybe a final little detail: when I did get the notebook driver running in a notebook on our cloud platform inside a container, it also seemed tremendously non-responsive, even though it was acceptably reponsive when running locally. I'm not sure if this is because of the specific details of the installation in our container, or because I am interacting with it remotely via a notebook running in the US from Europe...in any case, I think that if one does not have the ambition of faster than 100 ms updates, it would be good anyway from a CPU / network traffic, etc, to be able to debounce interactions you don't want |
A couple of thoughts:
|
I've implemented a debouce decorator in the vaex-jupyter package, example usage together with interact: %matplotlib ipympl
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, ColorPicker
import vaex.jupyter
x = np.linspace(0,1,100)
fig, ax = plt.subplots()
line, = ax.plot(x, x**.5)
@vaex.jupyter.debounced(0.2)
def update_plot(p=1, color="red", flip=False):
y = x**p
line.set_ydata(1-y if flip else y)
line.set_color(color)
fig.canvas.draw()
fig.canvas.flush_events()
interact(update_plot, p=(0,1,0.01), color=ColorPicker(value="red")) vaex-jupyter is a bit of a heavy dependency though. |
Thanks @maartenbreddels, I'll definitely have a look when I get a chance! This is now some nice compact code (assuming I don't have too much trouble with vaex-jupyter) |
Good question, I'm not 100% sure which is which, I've not really even thought about the technical definition of the terms until a few days ago :) I think that what we would need is debounce, in this sense: https://davidwalsh.name/javascript-debounce-function AKA: make sure that the plot_update function is called at most once per 100 ms say, for example, regardless how many updates are registered by the slider.
Indeed, this works, but the interactivity is significantly reduced (equivalent to the "click" solution in my video), and so I'm looking for something a bit more
I agree, this should work, although it requires some boilerplate / understanding from the user perspective, or perhaps the last library that @maartenbreddels mention. But in any case, both will raise the barrier for people using it, and what I like the most about ipywidgets and interact is the low barrier to use.
I think it's a common enough feature that it would be useful to have already in ipywidgets: what I like the most myself about ipywidgets is the low barrier to simple gui programming in a notebook: with only a few lines of code, people can make already quite functional GUI interactions, particularly using |
Small update: After reading ipywidgets docs page, I think I understand now better what you mean with the difference between debouncing and throttling, and from my guess, either seems fine. I've tried copy-pasting the code over:
But this results in an infinite number of plots below each other, rather than the identical lines without the throttling. Which completely baffled me...? Why has decorating my update function with a throttle change the behaviour of the output of (I have tried a few things, the last of which is the last code snippet from this issue, but then I get flickering, whereas with the original non-decorated Thanks, |
My guess is that the interact wraps the call to update_plot with a context manager, that makes sure it gets output to the right output widget. However, the throttle call will be called some time later, not in the context of the context manager. |
OK, for anyone who is interested, I found a workaround! The solution is to code the interactivity yourself, having the sliders / controls use a throttled plot update function. The key trick to solve the flashing was to use
This now no longer builds up an infinite queue of matplotlib updates! The refresh is not "instant" / video rate, but that is fine since for me it is more important that there is a visual indication of the interactivity as the slider is moved, and that when it stops moving, it goes quickly to the correct parameter. And this work at least :) |
(This workaround aside, I still support the incorporation of throttling directly into the interact functionality, I guess it's a few lines of code) |
The sample decorator doesn't work for me. It basically results in funneling all output to the log (at level "info"). Try it for yourself:
After opening that Binder link:
To "fix" the behavior, comment out the decorator, restart the kernel, and output should be displayed inline. +1 to this being a library feature with better guidance on how/where to use it. |
Two use cases:
If this is done on the client side (js), this will also lead to less traffic/events.
I can imaging something like this
For the bqplot use case
See here for a visual explaination:
http://benalman.com/projects/jquery-throttle-debounce-plugin/
throttle and debounced functions are also available in underscore
Having them in link and js(d)link could also be useful (say when a widget would make a ajax request)
The text was updated successfully, but these errors were encountered: