Repository: scikit-image/scikit-image

Issue:
IO:  unhandled exception, in case the URL cannot be opened
## Description
![image](https://user-images.githubusercontent.com/30770221/53887634-4a1eb600-3fe8-11e9-97db-f374815eb620.png)


## Way to reproduce
```python
def url_to_image(url):
    """download image from url and return it"""
    im_mat = io.imread(url)
    image = cv2.imdecode(im_mat, cv2.IMREAD_COLOR)
 
    # return the image
    return image
```


## Version information
```python
>>> from __future__ import print_function
>>> import sys; print(sys.version)
3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)]
>>> import platform; print(platform.platform())
Windows-10-10.0.17134-SP0
>>> import skimage; print("scikit-image version: {}".format(skimage.__version__))
scikit-image version: 0.14.2
>>> import numpy; print("numpy version: {}".format(numpy.__version__))
```

```python
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\skimage\io\util.py", line 28, in file_or_url_context
    with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f:
  File "C:\Python37\lib\tempfile.py", line 547, in NamedTemporaryFile
    (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
  File "C:\Python37\lib\tempfile.py", line 258, in _mkstemp_inner
    fd = _os.open(file, flags, 0o600)
OSError: [Errno 22] Invalid argument: 'C:\\Users\\kelvin\\AppData\\Local\\Temp\\tmpegjt2y8r.png?mi_guid=CFCDF62B5115D31389F2893D3CCFFA5CB42BDE7F8CD696BB57B97B8BB567940F&mi_language=EN'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "imagecheck.py", line 28, in <module>
    print(url_to_image("http://www.movable-ink-6437.com/p/rp/0fd853751b1edbb0.png?mi_guid=CFCDF62B5115D31389F2893D3CCFFA5CB42BDE7F8CD696BB57B97B8BB567940F&mi_language=EN"))
  File "imagecheck.py", line 9, in url_to_image
    im_mat = io.imread(url)
  File "C:\Python37\lib\site-packages\skimage\io\_io.py", line 61, in imread
    with file_or_url_context(fname) as fname:
  File "C:\Python37\lib\contextlib.py", line 112, in __enter__
    return next(self.gen)
  File "C:\Python37\lib\site-packages\skimage\io\util.py", line 34, in file_or_url_context
    os.remove(f.name)
UnboundLocalError: local variable 'f' referenced before assignment
```




Discussion:
Sloppy programming there—it's an unhandled exception, in case the URL cannot be opened.  I tried it now, and it works, so I guess it must have been an intermittent network error.

Either way, I can verify that the bug exists and should be fixed by catching any error occurring while downloading the data.
For the future generations:

The easier way to get an image for cv2 from a url is using imutils' function

```python
imutils.url_to_image(url)
```

which works by

```python
def url_to_image(url, readFlag=cv2.IMREAD_COLOR):
    # download the image, convert it to a NumPy array, and then read
    # it into OpenCV format
    resp = urlopen(url)
    image = np.asarray(bytearray(resp.read()), dtype="uint8")
    image = cv2.imdecode(image, readFlag)

    # return the image
    return image
```

but I didn't ever get that to work because I used

```python
from urllib import urlopen
```

instead of the python 3 equivalent:

```python
from urllib.request import urlopen
```

hopefully this helps someone lol
you can also try `imageio`
I guess I can't reproduce easily. @rfezzani is trying to fix this, but I'm not sure the fix is the "correct" one.


I think the origininal error has been fixed in an other issue, where we stripped the `?asdlfakjsdlkafjlasdkfjaldskjlfak` at the end of the URL.
Was this already fixed in https://github.com/scikit-image/scikit-image/pull/3642
Looking at the code, this doesn't seem right:

```python
        try:
            with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f:
                u = urllib.request.urlopen(resource_name)
                f.write(u.read())
            # f must be closed before yielding
            yield f.name
        finally:
            os.remove(f.name)
```

What happens if `NamedTemporaryFile` fails?
I observed in #3843 that `urllib.request.urlopen` raises

- an `urllib.error.URLError` if the service is unknown
- an `urllib.error.HTTPError` in case of HTTP errors.

#3843 attempts to fix #3785.
Edit:
But sorry, this doesn't answer your question :-) I will further investigate...
Looking at `NamedTemporaryFile` [source code](https://github.com/python/cpython/blob/2.7/Lib/tempfile.py#L447), the following errors may be raised:

- `FileNotFoundError` if no usable temporary directory is found ([link](https://github.com/python/cpython/blob/3.7/Lib/tempfile.py#L227))
- `FileExistsError` if no usable temporary file name found ([link](https://github.com/python/cpython/blob/3.7/Lib/tempfile.py#L271))
- `PermissionError` if temporary files can't be created ([link](https://github.com/python/cpython/blob/3.7/Lib/tempfile.py#L268))
- `BaseExeption` if `io.open` fails ([link](https://github.com/python/cpython/blob/3.7/Lib/tempfile.py#L556))
There are so many bad things that happen to your computer whne you can't create files. 
skimage will be the least of your worries. 

I doubt it is worth our time fixing something like this is worth our time unless we get a specific usecase of somebody being affected.

Resolving patch:
diff --git a/skimage/io/util.py b/skimage/io/util.py
--- a/skimage/io/util.py
+++ b/skimage/io/util.py
@@ -1,5 +1,6 @@
 import urllib.parse
 import urllib.request
+from urllib.error import URLError, HTTPError
 
 import os
 import re
@@ -28,7 +29,15 @@ def file_or_url_context(resource_name):
                 f.write(u.read())
             # f must be closed before yielding
             yield f.name
-        finally:
+        except (URLError, HTTPError):
+            # could not open URL
+            os.remove(f.name)
+            raise
+        except (FileNotFoundError, FileExistsError,
+                PermissionError, BaseException):
+            # could not create temporary file
+            raise
+        else:
             os.remove(f.name)
     else:
         yield resource_name


Test patch:
diff --git a/skimage/io/tests/test_io.py b/skimage/io/tests/test_io.py
--- a/skimage/io/tests/test_io.py
+++ b/skimage/io/tests/test_io.py
@@ -1,4 +1,5 @@
 import os
+import tempfile
 
 import numpy as np
 from skimage import io, data_dir
@@ -52,3 +53,49 @@ def test_imread_http_url(httpserver):
     # by extension
     image = io.imread(httpserver.url + '/test.jpg' + '?' + 's' * 266)
     assert image.shape == (1, 1)
+
+
+def _named_tempfile_func(error_class):
+    """Create a mock function for NamedTemporaryFile that always raises.
+
+    Parameters
+    ----------
+    error_class : exception class
+        The error that should be raised when asking for a NamedTemporaryFile.
+
+    Returns
+    -------
+    named_temp_file : callable
+        A function that always raises the desired error.
+
+    Notes
+    -----
+    Although this function has general utility for raising errors, it is
+    expected to be used to raise errors that ``tempfile.NamedTemporaryFile``
+    from the Python standard library could raise. As of this writing, these
+    are ``FileNotFoundError``, ``FileExistsError``, ``PermissionError``, and
+    ``BaseException``. See
+    `this comment <https://github.com/scikit-image/scikit-image/issues/3785#issuecomment-486598307>`__
+    for more information.
+    """
+    def named_temp_file(*args, **kwargs):
+        raise error_class()
+    return named_temp_file
+
+
+@testing.parametrize(
+    'error_class', [
+        FileNotFoundError, FileExistsError, PermissionError, BaseException
+    ]
+)
+def test_failed_temporary_file(monkeypatch, error_class):
+    # tweak data path so that file URI works on both unix and windows.
+    data_path = data_dir.lstrip(os.path.sep)
+    data_path = data_path.replace(os.path.sep, '/')
+    image_url = 'file:///{0}/camera.png'.format(data_path)
+    with monkeypatch.context():
+        monkeypatch.setattr(
+            tempfile, 'NamedTemporaryFile', _named_tempfile_func(error_class)
+        )
+        with testing.raises(error_class):
+            image = io.imread(image_url)