Octoframes/jupyter_compare_view

Error rendering splitview images

psychemedia opened this issue · 11 comments

Hi

This is a really nice idea for an extension...

However, I'm getting an error in the rendering of the compared images when using the magic (they render fine otherwise):

image

Chrome browser, current version of JupyterLab.

Thanks for the report!
What operating system are you on?
I've done the development on MAC OS, but I think this package should be platform independent.
Here is my suggestion:

  1. Run once
pip install jupyter-splitview --upgrade

I've updated to version 0.0.4 just now, maybe the error will disappear there.

  1. Can you check your internet connection?
    When executing the widget, third party JavaScript is loaded from https://github.com/NUKnightLab/juxtapose, so maybe there was an error there.

  2. Can you try this widget once in firefox or any other browser than chrome?
    I've just tested this widget in Chrome and Firefox and Safari, and all works well.

And can you run these two cells ones? They are the minimal implementation of the widget core.
The output should look like this:
image

from IPython.core.display import HTML
from IPython.display import display

html_code = f"""
<div class="outer_layer" style="position: relative; padding-top: 300px;">
    <div class="juxtapose" data-startingposition="35%" style="height: 400px; width: 400px; top: 1%; left: 1%; position: absolute;">
        <img src="https://raw.githubusercontent.com/scikit-image/scikit-image/main/skimage/data/chelsea.png" />' <img src="https://raw.githubusercontent.com/scikit-image/scikit-image/main/skimage/data/coins.png" />'
    </div>
</div>
"""
display(HTML(html_code))
from IPython.core.display import HTML
from IPython.display import display

html_code = f"""
<div class="outer_layer" style="position: relative; padding-top: 300px;">
    <div class="juxtapose" data-startingposition="35%" style="height: 400px; width: 400px; top: 1%; left: 1%; position: absolute;">
        <img src="https://raw.githubusercontent.com/scikit-image/scikit-image/main/skimage/data/chelsea.png" />' <img src="https://raw.githubusercontent.com/scikit-image/scikit-image/main/skimage/data/coins.png" />'
    </div>
</div>
<script src="https://cdn.knightlab.com/libs/juxtapose/latest/js/juxtapose.min.js"></script>
<link rel="stylesheet" href="https://cdn.knightlab.com/libs/juxtapose/latest/css/juxtapose.css" />
"""
display(HTML(html_code))

Those both render okay.

And now when I run the split view code, I actually get an error?

UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7fe528331720>
---------------------------------------------------------------------------
UnidentifiedImageError                    Traceback (most recent call last)
/tmp/ipykernel_801/2472879159.py in <module>
----> 1 get_ipython().run_cell_magic('splity', '--position 73% --height auto', 'import matplotlib.pyplot as plt\nimport numpy as np\n\narray1 = np.full((15, 30), 10)\narray2 = np.random.randint(0, 10, size=(15, 30))\nfig, ax1 = plt.subplots(figsize=(5, 10))\nax1.imshow(array1)\nfig, ax2 = plt.subplots(figsize=(5, 10))\nax2.imshow(array2)\n')

/usr/local/lib/python3.8/dist-packages/IPython/core/interactiveshell.py in run_cell_magic(self, magic_name, line, cell)
   2401             with self.builtin_trap:
   2402                 args = (magic_arg_s, cell)
-> 2403                 result = fn(*args, **kwargs)
   2404             return result
   2405 

/usr/local/lib/python3.8/dist-packages/decorator.py in fun(*args, **kw)
    230             if not kwsyntax:
    231                 args, kw = fix(args, kw, sig)
--> 232             return caller(func, *(extras + args), **kw)
    233     fun.__name__ = func.__name__
    234     fun.__doc__ = func.__doc__

/usr/local/lib/python3.8/dist-packages/IPython/core/magic.py in <lambda>(f, *a, **k)
    185     # but it's overkill for just that one bit of state.
    186     def magic_deco(arg):
--> 187         call = lambda f, *a, **k: f(*a, **k)
    188 
    189         if callable(arg):

~/.local/lib/python3.8/site-packages/jupyter_splitview/sw_cellmagic.py in splity(self, line, cell)
     53             imgdata = b64decode(out_images_base64[0])
     54             # maybe possible without the PIL dependency?
---> 55             im = Image.open(io.BytesIO(imgdata))
     56             width, height = im.size
     57             widget_height = height

~/.local/lib/python3.8/site-packages/PIL/Image.py in open(fp, mode, formats)
   3121     for message in accept_warnings:
   3122         warnings.warn(message)
-> 3123     raise UnidentifiedImageError(
   3124         "cannot identify image file %r" % (filename if filename else fp)
   3125     )

UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7fe528331720>

Seems like the images are not captured the right way.
Can you once run this in the first cell:

import io
from base64 import b64decode

from IPython.core import magic_arguments
from IPython.core.display import HTML
from IPython.core.magic import Magics, cell_magic, magics_class
from IPython.display import display
from IPython.utils.capture import capture_output
from PIL import Image


