Skip to content
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

Progress bar tqdm wrapper, and manual control #105

Merged
merged 29 commits into from
Jan 24, 2021

Conversation

tlambert03
Copy link
Member

this PR adds a basic tqdm_mgui iterator that updates a progress bar (from #104), with the internal state of the tqdm iterator. allowing iterator-based progress bars ( a step towards #25)

for i in tqdm_mgui(range(100)):
    time.sleep(0.1)
tqdm.mov

@codecov
Copy link

codecov bot commented Jan 17, 2021

Codecov Report

Merging #105 (79093ac) into master (8855f45) will increase coverage by 0.26%.
The diff coverage is 94.04%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #105      +/-   ##
==========================================
+ Coverage   90.51%   90.78%   +0.26%     
==========================================
  Files          25       26       +1     
  Lines        2435     2517      +82     
==========================================
+ Hits         2204     2285      +81     
- Misses        231      232       +1     
Impacted Files Coverage Δ
magicgui/widgets/_concrete.py 82.15% <70.00%> (-0.47%) ⬇️
magicgui/tqdm.py 96.96% <96.96%> (ø)
magicgui/type_map.py 89.67% <100.00%> (+0.13%) ⬆️
magicgui/types.py 100.00% <100.00%> (ø)
magicgui/widgets/_bases/value_widget.py 97.82% <100.00%> (+0.04%) ⬆️
magicgui/widgets/_function_gui.py 96.59% <100.00%> (+0.04%) ⬆️
magicgui/backends/_qtpy/widgets.py 85.19% <0.00%> (-0.26%) ⬇️
magicgui/widgets/_bases/ranged_widget.py 84.26% <0.00%> (+2.24%) ⬆️
magicgui/backends/_qtpy/application.py 97.22% <0.00%> (+8.33%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8855f45...709f07a. Read the comment docs.

@tlambert03
Copy link
Member Author

this is getting kinda cool!
If you use tqdm_mgui (or the convenience tmgrange shortcut) inside of a @magicgui-decorated function, it will add the progressbar to the gui. It also supports nested progressbars. For instance:

import random
from time import sleep
from magicgui import magicgui
from magicgui.tqdm import tmgrange, tqdm_mgui

@magicgui(call_button=True, layout="vertical")
def long_function(
    steps=20, repeats=4, choices="ABCDEFGHIJKLMNOP12345679", char="", delay=0.1
):
    # using the `tmgrange` shortcut instead of range()
    for r in tmgrange(repeats, label="repeats"):
        letters = [random.choice(choices) for _ in range(steps)]
        # `tqdm_gui`, like `tqdm`, accepts any iterable
        # this progress bar is nested and will be run & reset multiple times
        for letter in tqdm_mgui(letters, label="steps"):
            long_function.char.value = letter
            sleep(delay)

long_function.show(run=True)
Untitled.mov

@tlambert03
Copy link
Member Author

cc @GenevieveBuckley

@tlambert03 tlambert03 changed the title tqdm wrapper Progress bar tqdm wrapper, and manual control Jan 17, 2021
@tlambert03
Copy link
Member Author

progress bar can now also be controlled by annotating a parameter as magicgui.widgets.ProgressBar:

@magicgui(call_button="tick", pbar={"min": 0, "step": 2, "max": 20, "value": 0})
def manual(pbar: ProgressBar, increment: bool = True):
    """Example of manual progress bar control."""
    if increment:
		# can also use pbar.value, pbar.min, pbar.max, etc...
        pbar.increment()
    else:
        pbar.decrement()

@tlambert03 tlambert03 marked this pull request as ready for review January 17, 2021 19:25
@sofroniewn
Copy link
Contributor

Wow, this is looking really cool!! I can give a proper review later, but overall the functionality looks great. I'll need to do a little investigation to understand when I'd use tqdm_gui, tmgrange, and magicgui.widgets.ProgressBar. I've not used tqdm itself much, so maybe that's why it's not immediately obvious to me, but I'm excited to give this a go!

Copy link
Contributor

@sofroniewn sofroniewn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is looking good i left some comments/ questions, most are for my clarification/ naming ideas that could be ignored - probably the only thing i see as critical is enable/ disable idea for fixing the segfault after clicking run twice.

examples/progress.py Outdated Show resolved Hide resolved
sleep(delay)


long_running.show(run=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that if I click the run button while the thing is running i get a seg fault and quit - we should probably just disable the button while the loop starts and then enable it when done, possibly with a context manager so that it re-enables even with an error during the iteration

(base) czirwc1macos2701:magicgui nsofroniew$ python examples/progress.py
Traceback (most recent call last):
  File "/Users/nsofroniew/GitHub/magicgui/magicgui/widgets/_bases/value_widget.py", line 44, in <lambda>
    lambda *x: self.changed(value=x[0] if x else None)
  File "/Users/nsofroniew/GitHub/magicgui/magicgui/events.py", line 576, in __call__
    raise RuntimeError("EventEmitter loop detected!")
RuntimeError: EventEmitter loop detected!
Abort trap: 6

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I noticed that too. That's a larger issue having to do with long running functions that doesn't have to do with this PR ... will make a new issue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that issue might need to get fixed first, but thinking about it more I think i'd like the behavior to toggle run/ pause which would start and stop progress. could come in later PRs, but noting it now

@@ -0,0 +1,151 @@
"""A wrapper around the tqdm.tqdm iterator that adds a ProgressBar to a magicgui."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering if there is another place this could live that is not at the top level, i.e. a grouping of a larger thing that it might be an example of? I guess though there isn't anything else quite like in magicgui right now as it's not a widget per se (that is ProgressBar itself, this is the iterator) but something that if found inside magic functions with generate a widget?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess though there isn't anything else quite like in magicgui right now as it's not a widget per se

exactly. this is just the iterator magic. the main reason I put it at the top level is that I don't want to actually depend on tqdm directly (it's available as magicgui[tqdm]), and I want people to use from magicgui.tqdm import tqdm.... As you point out, you can still use the manual progressbar API without this (magicgui.widgets.ProgresBar)

magicgui/tqdm.py Show resolved Hide resolved
magicgui/tqdm.py Show resolved Hide resolved
examples/progress.py Show resolved Hide resolved
magicgui/tqdm.py Outdated
}


class tqdm_mgui(tqdm):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why not just have this class be called tqdm, to use it you're importing from magicgui import tqdm already, especially if this is a drop in replacement for tqdm / behavior is identical if not inside a magicgui decorated function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it's not yet a drop-in replacement :/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok now it is! I liked this idea... so now it's just from magicgui.tqdm import tqdm, trange ... and if it's not inside of a @magicgui it will work as usual (like in IPython)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love it so much!!

magicgui/tqdm.py Outdated
self._mgui._tqdm_depth -= 1


def tmgrange(*args, **kwargs) -> tqdm_mgui:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is analogous to trange, might be nice to link to that https://tqdm.github.io/docs/tqdm/#trange somehow. I'm also wondering if the above import were to change to from magicgui import tqdm would this be from magicgui import trange, maybe not, that's a little awkward?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above about not depending directly on tqdm. I wanted the import error to be raised on accessing magicgui.tqdm

Copy link
Member Author

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot!

examples/progress.py Outdated Show resolved Hide resolved
examples/progress.py Show resolved Hide resolved
sleep(delay)


long_running.show(run=True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I noticed that too. That's a larger issue having to do with long running functions that doesn't have to do with this PR ... will make a new issue

@@ -0,0 +1,151 @@
"""A wrapper around the tqdm.tqdm iterator that adds a ProgressBar to a magicgui."""
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess though there isn't anything else quite like in magicgui right now as it's not a widget per se

exactly. this is just the iterator magic. the main reason I put it at the top level is that I don't want to actually depend on tqdm directly (it's available as magicgui[tqdm]), and I want people to use from magicgui.tqdm import tqdm.... As you point out, you can still use the manual progressbar API without this (magicgui.widgets.ProgresBar)

magicgui/tqdm.py Outdated
}


class tqdm_mgui(tqdm):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it's not yet a drop-in replacement :/

magicgui/tqdm.py Outdated
self._mgui._tqdm_depth -= 1


def tmgrange(*args, **kwargs) -> tqdm_mgui:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above about not depending directly on tqdm. I wanted the import error to be raised on accessing magicgui.tqdm

Copy link
Contributor

@sofroniewn sofroniewn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I love it - looks great to me. I made an additional comment on the run/ pause toggle, but sounds like that might have to come in a new PR. I think that's fine, but depending on how much work it is might try and get it in before the next release too. I find it very tempting to click on the run button again and imagine others will too :-)

Copy link
Contributor

@jni jni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tlambert03 pretty happy with this. I'm not 100% about "overwriting" the tqdm name, but I see the argument in favour, so let's give it a go.

Overall, I was kind of envisioning a different, more magical API using generators. Half baked idea:

@magicgui(
    progress=[argname],  # where to get the total length
)
def generator(argname : List):
    for elem in list:
        process(elem)
        yield
    return

There's every chance this current API is better though; For one, my suggestion can't do nested iterations, or resetting, or any of that. But, maybe it could be extended (multiple args, yielded values). At any rate, these are all things that can be explored in the future.

@tlambert03
Copy link
Member Author

Overall, I was kind of envisioning a different, more magical API using generators. Half baked idea:

(as discussed on zulip), I like this idea too, and agree that it isn't mutually exclusive. I like that the current pattern lets someone simply wrap the iterator itself (i.e. it doesn't require you to specify the parameters of the iterable in two places), but I also like thinking about what we could do with generators. so I'll merge this, and keep thinking about that as a complementary pattern

@tlambert03 tlambert03 merged commit d13a829 into pyapp-kit:master Jan 24, 2021
@tlambert03 tlambert03 deleted the iterator-progress branch January 24, 2021 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants