ipyleaflet icon indicating copy to clipboard operation
ipyleaflet copied to clipboard

jupyterlab fails in rendering ImageOverlay from file

Open epifanio opened this issue 7 years ago • 12 comments

Trying to load a jpg image in ipyleaflet using ImageOverlay I got different behaviour between the standard notebook interface and jupyterlab.

The following code work in a standard notebook but fail to render the image (404 error) in jupyterlab.

from ipyleaflet import (
    Map, ImageOverlay, TileLayer
)

m = Map(default_tiles=TileLayer(opacity=1.0), center=meta['C'], zoom=12)

meta = {'C': (41.066192631345636, -68.91168330924846),
        'LL': (41.06314714201035, -68.93226141787825),
        'UR': (41.06923444219005, -68.89110330910114),
        'proj': '+proj=utm +zone=19 +datum=WGS84 +units=m +no_defs ',
        'raster': 'test.jpg'}

layer = ImageOverlay(url=meta['raster'], 
                     bounds=(meta['LL'], meta['UR']))
m.add_layer(layer)

This notebook may help to reproduce the issue.

If using a an url pointing to a fileonline, in jupyterlab, the image is rendered correctly.

epifanio avatar Feb 20 '18 00:02 epifanio

Seems to be the same issue like in https://github.com/jupyter-widgets/ipyleaflet/issues/347.

deeplook avatar May 09 '19 13:05 deeplook

@SylvainCorlay from https://github.com/jupyter-widgets/ipywidgets/pull/1993#issuecomment-371576976 seems there is a way to have this working. I tried with a recent version of pip-installed jupyter, jupyterlab, ipywidgets, ipyleaflet but the 404 error persists. Do you have any hints on how to get this fixed?

epifanio avatar Apr 12 '20 20:04 epifanio

I have the same issue, I can load files in the notebooks so it is not the same as #347. Only ImageOverlay of local images with ipyleaflet does not work (links to online files work). Any suggestion on how to fix this? many thanks.

ipydatawidgets==4.0.1 ipykernel==5.3.4 ipyleaflet==0.13.3 ipython==7.16.1 ipywidgets==7.5.1 jupyterhub==1.2.0.dev0 jupyterlab==2.2.2 jupyterlab-pygments==0.1.1 jupyterlab-server==1.2.0

import os.path
from ipyleaflet import Map, ImageOverlay
from IPython.display import Image, display

png = 'cat.png'

print("File exist: ", os.path.isfile(png))
i = Image(filename=png)
display(i)
m = Map(center=(25, -115), zoom=4)
image = VideoOverlay(
    url=png,
    bounds=((13, -130), (32, -100))
)

m.add_layer(image)
display(m)

Screenshot_20200729_123336

konanast avatar Jul 29 '20 09:07 konanast

This is documented in the LocalTileLayer page: https://ipyleaflet.readthedocs.io/en/latest/api_reference/local_tile_layer.html#example

In JupyterLab, the path is relative to the server (where you started JupyterLab) and you need to prefix the path with “files/”.

We should maybe add it to the ImageOverlay and VideoOverlay documentation. PR welcome!

martinRenou avatar Jul 29 '20 11:07 martinRenou

Thanks a lot for your quick reply. I have not figure out yet how to add a simple image on the map. In the documentation it says "Relative URL (e.g. ‘tiles/{z}/{x}/{y}.png’ or ‘files/tiles/{z}/{x}/{y}.png’ in JupyterLab)"

so if I have an image in the folder "/home/username/tests/image.png" how do I set it with LocalTileLayer? I have tried many things but nothing works: -/files/tiles/home/username/tests/image.png -/files/home/username/tests/image.png -/home/username/tests/image.png... and any other path combination that exist..

Also how do I set the bounds? do I have to set it in the path (/{z}/{x}/{y}) but how do I set the image extent then?

Thanks again!

konanast avatar Jul 29 '20 15:07 konanast

@konanast I believe only relative path is supported. See the documentation.

from ipyleaflet import Map, ImageOverlay

