jupyter-widgets / ipyleaflet

A Jupyter - Leaflet.js bridge
https://ipyleaflet.readthedocs.io
MIT License
1.49k stars 363 forks source link

jupyterlab fails in rendering ImageOverlay from file #120

Open epifanio opened 6 years ago

epifanio commented 6 years ago

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.

deeplook commented 5 years ago

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

epifanio commented 4 years ago

@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?

konanast commented 4 years ago

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

martinRenou commented 4 years ago

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!

konanast commented 4 years ago

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!

giswqs commented 4 years ago

@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 commented 4 years ago

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
konanast commented 4 years ago

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

martinRenou commented 4 years ago

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

konanast commented 4 years ago

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.

martinRenou commented 4 years ago

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

Aptychus commented 2 years ago

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.