Forum Archive

ImageView.load_from_url() freezes app?

lsloan117

I'm new to Pythonista's ui module and I wonder if anybody else has seen this problem:

I have a small program with a simple .pyui view file. The program loads the view and tries to put an image into the imageView object in the view:

view = ui.load_view()
imageView = view['imageview1']
imageView.load_from_url('http://i3.kym-cdn.com/photos/images/newsfeed/000/404/302/597.gif')
view.present()

This works great the first time the program is run. However, when the program is run a second time, the entire Pythonista app freezes. I need to halt Pythonista and start it again to continue.

I thought it might be a network issue, even though it only happens every second attempt. So I saved the image to a (very long) data URL encoded with Base64. Again, the program works the first time, but not the second time.

If I comment out the call to load_from_url(), the rest of the program will work fine.

I tried using an image in a much shorter URL, just in case size was the issue:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==

However, the program exhibited the same problem.

Should my program do some clean-up before it exits or before the image is loaded?

Update: Here's a simple single-file program that demonstrates the problem:

import ui
view = ui.View()
image = ui.ImageView()
image.load_from_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')
view.add_subview(image)
view.present()
ccc

Slightly simpler but still exhibits the same hang up...

import ui
image_view = ui.ImageView()
image_view.load_from_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')
image_view.present()
lsloan117

Nice. I didn't realize View() wasn't necessary. I'm glad you're able to reproduce the problem, too.

JonB

load_from_url asynchronously loads the image, according to the docs, which makes me worry about threading. Using ui.delay works without lockup.

import ui
image_view = ui.ImageView()
def load_image(): 
    image_view.load_from_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==')
image_view.present()
ui.delay(load_image,0.5)
ccc

Perfect solution! You can even change the 0.5 to 0.

lsloan117

I'll try that. I had glossed over the fact that it's asynchronous. I still have some questions:

Even if it loads the image asynchronously, why would it freeze the whole app? It seems like maybe the image would fail to display when the UI is shown instead.

Asynchronously loaded or not, any idea why it was successful the first time, but always failed the second?

If I include images in my app encoded with Base64, they don't need to be loaded asynchronously because there's no waiting for the network. How should I put those images into the UI?

JonB

This is still a bug I think, but the asynchronous part maybe helps explain why it is a problem -- I suspect there is maybe a dedicated thread or resource used for the asynchronous load, that is not getting freed or something ... so the first time works, but the second time doesn't. omz would have to weigh in. In any case, ui.delay runs the method in its own thread. I also tried ui.in_background, which didn't work in my tests, which is strange because I always thought in_background was equivalent to ui.delay(fcn,0)..

An alternate way to load images, is to create ui.Image objects. This can be done directly from files, using ui.Image.named;or you can load from a string containing the data (such as you'd get from open('picture.png').read()) using ui.Image.from_data.

These both return a ui.Image object (not to be confused with a PIL.Image object... there are a few threads here about conversion between these types).

I'd recommend just using files -- it is easier to change out the images if you want to later.
But, if you are intent on having a script which is a single .py file, for ease of distribution, then encoding images is a clever solution (though, keep the images small!).

IN that case, you'd store just the base64 file contents (not the extra url info), then when you want to load the image, decode the base64 and use the ui.Image.from_data method. Either method could also be used in conjunction with something like urllib or requests to download files from the internet dynamically (either writing the file to disk first, or using the read string).

Finally, to load a ui.Image into an ImageView, you'd use

image_view.image = ui.Image.from_data(image_binary)

(Random thought: zipping all required files, then base64 encoding that within a .py file might be a neat way to pack self-extracting py files for one-file distribution, similar to the way packui works)

lsloan117

Thanks for the info, @JonB. I hope @omz gets a chance to look into this soon.