voila icon indicating copy to clipboard operation
voila copied to clipboard

Updating view in the background with threads

Open katsar0v opened this issue 4 years ago • 16 comments

I am not sure if this is possible, but it definitely works in JupyterLab 1.x flawlessly. It does not in Voila, if there is a working way to do it, I will be happy to try it out and maybe make a PR in the documentation about it.

I have two notebooks, one with asyncio, one with threading.

Asyncio

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import time
import asyncio
import random
from IPython.display import display, clear_output
%matplotlib inline

fig = plt.figure()
ax = plt.axes()
x = np.linspace(0, random.randint(5,10), random.randint(100,1000))
plt.plot(x, np.sin(x))
plt.show()

async def do():
    while True:
        await asyncio.sleep(0.5)
        clear_output()
        fig = plt.figure()
        ax = plt.axes()
        x = np.linspace(0, random.randint(5,10), random.randint(100,1000))
        plt.plot(x, np.sin(x))
        display(plt.gcf())
        
    
print("Starting async thread")
asyncio.create_task(do())

Threading

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import time
import threading
import random
from IPython.display import display, clear_output
%matplotlib inline

fig = plt.figure()
ax = plt.axes()
x = np.linspace(0, random.randint(5,10), random.randint(100,1000))
plt.plot(x, np.sin(x))
plt.show()

def do():
    while True:
        time.sleep(0.5)
        clear_output()
        fig = plt.figure()
        ax = plt.axes()
        x = np.linspace(0, random.randint(5,10), random.randint(100,1000))
        plt.plot(x, np.sin(x))
        display(plt.gcf())
        
    
print("Starting async thread")
t = threading.Thread(target=do)
t.start()

Here I have preview (with gifs) what happens:

JupyterLab with asyncio:

jupyterlab_asyncio

JupyterLab with threading:

jupyterlab_threading

JupyterLab and voila with asyncio:

with_voila_asyncio

JupyterLab and voila with threading:

with_voila_threading

I would like to make an example in the Voila Gallery about a self-updating dashboard, but I need the threads working for this. If you could help me out with this, I will be really thankful.

PS: I tried with %matplotlib widget and %matplotlib ipympl, they did not refresh even in JupyterLab.

katsar0v avatar Oct 15 '19 16:10 katsar0v

Hi Kristiyan,

could you try if you get this to work with: https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html Also, clear_output(wait=True) helps reducing flicker.

cheers,

Maarten

maartenbreddels avatar Oct 15 '19 16:10 maartenbreddels

Hi Kristiyan,

could you try if you get this to work with: https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html Also, clear_output(wait=True) helps reducing flicker.

cheers,

Maarten

Could you maybe give me an example?

Tried:


import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

Which works in JupyterLab but not in Voila :)

Edit: it did not work in the JupyterLab preview, but worked running voila Untitled.ipynb. The examples I gave above do not work with voila Untitled.ipynb in the console.

PS: Another example that works in JupyterLab but not in Voila:

%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import ipywidgets as widgets
from IPython.display import display, clear_output
import numpy as np
import threading
import time
import random

out = widgets.Output()
def do(out):
    while True:
        display(out)
        data1 = pd.DataFrame(np.random.normal(size = random.randint(50,5000)))
        fig1, axes1 = plt.subplots()
        data1.hist(ax = axes1)
        plt.show(fig1)
        time.sleep(0.5)
        clear_output()
        out = widgets.Output()

    
with out:
    t = threading.Thread(target=do, args=(out,))
    t.start()

katsar0v avatar Oct 15 '19 18:10 katsar0v

@katsar0v how about something like this (using Python 3.7+):

dashboard-refresh

import asyncio

%matplotlib widget
import ipympl

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import random

SLEEP = 1

all_tasks = asyncio.all_tasks()
for task in all_tasks:
    task.cancel()


fig1, axes1 = plt.subplots()
plt.show(fig1)

async def do():
    while True:
        data1 = pd.DataFrame(np.random.normal(size = random.randint(50,5000)))
        axes1.cla()
        data1.hist(ax = axes1)
        plt.draw()
        await asyncio.sleep(SLEEP)

asyncio.create_task(do());

jtpio avatar Oct 15 '19 20:10 jtpio

That's great! Thanks @jtpio

It works with your code :) you used plt.draw() which works in voila, but plt.show() and clear_output() do not. What was the mistake I made and what can we add in the docs to avoid this in the future?

Also why display(widgets.Output()) together with clear_output() had no effect. I might be wrong becayse I do not understand the ipython way of visualizing things, but I think clear_output() does not work?

Still I'm going to make a repo with an interactive dashboard to use as a demo and boilerplate, so others do not struggle with these display problems like me :)

One last question @jtpio: currently it works with this plot, what if I want to make it with a widget (tabs or something like this) or just want to display a new YouTube video every 3 seconds?

