Search code examples
pythonimagepython-imaging-libraryjupyter-labipywidgets

PIL Image in a ipywidgets Container widget


I have some PIL images coming from some library and am trying to understand what is going on with them. To do that, I compute some values, call the library, and look for the picture. The computed values are of interest to me. I use a Jupyter notebook with some sliders to interact with the library.

Here is a very brief example of my code:

    from PIL import Image, ImageDraw
    import ipywidgets
    
    def explore_call(width, height, foo, bar):
        # some very important computation
        essential_value_one = width - foo
        essential_value_two = height - bar
        # output interim results
        print(f"{essential_value_one=}, {essential_value_two=}")
        # mimic PIL image return from a library
        image = Image.new(mode="RGB", size=(width, height), color="black")  
        draw = ImageDraw.Draw(image)
        draw.rectangle(xy=(foo, bar, essential_value_one, essential_value_two))
    
        return image
    
    ipywidgets.interact(explore_call,
                        width=ipywidgets.IntSlider(min=10, max=200, step=1, value=100),
                        height=ipywidgets.IntSlider(min=10, max=200, step=1, value=100),
                        foo=ipywidgets.IntSlider(min=0, max=20, step=1, value=10),
                        bar=ipywidgets.IntSlider(min=0, max=20, step=1, value=10),
                       )

That is semi-okay. Jupyter Notebook displays the returned image, but I wish to put it inside an ipywidgets container widget, replace the prints with some other widget, and do some basic layout. I found an ipywidgets.Image but couldn't instantiate it properly (I tried to save the PIL image into an io.StringIO buffer and then pass the value of it into ipywidgets.Image, but without success). So, how do I wrap a PIL image in a widget?


Solution

  • Well you have asked too much in single question, but according to my understanding your main problem is to display the image as a jupyter widget and not simple image to make it compaitible with other jupyter widgets. So according to your following statement

    (I tried to save the PIL image into a io.StringIO buffer and then pass the value of it into ipywidgets.Image, but without success)

    Its not going to work as the correct way is to convert the PIL image into bytearray/bytes and then pass the bytes to the ipywidgets.Image. e.g. check modified version of your function below

    from PIL import Image, ImageDraw
    from io import BytesIO
    import ipywidgets
    
    def explore_call(width, height, foo, bar):
        # some very important computation
        essential_value_one = width - foo
        essential_value_two = height - bar
        # output interim results
        print(f"{essential_value_one=}, {essential_value_two=}")
        # mimic PIL image return from a library
        image = Image.new(mode="RGB", size=(width, height), color="black")  
        draw = ImageDraw.Draw(image)
        draw.rectangle(xy=(foo, bar, essential_value_one, essential_value_two))
    
        imgBytes = BytesIO()
        image.save(imgBytes, format="PNG")
        
        return ipywidgets.Image(value=imgBytes.getvalue())