m = Map(center=(25, -115), zoom=4)

image = ImageOverlay(
    url="https://i.imgur.com/06Q1fSz.png",
    # url='../06Q1fSz.png',
    bounds=((13, -130), (32, -100))
)

m.add_layer(image);
m

giswqs avatar Jul 29 '20 16:07 giswqs

Here is a function from the geemap package that supports absolute path.

    def image_overlay(self, url, bounds, name):
        """Overlays an image from the Internet or locally on the map.

        Args:
            url (str): http URL or local file path to the image.
            bounds (tuple): bounding box of the image in the format of (lower_left(lat, lon), upper_right(lat, lon)), such as ((13, -130), (32, -100)).
            name (str): name of the layer to show on the layer control.
        """
        from base64 import b64encode
        from PIL import Image, ImageSequence
        from io import BytesIO
        try:
            if not url.startswith('http'):

                if not os.path.exists(url):
                    print('The provided file does not exist.')
                    return

                ext = os.path.splitext(url)[1][1:]  # file extension
                image = Image.open(url)

                f = BytesIO()
                if ext.lower() == 'gif':
                    frames = []
                    # Loop over each frame in the animated image
                    for frame in ImageSequence.Iterator(image):
                        frame = frame.convert('RGBA')
                        b = BytesIO()
                        frame.save(b, format="gif")
                        frame = Image.open(b)
                        frames.append(frame)
                    frames[0].save(f, format='GIF', save_all=True,
                                   append_images=frames[1:], loop=0)
                else:
                    image.save(f, ext)

                data = b64encode(f.getvalue())
                data = data.decode('ascii')
                url = 'data:image/{};base64,'.format(ext) + data
            img = ipyleaflet.ImageOverlay(url=url, bounds=bounds, name=name)
            self.add_layer(img)
        except Exception as e:
            print(e)
            return

giswqs avatar Jul 29 '20 16:07 giswqs

Thanks a lot for the replay! I finally figure out how it works

For Jupyter Lab: 'files/working_folder/image.png' For Jupyter Notebook (tree view): 'image.png'

Example code:

from ipyleaflet import Map, ImageOverlay
import os.path

png = 'files/test_project/cat.png'
# Or to get the working folder automatically: 'files/'+os.getcwd().split('/')[-1]+'/cat.png'

# Just to see what os.path returns?
try: 
    print("File exist: ", os.path.isfile(png))
except:
    pass

m = Map(center=(25, -115), zoom=4)
image = ImageOverlay(
    url=png,
    bounds=((13, -130), (32, -100))
)
m.add_layer(image)
m

The notebook (.ipynb) is in the same folder (test_project) with the image (cat.png) test_project/ -test.ipynb -cat.png

A simple way to get the working folder is:

import os
workingfolder = os.getcwd().split('/')[-1]
print(workingfolder)

The way it is described in the documentation is a bit confusing, please let me know how can I help to improve the documentation. Note also that os.path is false:/

Screenshot_20200729_192944

konanast avatar Jul 29 '20 16:07 konanast

We should definitely update the documentation (so that it's simpler to add a cat on a map :smile:)

martinRenou avatar Jul 30 '20 06:07 martinRenou

Yes:) I proposed to add this in the documentation of ImageOverlay:

An http url to the footage or a relative path to a local file (image/video, e.g. ‘image.png’ or ‘files/working_dir/image.png’ in JupyterLab). Note that absolute local paths are not supported.

So it will be easier for someone to understand how to make it work in Jupyter Lab.

konanast avatar Jul 30 '20 06:07 konanast

Would you like to open a PR so you get credits for it?

martinRenou avatar Jul 30 '20 06:07 martinRenou

Hello, I have tried the method above for Jupyterlab with 'files/working_folder/image.png' and it didn't work. So I found this ticket #745, I tried to copy the Copy Download Link method and it worked. But didn't find it in the documentation of ImageOverlay or the issues related to.

Aptychus avatar Apr 26 '22 14:04 Aptychus