@magics_class
class SplitViewMagic(Magics):
    @magic_arguments.magic_arguments()
    @magic_arguments.argument(
        "--position",
        "-p",
        default="50%",
        help=("The position where the slider starts"),
    )
    @magic_arguments.argument(
        "--height",
        "-h",
        default="300",
        help=(
            "The height that the widget has. The width will be adjusted automatically. \
             If height is choosen 'auto', the height will be defined by the resolution \
             in vertical direction of the first image."
        ),
    )
    @cell_magic
    def splity(self, line, cell):
        """Saves the png image and calls the splitview canvas"""

        with capture_output(stdout=False, stderr=False, display=True) as result:
            self.shell.run_cell(cell)

        # saves all jupyter output images into the out_images_base64 list
        out_images_base64 = []
        print(f"{out_images_base64 = }")
        for output in result.outputs:
            data = output.data
            print(f"{type(data) = }")
            print(f"{type(list(data.values())[0]) = }")

            if "image/png" in data:
                png_bytes_data = data["image/png"]
                out_images_base64.append(png_bytes_data)
        
        print(f"{len(out_images_base64) = }")

        # get the parameters the configure the widget
        args = magic_arguments.parse_argstring(SplitViewMagic.splity, line)

        slider_position = args.position
        widget_height = args.height

        if widget_height == "auto":
            imgdata = b64decode(out_images_base64[0])
            # maybe possible without the PIL dependency?
            im = Image.open(io.BytesIO(imgdata))
            width, height = im.size
            widget_height = height

        html_code = f"""
        <div class="outer_layer" style="position: relative; padding-top: {int(widget_height)+3}px;"> 
            <div class="juxtapose" data-startingposition="{slider_position}" style="height: {int(widget_height)}px;; width: auto; top: 1%; left: 1%; position: absolute;">
                <img src="data:image/jpeg;base64,{out_images_base64[0]}" />' <img src="data:image/jpeg;base64,{out_images_base64[1]}" />'
            </div>
        </div>
        <script src="https://cdn.knightlab.com/libs/juxtapose/latest/js/juxtapose.min.js"></script>
        <link rel="stylesheet" href="https://cdn.knightlab.com/libs/juxtapose/latest/css/juxtapose.css" />
        """
        display(HTML(html_code))
        
from IPython import get_ipython  # register cell magic

ipy = get_ipython()
ipy.register_magics(SplitViewMagic)

and this in the second cell?

%%splity --position 73% 
import matplotlib.pyplot as plt
import numpy as np

array1 = np.full((15, 30), 10)
array2 = np.random.randint(0, 10, size=(15, 30))
fig, ax1 = plt.subplots(figsize=(5, 10))
ax1.imshow(array1)
fig, ax2 = plt.subplots(figsize=(5, 10))
ax2.imshow(array2)

Output should be:

out_images_base64 = []
type(data) = <class 'dict'>
type(list(data.values())[0]) = <class 'str'>
type(data) = <class 'dict'>
type(list(data.values())[0]) = <class 'str'>
type(data) = <class 'dict'>
type(list(data.values())[0]) = <class 'str'>
len(out_images_base64) = 2

Yep - matches on those.

As the rendered HTML cell output I get:

<div class="lm-Widget p-Widget jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output" data-mime-type="text/html">
        <div class="outer_layer" style="position: relative; padding-top: 303px;"> 
            <div class="juxtapose juxtapose-2" data-startingposition="73%" style="height: 300px;; width: auto; top: 1%; left: 1%; position: absolute;"></div>
        </div>
        <script src="https://cdn.knightlab.com/libs/juxtapose/latest/js/juxtapose.min.js"></script>
        <link rel="stylesheet" href="https://cdn.knightlab.com/libs/juxtapose/latest/css/juxtapose.css">
</div>

(Apols for not helping debug this more; I'm rushing about all w/e... Will try to get a look at it next week. It seems the image tag appears briefly as a broken image then disappears, so I guess the encoding/data URI bit is mangled somehow?!)

Does it work in MyBinder?

Does it work in MyBinder?

I have not tried it there, but feel free to give it a shot!
If it works, a pull request with a binder badge in the README is very welcome.
I've run the widget on multiple machines, and I've not run into an error, so it's not possible for me to reproduce your problem.
I think with the combination of the above scripts + some Stack Overflow research, you have good potential to solve this issue and in case that it can be solved, feel very welcome to make a pull request with the fix as well.

Any updates on this?

@psychemedia : As you can see here https://github.com/kolibril13/jupyter-splitview#005 there was a lot of activity recently on this repo, the whole project infrastructure got refactored, so I think it's worth once testing the newest version of this package by pip install jupyter-splitview --upgrade and see if your error still occurs.

Okay- thanks.

[UPDATE: works fine now; thanks...]

I am glad to hear that!
In case that you have some time and motivation, would you like to contribute a binder example? :)