Forum Archive

how to update ui.Label display during computation?

jmv38

as the title says, i update a ui.Label during a loop computation, to see progress, but the label display is updated only at the end... so it doesnt show progress.

i have tried to implement a subclass of ui.view with an update(), but i failed (nothing appears any more, probably my bad)

I vaguely remember reading things about decorators and main thread, but i have no experience with this.

Any help? Thanks.

cvp

@jmv38 could you post the code?

cvp

You could try to put this line before the function here you perform your loop

@on_main_thread 
jmv38

@cvp thank you, but it doesnt solve it

i have a class with

class Neural_Network(object):
  @on_main_thread 
  def trainAll(self, iterations):
    for i in range(iterations):
      finalLoss = self.lossArray[i]
      showLearning(i,finalLoss)

showLearning is

def showLearning(i,v):
    c = 'lightgreen'
    trainInfo.bg_color = c
    txt = 'step {:d} : {:5.2f}%'.format(i, int(10000*float(1-v))/100)
    trainInfo.text = txt

trainInfo is a ui.Label defined globally, thati would like to update after each i cycle

cvp

@jmv38 please, try something like

def ui_label():
    global global_txt
    trainInfo.text = global_txt

def showLearning(i,v):
    global global_txt
    c = 'lightgreen'
    trainInfo.bg_color = c
    global_txt = 'step {:d} : {:5.2f}%'.format(i, int(10000*float(1-v))/100)
    ui.delay(ui_label,0.01)
jmv38

@cvp thank you
i tried your code, but it doesnt change: no intermediate update.

cvp

@jmv38 try this one, I can't test because I don't have entire code

import ui
import threading

class my_thread(threading.Thread):
    global trainInfo
    def __init__(self, iterations, lossArray):
        threading.Thread.__init__(self)
        self.iterations = iterations
        self.lossArray = lossArray # <============ was erroneous, Sorry 
    def run(self):
        for i in range(self.iterations):
            finalLoss = self.lossArray[i]
            self.showLearning(i,finalLoss)
    def showLearning(self,i,v):
        c = 'lightgreen'
        trainInfo.bg_color = c
        txt = 'step {:d} : {:5.2f}%'.format(i, int(10000*float(1-v))/100)
        trainInfo.text = txt

class Neural_Network(object):
    def trainAll(self, iterations):
        neural_thread = my_thread(iterations, self.lossArray)
        neural_thread.start()
cvp

This works

import ui
import threading

class my_thread(threading.Thread):
    global trainInfo
    def __init__(self, iterations, lossArray):
        threading.Thread.__init__(self)
        self.iterations = iterations
        self.lossArray = lossArray
    def run(self):
        for i in range(self.iterations):
            finalLoss = self.lossArray[i]
            self.showLearning(i,finalLoss)
    def showLearning(self,i,v):
        c = 'lightgreen'
        trainInfo.bg_color = c
        txt = 'step {:d} : {:5.2f}%'.format(i, int(10000*float(1-v))/100)
        trainInfo.text = txt

class Neural_Network(object):
    def __init__(self):
        self.lossArray = [1]*1000
        self.trainAll(100)
    def trainAll(self, iterations):
        neural_thread = my_thread(iterations, self.lossArray)
        neural_thread.start()

v =ui.View()
v.frame = (0,0,500,500)

trainInfo = ui.Label()
trainInfo.frame = (10,10,200,32)
v.add_subview(trainInfo)

v.present('sheet')

x = Neural_Network()
jmv38

@cvp thank you
i have checked your code: works correctly!
i have to think a little bit before i can include it into my project.
Because it is a bit tricky and i dont want it to come in the way when i do my future versions...
Strange that there is no simpler way to do this simple thing...
Thanks!

cvp

@jmv38 I'm sure there is an easier way with the right decorators at the right place...
Perhaps, @jonb could help but I think he would need more of your code to know in which thread is each def like trainall...

JonB

You could probably use UI.in_background around your loop. That is what it is for.

You didn't show the rest of your context-- does your loop get called from a UI event like a button action? The key to remember -- NOTHING is updated in the UI until your button action (or touch moved, etc ) until your action returns. UI.in_background allows your button to return, queueing up the work on a shared background thread.

cvp

@JonB Could we say that a thread like I did is almost like ui_in_background thread for what concerns UI update?

jmv38

@JonB the code is not ready, so i was reluctant to share it...
but if you want here is a gist:
https://gist.github.com/3acaab3b4b1556abbdbd3f5483d9d88e
draw in the 6 skech boxes, press ‘train’: training last 30s, and at the end the label is updated. I would like it to update live. (it is actually updated at each step)
Thanks.

cvp

@jmv38 I've tried your code with

import objc_util
.
.
.
@ui.in_background
def showLearning(i,v):
jmv38

@cvp thank you
it seems to work but i have doubts: it seems that the whole code executes, without the ui.label updating, then all the updates occur at the end. As if they are buffered somewhere, then all the updates occur very quickly at once.
So i dont think it really works.

cvp

@jmv38 you're right, sorry

jmv38

@cvp don’t be sorry, thank you for trying to help!
if you have any other idea, you are welcome.

jmv38

hello
i managed to get it: I launched my function and my loop steps with a ui.delay() call. So my ui.button actions can return and the ui.label can be updated (i followed JonB directions). And then it works.
I will post the code in the neural networks thread.
here is the idea:

def trainAll(self, iterations= None):
    if iterations:
      self.iterations = iterations
      self.lossArray = []
    loss = np.mean(np.square(y - NN.forward(X)))
    if self.iterations > 0:
      self.lossArray.append(loss)
      self.train(X, y)
      showLearning(len(self.lossArray),loss)
      self.iterations-=1
      ui.delay(self.trainAll, 0.1)
JonB

yeah, ui.in_background would work, but you might need to wrap the actual label setting in a on_main_thread, or perhaps call set_needs_display.

the delay allows the callback to return, but then runs the other bit on the main thread, so is also good -- though no other ui will be allowed, which maybe is a good thing in this case.

See this thread for another option -- you can define a decorator that runs a method in a nee python thread, so the convienece of a decorator but without the drawback of reponsivity issues.
https://forum.omz-software.com/topic/3495/label-text-not-displayed-until-end-of-button-action/5