Repository: imageio/imageio

Issue:
Failure to write when using a file for plugins using get_local_filename
Hi, 

It's said in the [docs](https://imageio.readthedocs.io/en/stable/userapi.html#imageio.imwrite) that I can use file objects for imread/imwrite functions
but actually it's only true for PIL, so this code 
```python
import imageio
import numpy as np

image = np.random.randint(0, 65535, size=[512, 512], dtype=np.uint16)

with open('test.png', 'wb') as f:
    imageio.imwrite(f, image, format='PNG-FI')
```
creates an empty file without any exception or warning.

It would be great to have the binary streams support like file/BytesIO for FI.

```
imageio: v2.4.1
```

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/21481432/badge)](https://coveralls.io/builds/21481432)

Coverage increased (+0.008%) to 91.135% when pulling **404fe1c66322d804fbe34127ab6b8c8bc8eb11f0 on 424** into **1a7f572a3bff99e479f66c3e58720443caabe4b9 on master**.


Patch:
diff --git a/imageio/core/request.py b/imageio/core/request.py
index 76eccdf..99ef28f 100644
--- a/imageio/core/request.py
+++ b/imageio/core/request.py
@@ -104,6 +104,7 @@ class Request(object):
 
         # To handle the plugin side
         self._file = None  # To store the file instance
+        self._file_is_local = False  # whether the data needs to be copied at end
         self._filename_local = None  # not None if using tempfile on this FS
         self._firstbytes = None  # For easy header parsing
 
@@ -206,12 +207,12 @@ class Request(object):
             if hasattr(uri, "read") and hasattr(uri, "close"):
                 self._uri_type = URI_FILE
                 self._filename = "<file>"
-                self._file = uri
+                self._file = uri  # Data must be read from here
         elif is_write_request:
             if hasattr(uri, "write") and hasattr(uri, "close"):
                 self._uri_type = URI_FILE
                 self._filename = "<file>"
-                self._file = uri
+                self._file = uri  # Data must be written here
 
         # Expand user dir
         if self._uri_type == URI_FILENAME and self._filename.startswith("~"):
@@ -321,7 +322,7 @@ class Request(object):
         Get a file object for the resource associated with this request.
         If this is a reading request, the file is in read mode,
         otherwise in write mode. This method is not thread safe. Plugins
-        do not need to close the file when done.
+        should not close the file when done.
 
         This is the preferred way to read/write the data. But if a
         format cannot handle file-like objects, they should use
@@ -337,7 +338,9 @@ class Request(object):
 
         if self._uri_type == URI_BYTES:
             if want_to_write:
+                # Create new file object, we catch the bytes in finish()
                 self._file = BytesIO()
+                self._file_is_local = True
             else:
                 self._file = BytesIO(self._bytes)
 
@@ -353,6 +356,7 @@ class Request(object):
             if want_to_write:
                 # Create new file object, we catch the bytes in finish()
                 self._file = BytesIO()
+                self._file_is_local = True
             else:
                 # Open zipfile and open new file object for specific file
                 self._zipfile = zipfile.ZipFile(filename, "r")
@@ -395,18 +399,28 @@ class Request(object):
         results.
         """
 
-        # Init
-        bytes = None
-
-        # Collect bytes from temp file
-        if self.mode[0] == "w" and self._filename_local:
-            with open(self._filename_local, "rb") as file:
-                bytes = file.read()
-
-        # Collect bytes from BytesIO file object.
-        written = (self.mode[0] == "w") and self._file
-        if written and self._uri_type in [URI_BYTES, URI_ZIPPED]:
-            bytes = self._file.getvalue()
+        if self.mode[0] == "w":
+
+            # See if we "own" the data and must put it somewhere
+            bytes = None
+            if self._filename_local:
+                with open(self._filename_local, "rb") as file:
+                    bytes = file.read()
+            elif self._file_is_local:
+                bytes = self._file.getvalue()
+
+            # Put the data in the right place
+            if bytes is not None:
+                if self._uri_type == URI_BYTES:
+                    self._result = bytes  # Picked up by imread function
+                elif self._uri_type == URI_FILE:
+                    self._file.write(bytes)
+                elif self._uri_type == URI_ZIPPED:
+                    zf = zipfile.ZipFile(self._filename_zip[0], "a")
+                    zf.writestr(self._filename_zip[1], bytes)
+                    zf.close()
+                # elif self._uri_type == URI_FILENAME: -> is always direct
+                # elif self._uri_type == URI_FTP/HTTP: -> write not supported
 
         # Close open files that we know of (and are responsible for)
         if self._file and self._uri_type != URI_FILE:
@@ -415,6 +429,7 @@ class Request(object):
         if self._zipfile:
             self._zipfile.close()
             self._zipfile = None
+
         # Remove temp file
         if self._filename_local:
             try:
@@ -423,15 +438,6 @@ class Request(object):
                 pass
             self._filename_local = None
 
-        # Handle bytes that we collected
-        if bytes is not None:
-            if self._uri_type == URI_BYTES:
-                self._result = bytes  # Picked up by imread function
-            elif self._uri_type == URI_ZIPPED:
-                zf = zipfile.ZipFile(self._filename_zip[0], "a")
-                zf.writestr(self._filename_zip[1], bytes)
-                zf.close()
-
         # Detach so gc can clean even if a reference of self lingers
         self._bytes = None
 
diff --git a/imageio/plugins/tifffile.py b/imageio/plugins/tifffile.py
index 77f9e6b..233e6b0 100644
--- a/imageio/plugins/tifffile.py
+++ b/imageio/plugins/tifffile.py
@@ -286,17 +286,14 @@ class TiffFormat(Format):
 
             try:
                 self._tf = _tifffile.TiffWriter(
-                    self.request.get_local_filename(),
-                    bigtiff,
-                    byteorder,
-                    software=software,
+                    self.request.get_file(), bigtiff, byteorder, software=software
                 )
                 self._software = None
             except TypeError:
                 # In tifffile >= 0.15, the `software` arg is passed to
                 # TiffWriter.save
                 self._tf = _tifffile.TiffWriter(
-                    self.request.get_local_filename(), bigtiff, byteorder
+                    self.request.get_file(), bigtiff, byteorder
                 )
                 self._software = software
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 793971f..54d952e 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -216,56 +216,60 @@ def test_request_read_sources():
 
     # Read that image from these different sources. Read data from file
     # and from local file (the two main plugin-facing functions)
-    for X in range(2):
-
-        # Define uris to test. Define inside loop, since we need fresh files
-        uris = [
-            filename,
-            os.path.join(zipfilename, fname),
-            bytes,
-            memoryview(bytes),
-            open(filename, "rb"),
-        ]
-        if has_inet:
-            uris.append(burl + fname)
-
-        for uri in uris:
-            R = Request(uri, "ri")
-            first_bytes = R.firstbytes
-            if X == 0:
-                all_bytes = R.get_file().read()
-            else:
-                with open(R.get_local_filename(), "rb") as f:
-                    all_bytes = f.read()
-            R.finish()
-            # Test
-            assert len(first_bytes) > 0
-            assert all_bytes.startswith(first_bytes)
-            assert bytes == all_bytes
+    for file_or_filename in range(2):
+        for check_first_bytes in range(2):
+
+            # Define uris to test. Define inside loop, since we need fresh files
+            uris = [
+                filename,
+                os.path.join(zipfilename, fname),
+                bytes,
+                memoryview(bytes),
+                open(filename, "rb"),
+            ]
+            if has_inet:
+                uris.append(burl + fname)
+
+            for uri in uris:
+                R = Request(uri, "ri")
+                if check_first_bytes:
+                    first_bytes = R.firstbytes
+                if file_or_filename == 0:
+                    all_bytes = R.get_file().read()
+                else:
+                    with open(R.get_local_filename(), "rb") as f:
+                        all_bytes = f.read()
+                R.finish()
+                assert bytes == all_bytes
+                if check_first_bytes:
+                    assert len(first_bytes) > 0
+                    assert all_bytes.startswith(first_bytes)
 
 
 def test_request_save_sources():
 
-    # Prepare desinations
+    # Get test data
     fname = "images/chelsea.png"
     filename = get_remote_file(fname, test_dir)
     with open(filename, "rb") as f:
         bytes = f.read()
-    #
+    assert len(bytes) > 0
+
+    # Prepare destinations
     fname2 = fname + ".out"
     filename2 = os.path.join(test_dir, fname2)
     zipfilename2 = os.path.join(test_dir, "test2.zip")
-    file2 = BytesIO()
+    file2 = None
 
     # Write an image into many different destinations
     # Do once via file and ones via local filename
-    for i in range(2):
-        # Clear
+    for file_or_filename in range(2):
+        # Clear destinations
         for xx in (filename2, zipfilename2):
             if os.path.isfile(xx):
-                print("trying to delete", xx)
                 os.remove(xx)
-        # Write to three destinations
+        file2 = BytesIO()
+        # Write to four destinations
         for uri in (
             filename2,
             os.path.join(zipfilename2, fname2),
@@ -273,18 +277,19 @@ def test_request_save_sources():
             imageio.RETURN_BYTES,  # This one last to fill `res`
         ):
             R = Request(uri, "wi")
-            if i == 0:
+            if file_or_filename == 0:
                 R.get_file().write(bytes)  # via file
             else:
                 with open(R.get_local_filename(), "wb") as f:
                     f.write(bytes)  # via local
             R.finish()
             res = R.get_result()
-        # Test three results
+        # Test four results
         with open(filename2, "rb") as f:
             assert f.read() == bytes
         with ZipFile(zipfilename2, "r") as zf:
             assert zf.open(fname2).read() == bytes
+        assert file2.getvalue() == bytes
         assert res == bytes