katsar0v avatar Oct 16 '19 05:10 katsar0v

Also why display(widgets.Output()) together with clear_output() had no effect. I might be wrong becayse I do not understand the ipython way of visualizing things, but I think clear_output() does not work?

out.clear_output() should work with the Output widget.

what if I want to make it with a widget (tabs or something like this) or just want to display a new YouTube video every 3 seconds?

ipywidgets has a Tab widget: https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html#Tabs, which can be updated in the do function used in the examples above.

Also you might want to consider using bqplot and its Figure widget (lots of example on Binder)

jtpio avatar Oct 16 '19 10:10 jtpio

Tried:


import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

Which works in JupyterLab but not in Voila :)

This works for me, which version are you using?

maartenbreddels avatar Oct 16 '19 13:10 maartenbreddels

Tried:


import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

Which works in JupyterLab but not in Voila :)

This works for me, which version are you using?

This one works, sorry for the confusion, tested again. It seems clear_output() and other lines in the examples (gifs) I posted above do not work in Voila.

katsar0v avatar Oct 16 '19 14:10 katsar0v

@katsar0v do you still want to keep this issue open for further discussions, now that your self-updating example is working?

jtpio avatar Nov 04 '19 08:11 jtpio

We might close this issue, although it is not clear why it is not working with normal threads and why the code I posted above does not work

katsar0v avatar Nov 12 '19 15:11 katsar0v

I can get the append-only example to work, but if I try to clear the output, it doens't work!?

import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.clear_output() # <<< THIS DOES NOTHING
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

anjackson avatar Mar 04 '20 12:03 anjackson

Hello!

I did thread, where plot updated every 1 second. In Jupyter all right. But in voila, I haven't updated plot.

Code below:

def learning_curves(stop_button):

        metrics_graphic_buffer.append(f_score[0])
        metrics_graphic_buffer1.append(f_score[1])
        metrics_graphic_buffer2.append(f_score[2])

        stop_button.on_click(stoping)
        
        with output_learning_curves:
            clear_output()
    #             print("Metrics graphic buffer", metrics_graphic_buffer)
            plt.figure(figsize=(12, 12))
            plt.axis([0, 20, 0, 1.1])
            plt.plot(metrics_graphic_buffer, 'r--', label = 'Tap', 
                     linewidth = 1.0)
            plt.plot(metrics_graphic_buffer1, 'g--', label = 'Toilet', 
                     linewidth = 3.0)
            plt.plot(metrics_graphic_buffer2, 'y--', label = 'Shower', 
                     linewidth = 5.0)
            plt.plot(avr, 'black', label = 'Average f-score', 
                     linewidth = 4.0)
            plt.legend(loc='upper right', prop={'size': 12})
            plt.draw()
            time.sleep(1)

        if flag == False:
            print ('Status flag1', flag)
            break

Initial thread : thread = threading.Thread(target=learning_curves, args=(stop_button, ))

Start thread :

def on_clicked_Learn_button(state):
    with output_learning_curves:
        thread.start()

Thanx for your help !

Sedom9 avatar Oct 01 '20 12:10 Sedom9

Tried:


import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

Which works in JupyterLab but not in Voila :)

This works for me, which version are you using?

This one works, sorry for the confusion, tested again. It seems clear_output() and other lines in the examples (gifs) I posted above do not work in Voila.

seems ipw.HTML can work as a replacement:

def ipwHTMLstr(df, max_rows=20, max_cols=20):
    return '<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output">%s</div>'%df.to_html(escape=True, max_rows=max_rows, max_cols=max_cols)

# in thread foo:
ht.value=ipwHTMLstr(df)

cailiang9 avatar Apr 24 '21 17:04 cailiang9

setting out.output=[] can fix out.clear_output not working issue. see github for reference: https://github.com/jupyter-widgets/ipywidgets/blob/master/ipywidgets/widgets/widget_output.py#L159

I can get the append-only example to work, but if I try to clear the output, it doens't work!?

import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time

def thread_func(something, out):
    for i in range(1, 5):
        time.sleep(0.3)
        out.clear_output() # <<< THIS DOES NOTHING
        out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
    out.append_display_data(HTML("<em>All done!</em>"))

display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)

thread = threading.Thread(
    target=thread_func,
    args=("some text", out))
thread.start()

cailiang9 avatar Apr 24 '21 17:04 cailiang9

Encountered this problem again in #1341.

rht avatar Jun 27 '23 07:06 rht

Encountered this problem again in #1341.

@rht did you ever get this to work?

richy1996 avatar Feb 11 '24 18:02 richy1996

Yes, I did: https://github.com/voila-dashboards/voila/issues/1341#issuecomment-1608976452. Though we (Mesa) has since been using Solara instead.

rht avatar Feb 11 '24 22:02 rht