Repository: imageio/imageio

Issue:
[Feature Request] Partial Conformance with the SciPy Image I/O API
The long-heralded release of [SciPy 1.0.0](https://scipy.github.io/devdocs/release.1.0.0.html) has landed.

Sixteen years was a hard wait, but good things come to those who wait... *for sixteen years.*

## The Inevitable Bad News

Well, mostly good things. SciPy version:

* 1.0.0 [officially deprecates](https://scipy.github.io/devdocs/release.1.0.0.html#backwards-incompatible-changes) *all* image I/O functionality residing in the `scipy.ndimage` and `scipy.misc` packages – including the commonly called `scipy.ndimage.imread()` and `scipy.ndimage.imwrite()` functions.
* 1.2.0 plans to **permanently remove** this functionality. *Boom!*

Since [our downstream biophysics simulator](https://gitlab.com/betse/betse) heavily depends upon this functionality, SciPy 1.0.0 is more of a bad than a good thing for us. <sup>Yet again, progress only brings pain.</sup>

[SciPy documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html) explicitly recommends `imageio` as a suitable replacement. Since the `imageio` API is *considerably* more bare bones than the equivalent SciPy API, however, `imageio` may prove to be a less-than-suitable replacement for some end users – mostly, us.

## The Overly Optimistic Hope

Ideally, the `imageio` API could be marginally improved to provide at least *partial* support for several of the most commonly used features of SciPy's image I/O API. This includes:

* **Pillow-based mode conversion.** The [`scipy.ndimage.imread()`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html) function, for example, provides an optional `mode` string parameter permitting external callers to specify a desired target colorspace (defaulting to `None`). If non-`None` and different from the image's current `mode`, this parameter is passed as is to the `PIL.Image.convert()` method. This is two lines of Python. So, this is good.
* **Grayscale flattening.** Again, the [`scipy.ndimage.imread()`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html) function provides an optional `flatten` boolean parameter permitting external callers to reduce non-grayscale images to grayscale (defaulting to `False`). If `True`, the `mode` parameter is coerced to `F`. Why not `L`? I have no idea and doubt SciPy developers do either. I also resent the overly ambiguous name of this boolean, when something resembling `grayscale` or `as_gray` would have sown less confusion. This also is two lines of Python. So, at least that is good.

This isn't simply syntactic sugar.

This is critical functionality of general interest. Well, O.K... `flatten` is blatantly syntactic sugar.

But `mode` isn't. External callers have *no* explicit access to the `PIL.Image` instance internally instantiated by this function. These optional parameters are the only means callers have of intervening in the conversion process. Doing so after the fact is non-trivial (or perhaps even infeasible).

I accept that `imageio` is not, will never be, and *should* never be an image processor. `imageio` is a pure image I/O framework – just as Code Jesus intended. Still... the thin line between image I/O and image processing is an easy Rubicon to cross, especially when Pillow already performs all of the heavy lifting on our behalf.

## Bold Details for a Bright Future

SciPy's image I/O is unconditionally implemented in terms of Pillow.

`imageio`, on the other hand, is conditionally implemented in terms of Pillow and whatever else it happens to scrounge from the local filesystem (e.g., FreeImage). Presumably, this conditionality prevents the optional `mode` and `flatten` parameters from being directly handled by the `imageio.imread()` function.

I grok that. Nonetheless, would it be feasible to permit these parameters to be:

* Passed by callers as variadic keyword arguments accepted by `imageio.imread()`.
* Passed by Pillow plugins invoked by `imageio.imread()` to the `PIL.Image.convert()` method, munging a `True` value for the `flatten` key into a `mode` value of either `L` or `F`.
* Processed in some fashion by the non-Pillow plugins. Hopefully, these parameters won't simply be silently ignored – but I'll take what I can get.

The `imageio.plugins.pillow.pil_get_frame()` function appears to offer similar functionality, albeit in a manner *not* exposed to external callers. Or is it? My modest (read: *awful*) `grep` skills failed me here.

Alternately, would exposing the `PIL.Image` instance internally instantiated by Pillow plugins via some public variable or getter method of the `imageio.core.format.Reader` class be feasible? I half-suspect this class *already* exposes `PIL.Image` instances, but (again) shamefully failed to `grep` the exact variable or method for doing so.

## The End of a Dream

Thus ends this excrutiating feature request.

Thanks for the long years of gruelling work. May there be many more to come! 🌞 
imageio.imread with jpg image with Spyder 3.2.5 and Python 3.6 debug message Error closing: 'NoneType' object has no attribute 'close'
when running imageio.imread to import a jpg image with DEBUG logging enabled I get the following on Spyder 3.2.5 and Python 3.6

```
import imageio
import logging

logging.basicConfig(level=logging.DEBUG)
a = imageio.imread('picture.jpg')
```
`DEBUG:PIL.Image:Error closing: 'NoneType' object has no attribute 'close'`

the picture imports fine.
  

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

Coverage increased (+0.02%) to 93.076% when pulling **aea557ffd9eaf68be110b7d8142c906b2b4e8891 on scipy** into **0c7eea3babaaefc049e93db937155c477fc5ca7e on master**.

almarklein: Ping @leycec @untom, it would be great if you could take a look.



Patch:
diff --git a/README.md b/README.md
index 1df3bc2..da3a1fe 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 [![Build Status](https://ci.appveyor.com/api/projects/status/4wjqg4o5r2q53iwt/branch/master?svg=true)](https://ci.appveyor.com/project/almarklein/imageio/branch/master)
 [![Build Status](https://travis-ci.org/imageio/imageio.svg?branch=master)](https://travis-ci.org/imageio/imageio)
 [![Coverage Status](https://coveralls.io/repos/imageio/imageio/badge.png?branch=master)](https://coveralls.io/r/imageio/imageio?branch=master)
-[![Documentation Status](https://readthedocs.org/projects/imageio/badge/?version=latest)](https://imageio.readthedocs.org)
+[![Documentation Status](https://readthedocs.org/projects/imageio/badge/?version=latest)](https://imageio.readthedocs.io)
    
 Website: http://imageio.github.io
 
@@ -20,7 +20,7 @@ Python 2.7 and 3.4+, and is easy to install.
 
 <h2>Example</h2>
 Here's a minimal example of how to use imageio. See the docs for 
-<a href='http://imageio.readthedocs.org/en/latest/examples.html'>more examples</a>.
+<a href='http://imageio.readthedocs.io/en/latest/examples.html'>more examples</a>.
 <pre>
 >>> import imageio
 >>> im = imageio.imread('imageio:chelsea.png')  # read a standard image
@@ -37,18 +37,18 @@ As a user, you just have to remember a handfull of functions:
     <li>mimread() and mimwrite() - for image series (animations)</li>
     <li>volread() and volwrite() - for volumetric image data</li>
     <li>get_reader() and get_writer() - for more control (e.g. streaming)</li>
-    <li>See the <a href='http://imageio.readthedocs.org/en/latest/userapi.html'>user api</a> for more information</li>
+    <li>See the <a href='http://imageio.readthedocs.io/en/latest/userapi.html'>user api</a> for more information</li>
 </ul>
 
 
 <h2>Features</h2>
 <ul>
     <li>Simple interface via a consise set of functions.</li>
-    <li>Easy to <a href='http://imageio.readthedocs.org/en/latest/installation.html'>install</a> using conda or pip.</li>    
-    <li>Few dependencies (only Numpy).</li>
+    <li>Easy to <a href='http://imageio.readthedocs.io/en/latest/installation.html'>install</a> using conda or pip.</li>    
+    <li>Few dependencies (only Numpy and Pillow).</li>
     <li>Pure Python, runs on Python 2.7, 3.4+, and Pypy</li>
     <li>Cross platform, runs on Windows, Linux, OS X (Raspberry Pi planned)</li>
-    <li>Lots of supported <a href='http://imageio.readthedocs.org/en/latest/formats.html'>formats</a>.</li>
+    <li>Lots of supported <a href='http://imageio.readthedocs.io/en/latest/formats.html'>formats</a>.</li>
     <li>Can read from file names, file objects, zipfiles, http/ftp, and raw bytes.</li>
     <li>Easy to extend using plugins.</li>
     <li>Code quality is maintained with many tests and continuous integration.</li>
@@ -68,8 +68,8 @@ a way to download these with one function call, and prompts the user to do
 so when needed. The download is cached in your appdata
 directory, this keeps imageio light and scalable.
 </p><p>
-We plan to provide a wide range of image formats. Also scientific
-formats. Any help in implementing more formats is very welcome!
+Imageio provides a wide range of image formats, including scientific
+formats. Any help with implementing more formats is very welcome!
 </p><p>
 The codebase adheres to (a subset of) the PEP8 style guides. We strive
 for maximum test coverage (100% for the core, >95% for each plugin).
@@ -107,7 +107,7 @@ with one function call:
 Imageio was based out of the frustration that many libraries that needed
 to read or write image data produced their own functionality for IO.
 PIL did not meet the needs very well, and libraries like scikit-image
-need to be able to deal with scientific formats. I felt there was a
+need to be able to deal with scientific formats. There was a
 need for a good image io library, which is an easy dependency, easy to
 maintain, and scalable to exotic file formats.
 </p><p>
@@ -117,13 +117,12 @@ We created a simple but powerful core, a clean user API, and a proper
 plugin system.
 </p><p>
 The purpose of imageio is to support reading and writing of image data.
-We're not processing images, you should use scikit-image for that. Imageio
+We're not processing images, you should use e.g. scikit-image for that. Imageio
 should be easy to install and be lightweight. Imageio's plugin system
 makes it possible to scale the number of supported formats and still
-keep a low footprint.
+keep a small footprint.
 </p><p>
-It is impossible for one person to implement and maintain a wide variety
-of formats. My hope is to form a group of developers, who each maintain
+It is our hope to form a group of developers, whom each maintain
 one or more plugins. In that way, the burder of each developer is low,
 and together we can make imageio into a really useful library!
 </p>
diff --git a/appveyor.yml b/appveyor.yml
index 8882185..1344274 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -5,12 +5,8 @@ environment:
 
   matrix:
 
-    - PYTHON: "C:\\Python35-conda64"
-      PYTHON_VERSION: "3.5"
-      PYTHON_ARCH: "64"
-
-    - PYTHON: "C:\\Python27-conda64"
-      PYTHON_VERSION: "2.7"
+    - PYTHON: "C:\\Python36-conda64"
+      PYTHON_VERSION: "3.6"
       PYTHON_ARCH: "64"
 
 install:
diff --git a/docs/conf.py b/docs/conf.py
index 7d4f5d3..5f1bafc 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,7 +50,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'imageio'
-copyright = u'1014-2018, imageio contributors'
+copyright = u'2014-2018, imageio contributors'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
diff --git a/docs/drop27.rst b/docs/drop27.rst
new file mode 100644
index 0000000..14ebd0b
--- /dev/null
+++ b/docs/drop27.rst
@@ -0,0 +1,18 @@
+Plan for dropping support for Python 2.7
+========================================
+
+Starting on January 1, 2020, imageio will stop supporting Python 2.7.
+The latest release that still supports Python 2.7 will remain available
+on Pypi and conda-forge. A branch will be made that may be used to
+continue supporting 2.7, but one should not expect (free) contributions
+from the imageio developers there.
+
+For more information on the scientific Python ecosystem's transition
+to Python3 only, see the python3-statement_.
+
+For more information on porting your code to run on Python 3, see the
+python3-howto_.
+
+.. _python3-statement: http://www.python3statement.org/
+
+.. _python3-howto: https://docs.python.org/3/howto/pyporting.html
diff --git a/docs/installation.rst b/docs/installation.rst
index 22cd3e0..d1d7888 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -2,7 +2,7 @@ Installing imageio
 ==================
 
 Imageio is written in pure Python, so installation is easy. 
-Imageio works on Python 2.7 and 3.4+. It also works on Pypy.
+Imageio works on Python `2.7 <drop27.html>`_ and 3.4+. It also works on Pypy.
 Imageio depends on Numpy and Pillow. For some formats, imageio needs
 additional libraries/executables (e.g. ffmpeg), which imageio helps you
 download and store in a folder in your application data.
@@ -14,5 +14,8 @@ To install imageio, use one of the following methods:
 * Good old ``python setup.py install``
 
 For developers, we provide a simple mechanism to allow importing 
-imageio from the cloned repository. See the file ``imageio.proxy.io`` for
+imageio from the cloned repository. See the file ``imageio.proxy.py`` for
 details.
+
+After installation, checkout the
+:doc:`examples  <examples>` and :doc:`user api <userapi>`. 
diff --git a/docs/scipy.rst b/docs/scipy.rst
new file mode 100644
index 0000000..9f3c355
--- /dev/null
+++ b/docs/scipy.rst
@@ -0,0 +1,28 @@
+Transitioning from Scipy's imread
+=================================
+
+Scipy is `deprecating <https://scipy.github.io/devdocs/release.1.0.0.html#backwards-incompatible-changes>`_
+their image I/O functionality.
+
+This document is intended to help people coming from
+`Scipy <https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html>`_
+to adapt to Imageio's :func:`imread <imageio.imread>` function.
+We recommend reading the :doc:`user api <userapi>` and checkout some
+:doc:`examples <examples>` to get a feel of imageio.
+
+Imageio makes use of variety of plugins to support reading images (and volumes/movies)
+from many different formats. Fortunately, Pillow is the main plugin for common images,
+which is the same library as used by  Scipy's ``imread``. Note that Imageio
+automatically selects a plugin based on the image to read (unless a format is
+explicitly specified), but uses Pillow where possible. 
+
+In short terms: For images previously read by Scipy's imread, imageio should
+generally use Pillow as well, and imageio provides the same functionality as Scipy
+in these cases. But keep in mind:
+
+    * Instead of ``mode``, use the ``pilmode`` keyword argument.
+    * Instead of ``flatten``, use the ``as_gray`` keyword argument.
+    * The documentation for the above arguments is not on :func:`imread <imageio.imread>`,
+      but on the docs of the individual formats, e.g. :doc:`PNG <format_png-pil>`.
+    * Imageio's functions all return numpy arrays, albeit as a subclass (so that
+      meta data can be attached). This subclass is called ``Image``.
diff --git a/docs/sec_gettingstarted.rst b/docs/sec_gettingstarted.rst
index 60bc20a..182d714 100644
--- a/docs/sec_gettingstarted.rst
+++ b/docs/sec_gettingstarted.rst
@@ -7,4 +7,5 @@ Getting started
   
   Installation <installation>
   Usage examples <examples>
+  Transitioning from Scipy <scipy>
   Release notes <releasenotes>
diff --git a/imageio/core/format.py b/imageio/core/format.py
index 2167816..1bac071 100644
--- a/imageio/core/format.py
+++ b/imageio/core/format.py
@@ -588,7 +588,10 @@ class FormatManager(object):
                     return format
             else:
                 # Maybe the user meant to specify an extension
-                return self['.'+name.lower()]
+                try:
+                    return self['.'+name.lower()]
+                except IndexError:
+                    pass  # Fail using original name below
         
         # Nothing found ...
         raise IndexError('No format known by name %s.' % name)
diff --git a/imageio/core/functions.py b/imageio/core/functions.py
index 581bc9e..51fcb56 100644
--- a/imageio/core/functions.py
+++ b/imageio/core/functions.py
@@ -198,6 +198,10 @@ def imread(uri, format=None, **kwargs):
         to see what arguments are available for a particular format.
     """
 
+    if 'mode' in kwargs:
+        raise TypeError('Invalid keyword argument "mode", '
+                        'perhaps you mean "pilmode"?')
+    
     # Get reader and read first
     reader = read(uri, format, 'i', **kwargs)
     with reader:
diff --git a/imageio/core/util.py b/imageio/core/util.py
index fc613d5..0cdd39c 100644
--- a/imageio/core/util.py
+++ b/imageio/core/util.py
@@ -13,7 +13,7 @@ import re
 import struct
 import sys
 import time
-from warnings import warn
+from logging import warning as warn
 
 # Make pkg_resources optional if setuptools is not available
 try:
@@ -53,6 +53,12 @@ def urlopen(*args, **kwargs):
     return urlopen(*args, **kwargs)
 
 
+def _precision_warn(p1, p2, extra=''):
+    t = ('Lossy conversion from {} to {}. {} Convert image to {} prior to '
+         'saving to suppress this warning.')
+    warn(t.format(p1, p2, extra, p2))
+
+
 def image_as_uint(im, bitdepth=None):
     """ Convert the given image to uint (default: uint8)
     
@@ -73,29 +79,26 @@ def image_as_uint(im, bitdepth=None):
         out_type = np.uint16
     else:
         raise ValueError('Bitdepth must be either 8 or 16')
-    dtype_str = str(im.dtype)
+    dtype_str1 = str(im.dtype)
+    dtype_str2 = out_type.__name__
     if ((im.dtype == np.uint8 and bitdepth == 8) or
        (im.dtype == np.uint16 and bitdepth == 16)):
         # Already the correct format? Return as-is
         return im
-    if (dtype_str.startswith('float') and
+    if (dtype_str1.startswith('float') and
        np.nanmin(im) >= 0 and np.nanmax(im) <= 1):
-        warn('Lossy conversion from {} to {}, range [0, 1]'.format(
-             dtype_str, out_type.__name__))
-        im = im.astype(np.float64) * (np.power(2.0, bitdepth)-1)
+        _precision_warn(dtype_str1, dtype_str2, 'Range [0, 1].')
+        im = im.astype(np.float64) * (np.power(2.0, bitdepth)-1) + 0.499999999
     elif im.dtype == np.uint16 and bitdepth == 8:
-        warn('Lossy conversion from uint16 to uint8, '
-             'losing 8 bits of resolution')
+        _precision_warn(dtype_str1, dtype_str2, 'Losing 8 bits of resolution.')
         im = np.right_shift(im, 8)
     elif im.dtype == np.uint32:
-        warn('Lossy conversion from uint32 to {}, '
-             'losing {} bits of resolution'.format(out_type.__name__,
-                                                   32-bitdepth))
+        _precision_warn(dtype_str1, dtype_str2,
+                        'Losing {} bits of resolution.'.format(32-bitdepth))
         im = np.right_shift(im, 32-bitdepth)
     elif im.dtype == np.uint64:
-        warn('Lossy conversion from uint64 to {}, '
-             'losing {} bits of resolution'.format(out_type.__name__,
-                                                   64-bitdepth))
+        _precision_warn(dtype_str1, dtype_str2,
+                        'Losing {} bits of resolution.'.format(64-bitdepth,))
         im = np.right_shift(im, 64-bitdepth)
     else:
         mi = np.nanmin(im)
@@ -106,12 +109,12 @@ def image_as_uint(im, bitdepth=None):
             raise ValueError('Maximum image value is not finite')
         if ma == mi:
             raise ValueError('Max value == min value, ambiguous given dtype')
-        warn('Conversion from {} to {}, '
-             'range [{}, {}]'.format(dtype_str, out_type.__name__, mi, ma))
+        _precision_warn(dtype_str1, dtype_str2,
+                        'Range [{}, {}].'.format(mi, ma))
         # Now make float copy before we scale
         im = im.astype('float64')
         # Scale the values between 0 and 1 then multiply by the max value
-        im = (im - mi) / (ma - mi) * (np.power(2.0, bitdepth)-1)
+        im = (im - mi) / (ma - mi) * (np.power(2.0, bitdepth)-1) + 0.499999999
     assert np.nanmin(im) >= 0
     assert np.nanmax(im) < np.power(2.0, bitdepth)
     return im.astype(out_type)
diff --git a/imageio/plugins/_freeimage.py b/imageio/plugins/_freeimage.py
index d52a367..9e66a7d 100644
--- a/imageio/plugins/_freeimage.py
+++ b/imageio/plugins/_freeimage.py
@@ -18,7 +18,7 @@ import os
 import sys
 import ctypes
 import threading
-from logging import warn
+from logging import warning as warn
 import numpy
 
 from ..core import (get_remote_file, load_lib, Dict, resource_dirs, 
diff --git a/imageio/plugins/pillow.py b/imageio/plugins/pillow.py
index 709e7b5..5c1fae3 100644
--- a/imageio/plugins/pillow.py
+++ b/imageio/plugins/pillow.py
@@ -19,6 +19,38 @@ from .pillow_info import pillow_formats, pillow_docs
 # todo: Pillow ImageGrab module supports grabbing the screen on Win and OSX.
 
 
+GENERIC_DOCS = """
+    Parameters for reading
+    ----------------------
+    
+    pilmode : str
+        From the Pillow documentation:
+        
+        * 'L' (8-bit pixels, grayscale)
+        * 'P' (8-bit pixels, mapped to any other mode using a color palette)
+        * 'RGB' (3x8-bit pixels, true color)
+        * 'RGBA' (4x8-bit pixels, true color with transparency mask)
+        * 'CMYK' (4x8-bit pixels, color separation)
+        * 'YCbCr' (3x8-bit pixels, color video format)
+        * 'I' (32-bit signed integer pixels)
+        * 'F' (32-bit floating point pixels)
+        
+        PIL also provides limited support for a few special modes, including
+        'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
+        (true color with premultiplied alpha).
+        
+        When translating a color image to grayscale (mode 'L', 'I' or 'F'),
+        the library uses the ITU-R 601-2 luma transform::
+        
+            L = R * 299/1000 + G * 587/1000 + B * 114/1000
+    as_gray : bool
+        If True, the image is converted using mode 'F'. When `mode` is
+        not None and `as_gray` is True, the image is first converted
+        according to `mode`, and the result is then "flattened" using
+        mode 'F'.
+"""
+
+
 class PillowFormat(Format):
     """
     Base format class for Pillow formats.
@@ -78,29 +110,36 @@ class PillowFormat(Format):
                     return True
     
     class Reader(Format.Reader):
-    
-        def _open(self, **kwargs):
+        
+        def _open(self, pilmode=None, as_gray=False):
             Image = self.format._init_pillow()
             try:
                 factory, accept = Image.OPEN[self.format.plugin_id]
             except KeyError:
                 raise RuntimeError('Format %s cannot read images.' %
                                    self.format.name)
-            self._fp = self.request.get_file()
+            self._fp = self._get_file()
             self._im = factory(self._fp, '')
             if hasattr(Image, '_decompression_bomb_check'):
                 Image._decompression_bomb_check(self._im.size)
             pil_try_read(self._im)
-            self._grayscale = _palette_is_grayscale(self._im)
+            # Store args
+            self._kwargs = dict(mode=pilmode, as_gray=as_gray,
+                                is_gray=_palette_is_grayscale(self._im))
             # Set length
             self._length = 1
             if hasattr(self._im, 'n_frames'):
                 self._length = self._im.n_frames
         
+        def _get_file(self):
+            self._we_own_fp = False
+            return self.request.get_file()
+        
         def _close(self):
-            if hasattr(self._im, 'close'):  # see issue #216
-                self._im.close()
-            # request object handled closing the _fp
+            save_pillow_close(self._im)
+            if self._we_own_fp:
+                self._fp.close()
+            # else: request object handles closing the _fp
         
         def _get_length(self):
             return self._length
@@ -122,7 +161,7 @@ class PillowFormat(Format):
                     i += 1
                     self._seek(i)
             self._im.getdata()[0]
-            im = pil_get_frame(self._im, self._grayscale)
+            im = pil_get_frame(self._im, **self._kwargs)
             return im, self._im.info
         
         def _get_meta_data(self, index):
@@ -132,7 +171,7 @@ class PillowFormat(Format):
     
     class Writer(Format.Writer):
         
-        def _open(self, **kwargs):
+        def _open(self):
             Image = self.format._init_pillow()
             try:
                 self._save_func = Image.SAVE[self.format.plugin_id]
@@ -141,7 +180,6 @@ class PillowFormat(Format):
                                    self.format.name)
             self._fp = self.request.get_file()
             self._meta = {}
-            self._meta.update(kwargs)
             self._written = False
         
         def _close(self):
@@ -160,7 +198,7 @@ class PillowFormat(Format):
             if 'bits' in self._meta:
                 img = img.quantize()  # Make it a P image, so bits arg is used
             img.save(self._fp, format=self.format.plugin_id, **self._meta)
-            img.close()
+            save_pillow_close(img)
         
         def set_meta_data(self, meta):
             self._meta.update(meta)
@@ -175,6 +213,31 @@ class PNGFormat(PillowFormat):
     ----------------------
     ignoregamma : bool
         Avoid gamma correction. Default False.
+    pilmode : str
+        From the Pillow documentation:
+        
+        * 'L' (8-bit pixels, grayscale)
+        * 'P' (8-bit pixels, mapped to any other mode using a color palette)
+        * 'RGB' (3x8-bit pixels, true color)
+        * 'RGBA' (4x8-bit pixels, true color with transparency mask)
+        * 'CMYK' (4x8-bit pixels, color separation)
+        * 'YCbCr' (3x8-bit pixels, color video format)
+        * 'I' (32-bit signed integer pixels)
+        * 'F' (32-bit floating point pixels)
+        
+        PIL also provides limited support for a few special modes, including
+        'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
+        (true color with premultiplied alpha).
+        
+        When translating a color image to grayscale (mode 'L', 'I' or 'F'),
+        the library uses the ITU-R 601-2 luma transform::
+        
+            L = R * 299/1000 + G * 587/1000 + B * 114/1000
+    as_gray : bool
+        If True, the image is converted using mode 'F'. When `mode` is
+        not None and `as_gray` is True, the image is first converted
+        according to `mode`, and the result is then "flattened" using
+        mode 'F'.
     
     Parameters for saving
     ---------------------
@@ -206,12 +269,12 @@ class PNGFormat(PillowFormat):
         bits. In this case, given as a number between 1-256.
     dictionary (experimental): dict
         Set the ZLIB encoder dictionary.
-    
     """
     
     class Reader(PillowFormat.Reader):
-        def _open(self, ignoregamma=False):
-            return PillowFormat.Reader._open(self)
+        def _open(self, pilmode=None, as_gray=False, ignoregamma=False):
+            return PillowFormat.Reader._open(self,
+                                             pilmode=pilmode, as_gray=as_gray)
         
         def _get_data(self, index):
             im, info = PillowFormat.Reader._get_data(self, index)
@@ -223,7 +286,7 @@ class PNGFormat(PillowFormat):
                 else:
                     scale = float(65536 if im.dtype == np.uint16 else 255)
                     gain = 1.0
-                    im = ((im / scale) ** gamma) * scale * gain
+                    im[:] = ((im / scale) ** gamma) * scale * gain + 0.4999
             return im, info
                     
     # -- 
@@ -257,7 +320,8 @@ class PNGFormat(PillowFormat):
                 if key not in ok_keys:
                     raise TypeError('Invalid arg for PNG writer: %r' % key)
             
-            return PillowFormat.Writer._open(self, **kwargs)
+            PillowFormat.Writer._open(self)
+            self._meta.update(kwargs)
         
         def _append_data(self, im, meta):
             if str(im.dtype) == 'uint16' and (im.ndim == 2 or
@@ -277,6 +341,31 @@ class JPEGFormat(PillowFormat):
     ----------------------
     exifrotate : bool
         Automatically rotate the image according to exif flag. Default True.
+    pilmode : str
+        From the Pillow documentation:
+        
+        * 'L' (8-bit pixels, grayscale)
+        * 'P' (8-bit pixels, mapped to any other mode using a color palette)
+        * 'RGB' (3x8-bit pixels, true color)
+        * 'RGBA' (4x8-bit pixels, true color with transparency mask)
+        * 'CMYK' (4x8-bit pixels, color separation)
+        * 'YCbCr' (3x8-bit pixels, color video format)
+        * 'I' (32-bit signed integer pixels)
+        * 'F' (32-bit floating point pixels)
+        
+        PIL also provides limited support for a few special modes, including
+        'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
+        (true color with premultiplied alpha).
+        
+        When translating a color image to grayscale (mode 'L', 'I' or 'F'),
+        the library uses the ITU-R 601-2 luma transform::
+        
+            L = R * 299/1000 + G * 587/1000 + B * 114/1000
+    as_gray : bool
+        If True, the image is converted using mode 'F'. When `mode` is
+        not None and `as_gray` is True, the image is first converted
+        according to `mode`, and the result is then "flattened" using
+        mode 'F'.
     
     Parameters for saving
     ---------------------
@@ -289,7 +378,7 @@ class JPEGFormat(PillowFormat):
     optimize : bool
         On saving, compute optimal Huffman coding tables (can reduce a few
         percent of file size). Default False.
-    dpi : tuplw of int
+    dpi : tuple of int
         The pixel density, ``(x,y)``.
     icc_profile : object
         If present and true, the image is stored with the provided ICC profile.
@@ -304,8 +393,18 @@ class JPEGFormat(PillowFormat):
     """
     
     class Reader(PillowFormat.Reader):
-        def _open(self, exifrotate=True):
-            return PillowFormat.Reader._open(self)
+        def _open(self, pilmode=None, as_gray=False, exifrotate=True):
+            return PillowFormat.Reader._open(self,
+                                             pilmode=pilmode, as_gray=as_gray)
+        
+        def _get_file(self):
+            # Pillow uses seek for JPG, so we cannot directly stream from web
+            if self.request.filename.startswith(('http://', 'https://')):
+                self._we_own_fp = True
+                return open(self.request.get_local_filename(), 'rb')
+            else:
+                self._we_own_fp = False
+                return self.request.get_file()
         
         def _get_data(self, index):
             im, info = PillowFormat.Reader._get_data(self, index)
@@ -360,7 +459,8 @@ class JPEGFormat(PillowFormat):
             kwargs['progressive'] = bool(progressive)
             kwargs['optimize'] = bool(progressive)
             
-            return PillowFormat.Writer._open(self, **kwargs)
+            PillowFormat.Writer._open(self)
+            self._meta.update(kwargs)
         
         def _append_data(self, im, meta):
             if im.ndim == 3 and im.shape[-1] == 4:
@@ -370,6 +470,13 @@ class JPEGFormat(PillowFormat):
             return
 
 
+def save_pillow_close(im):
+    # see issue #216 and #300
+    if hasattr(im, 'close'):
+        if hasattr(getattr(im, 'fp', None), 'close'):
+            im.close()
+
+
 ## Func from skimage
 
 # This cells contains code from scikit-image, in particular from
@@ -383,7 +490,7 @@ def pil_try_read(im):
         # this will raise an IOError if the file is not readable
         im.getdata()[0]
     except IOError as e:
-        site = "http://pillow.readthedocs.org/en/latest/installation.html"
+        site = "http://pillow.readthedocs.io/en/latest/installation.html"
         site += "#external-libraries"
         pillow_error_message = str(e)
         error_message = ('Could not load "%s" \n'
@@ -406,68 +513,91 @@ def _palette_is_grayscale(pil_image):
     return np.allclose(np.diff(valid_palette), 0)
 
     
-def pil_get_frame(im, grayscale, dtype=None):
+def pil_get_frame(im, is_gray=None, as_gray=None, mode=None, dtype=None):
+    """ 
+    is_gray: Whether the image *is* gray (by inspecting its palette).
+    as_gray: Whether the resulting image must be converted to gaey.
+    mode: The mode to convert to.
+    """
+    
+    if is_gray is None:
+        is_gray = _palette_is_grayscale(im)
+    
     frame = im
     
-    if im.format == 'PNG' and im.mode == 'I' and dtype is None:
-        dtype = 'uint16'
-
-    if im.mode == 'P':
-        if grayscale is None:
-            grayscale = _palette_is_grayscale(im)
-
-        if grayscale:
-            frame = im.convert('L')
+    # Convert ...
+    if mode is not None:
+        # Mode is explicitly given ...
+        if mode != im.mode:
+            frame = im.convert(mode)
+    elif as_gray:
+        pass  # don't do any auto-conversions (but do the explit one above)
+    elif im.mode == 'P' and is_gray:
+        # Paletted images that are already gray by their palette
+        # are converted so that the resulting numpy array is 2D.
+        frame = im.convert('L')
+    elif im.mode == 'P':
+        # Paletted images are converted to RGB/RGBA. We jump some loops to make
+        # this work well.
+        if im.info.get('transparency', None) is not None:
+            # Let Pillow apply the transparency, see issue #210 and #246
+            frame = im.convert('RGBA')
+        elif im.palette.mode in ('RGB', 'RGBA'):
+            # We can do this ourselves. Pillow seems to sometimes screw
+            # this up if a  multi-gif has a pallete for each frame ...
+            # Create palette array
+            p = np.frombuffer(im.palette.getdata()[1], np.uint8)
+            # Shape it.
+            nchannels = len(im.palette.mode)
+            p.shape = -1, nchannels
+            if p.shape[1] == 3:
+                p = np.column_stack((p, 255*np.ones(p.shape[0], p.dtype)))
+            # Apply palette
+            frame_paletted = np.array(im, np.uint8)
+            try:
+                frame = p[frame_paletted]
+            except Exception:
+                # Ok, let PIL do it. The introduction of the branch that
+                # tests `im.info['transparency']` should make this happen
+                # much less often, but let's keep it, to be safe.
+                frame = im.convert('RGBA')
         else:
-            
-            if im.info.get('transparency', None) is not None:
-                # Let Pillow apply the transparency, see issue #210 and #246
+            # Let Pillow do it. Unlinke skimage, we always convert
+            # to RGBA; palettes can be RGBA.
+            if True:  # im.format == 'PNG' and 'transparency' in im.info:
                 frame = im.convert('RGBA')
-            elif im.palette.mode in ('RGB', 'RGBA'):
-                # We can do this ourselves. Pillow seems to sometimes screw
-                # this up if a  multi-gif has a pallete for each frame ...
-                # Create palette array
-                p = np.frombuffer(im.palette.getdata()[1], np.uint8)
-                # Shape it.
-                nchannels = len(im.palette.mode)
-                p.shape = -1, nchannels
-                if p.shape[1] == 3:
-                    p = np.column_stack((p, 255*np.ones(p.shape[0], p.dtype)))
-                # Apply palette
-                frame_paletted = np.array(im, np.uint8)
-                try:
-                    frame = p[frame_paletted]
-                except Exception:
-                    # Ok, let PIL do it. The introduction of the branch that
-                    # tests `im.info['transparency']` should make this happen
-                    # much less often, but let's keep it, to be safe.
-                    frame = im.convert('RGBA')
             else:
-                # Let Pillow do it. Unlinke skimage, we always convert
-                # to RGBA; palettes can be RGBA.
-                if True:  # im.format == 'PNG' and 'transparency' in im.info:
-                    frame = im.convert('RGBA')
-                else:
-                    frame = im.convert('RGB')
-
-    elif im.mode == '1':
-        frame = im.convert('L')
-
+                frame = im.convert('RGB')
     elif 'A' in im.mode:
         frame = im.convert('RGBA')
-
     elif im.mode == 'CMYK':
         frame = im.convert('RGB')
-
+    
+    # Apply a post-convert if necessary
+    if as_gray:
+        frame = frame.convert('F')  # Scipy compat
+    elif not isinstance(frame, np.ndarray) and frame.mode == '1':
+        # Workaround for crash in PIL. When im is 1-bit, the call array(im)
+        # can cause a segfault, or generate garbage. See
+        # https://github.com/scipy/scipy/issues/2138 and
+        # https://github.com/python-pillow/Pillow/issues/350.
+        #
+        # This converts im from a 1-bit image to an 8-bit image. 
+        frame = frame.convert('L')
+    
+    # Convert to numpy array
     if im.mode.startswith('I;16'):
+        # e.g. in16 PNG's
         shape = im.size
         dtype = '>u2' if im.mode.endswith('B') else '<u2'
         if 'S' in im.mode:
             dtype = dtype.replace('u', 'i')
         frame = np.fromstring(frame.tobytes(), dtype)
         frame.shape = shape[::-1]
-
     else:
+        # Use uint16 for PNG's in mode I
+        if im.format == 'PNG' and im.mode == 'I' and dtype is None:
+            dtype = 'uint16'
         frame = np.array(frame, dtype=dtype)
 
     return frame
@@ -533,7 +663,7 @@ def register_pillow_formats():
         format = FormatCls(id + '-PIL', summary, ext, FormatCls._modes)
         format._plugin_id = id
         if FormatCls is PillowFormat or not FormatCls.__doc__:
-            format.__doc__ = pillow_docs[id]
+            format.__doc__ = pillow_docs[id] + GENERIC_DOCS
         formats.add_format(format)
 
 
diff --git a/imageio/plugins/pillow_info.py b/imageio/plugins/pillow_info.py
index a799307..3967df2 100644
--- a/imageio/plugins/pillow_info.py
+++ b/imageio/plugins/pillow_info.py
@@ -64,7 +64,7 @@ def generate_info():  # pragma: no cover
     # Fill in the blancs
     for id in ids:
         if id in docs:
-            docs[id] = '*This is a copy from the Pillow docs.*\n\n' + docs[id]
+            docs[id] = '*From the Pillow docs:*\n\n' + docs[id]
         else:
             docs[id] = 'No docs for %s.' % id
             print('no docs for', id)
@@ -150,7 +150,7 @@ pillow_formats = [
 
 pillow_docs = {
 'BMP':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
@@ -164,7 +164,7 @@ u"""*This is a copy from the Pillow docs.*
         Set to ``bmp_rle`` if the file is run-length encoded.
     """,
 'BUFR':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  1.1.3
@@ -175,14 +175,14 @@ u"""*This is a copy from the Pillow docs.*
     :py:func:`PIL.BufrStubImagePlugin.register_handler`.
     """,
 'CUR':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     CUR is used to store cursors on Windows. The CUR decoder reads the largest
     available cursor. Animated cursors are not supported.
     """,
 'DCX':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     DCX is a container file format for PCX files, defined by Intel. The DCX format
@@ -194,7 +194,7 @@ u"""*This is a copy from the Pillow docs.*
     
     """,
 'DDS':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     DDS is a popular container texture format used in video games and natively
@@ -207,7 +207,7 @@ u"""*This is a copy from the Pillow docs.*
 'DIB':
 u"""No docs for DIB.""",
 'EPS':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies EPS files containing image data, and can read files that contain
@@ -233,7 +233,7 @@ u"""*This is a copy from the Pillow docs.*
             im.size #(200,200)
     """,
 'FITS':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  1.1.5
@@ -246,7 +246,7 @@ u"""*This is a copy from the Pillow docs.*
 'FLI':
 u"""No docs for FLI.""",
 'FPX':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads Kodak FlashPix files. In the current version, only the highest
@@ -260,7 +260,7 @@ u"""*This is a copy from the Pillow docs.*
         README for details.
     """,
 'FTEX':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  3.2.0
@@ -270,7 +270,7 @@ u"""*This is a copy from the Pillow docs.*
     per file, in the compressed and uncompressed formats.
     """,
 'GBR':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     The GBR decoder reads GIMP brush files, version 1 and 2.
@@ -299,7 +299,7 @@ u"""*This is a copy from the Pillow docs.*
         transparent.
     """,
 'GIF':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads GIF87a and GIF89a versions of the GIF file format. The library writes
@@ -353,6 +353,7 @@ u"""*This is a copy from the Pillow docs.*
     **append_images**
         A list of images to append as additional frames. Each of the
         images in the list can be single or multiframe images.
+        This is currently only supported for GIF, PDF, TIFF, and WebP.
     
     **duration**
         The display duration of each frame of the multiframe gif, in
@@ -367,13 +368,24 @@ u"""*This is a copy from the Pillow docs.*
         eliminating unused colors. This is only useful if the palette can
         be compressed to the next smaller power of 2 elements.
     
-    **palette** 
+    **palette**
         Use the specified palette for the saved image. The palette should
         be a bytes or bytearray object containing the palette entries in
         RGBRGB... form. It should be no more than 768 bytes. Alternately,
         the palette can be passed in as an
         :py:class:`PIL.ImagePalette.ImagePalette` object.
     
+    **disposal**
+        Indicates the way in which the graphic is to be treated after being displayed.
+    
+        * 0 - No disposal specified.
+        * 1 - Do not dispose.
+        * 2 - Restore to background color.
+        * 3 - Restore to previous content.
+    
+         Pass a single integer for a constant disposal, or a list or tuple
+         to set the disposal for each frame separately.
+    
     Reading local images
     ~~~~~~~~~~~~~~~~~~~~
     
@@ -392,7 +404,7 @@ u"""*This is a copy from the Pillow docs.*
             im.tile = [(tag, (0, 0) + im.size, offset, extra)]
     """,
 'GRIB':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  1.1.5
@@ -407,7 +419,7 @@ u"""*This is a copy from the Pillow docs.*
     :py:func:`PIL.GribStubImagePlugin.register_handler`.
     """,
 'HDF5':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  1.1.5
@@ -418,7 +430,7 @@ u"""*This is a copy from the Pillow docs.*
     :py:func:`PIL.Hdf5StubImagePlugin.register_handler`.
     """,
 'ICNS':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and (macOS only) writes macOS ``.icns`` files.  By default, the
@@ -438,7 +450,7 @@ u"""*This is a copy from the Pillow docs.*
         :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``).
     """,
 'ICO':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     ICO is used to store icons on Windows. The largest available icon is read.
@@ -448,8 +460,8 @@ u"""*This is a copy from the Pillow docs.*
     **sizes**
         A list of sizes including in this ico file; these are a 2-tuple,
         ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48),
-        (64, 64), (128, 128), (255, 255)]``. Any sizes bigger than the original
-        size or 255 will be ignored.
+        (64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original
+        size or 256 will be ignored.
     
     IM
     ^^
@@ -463,7 +475,7 @@ u"""*This is a copy from the Pillow docs.*
 'IM':
 u"""No docs for IM.""",
 'IMT':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads Image Tools images containing ``L`` data.
@@ -471,7 +483,7 @@ u"""*This is a copy from the Pillow docs.*
 'IPTC':
 u"""No docs for IPTC.""",
 'JPEG':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or
@@ -556,11 +568,11 @@ u"""*This is a copy from the Pillow docs.*
         If present, sets the subsampling for the encoder.
     
         * ``keep``: Only valid for JPEG files, will retain the original image setting.
-        * ``4:4:4``, ``4:2:2``, ``4:1:1``: Specific sampling values
+        * ``4:4:4``, ``4:2:2``, ``4:2:0``: Specific sampling values
         * ``-1``: equivalent to ``keep``
         * ``0``: equivalent to ``4:4:4``
         * ``1``: equivalent to ``4:2:2``
-        * ``2``: equivalent to ``4:1:1``
+        * ``2``: equivalent to ``4:2:0``
     
     **qtables**
         If present, sets the qtables for the encoder. This is listed as an
@@ -582,7 +594,7 @@ u"""*This is a copy from the Pillow docs.*
         details.
     """,
 'JPEG2000':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     .. versionadded:: Pillow  2.4.0
@@ -671,27 +683,29 @@ u"""*This is a copy from the Pillow docs.*
        ``_imaging`` DLL).
     """,
 'MCIDAS':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies and reads 8-bit McIdas area files.
     """,
 'MIC':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the
     first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
     :py:meth:`~file.tell` to read other sprites from the file.
+    
+    Note that there may be an embedded gamma of 2.2 in MIC files.
     """,
 'MPEG':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies MPEG files.
     """,
 'MPO':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary
@@ -700,14 +714,14 @@ u"""*This is a copy from the Pillow docs.*
     zero-indexed and random access is supported.
     """,
 'MSP':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies and reads MSP files from Windows 1 and 2. The library writes
     uncompressed (Windows 1) versions of this format.
     """,
 'PCD':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads PhotoCD files containing ``RGB`` data. This only reads the 768x512
@@ -715,13 +729,13 @@ u"""*This is a copy from the Pillow docs.*
     encoding.
     """,
 'PCX':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.
     """,
 'PIXAR':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL provides limited support for PIXAR raster files. The library can identify
@@ -730,7 +744,7 @@ u"""*This is a copy from the Pillow docs.*
     The format code is ``PIXAR``.
     """,
 'PNG':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``,
@@ -739,9 +753,22 @@ u"""*This is a copy from the Pillow docs.*
     The :py:meth:`~PIL.Image.Image.write` method sets the following
     :py:attr:`~PIL.Image.Image.info` properties, when appropriate:
     
+    **chromaticity**
+        The chromaticity points, as an 8 tuple of floats. (``White Point
+        X``, ``White Point Y``, ``Red X``, ``Red Y``, ``Green X``, ``Green
+        Y``, ``Blue X``, ``Blue Y``)
+    
     **gamma**
         Gamma, given as a floating point number.
     
+    **srgb**
+        The sRGB rendering intent as an integer.
+    
+          * 0 Perceptual
+          * 1 Relative Colorimetric
+          * 2 Saturation
+          * 3 Absolute Colorimetric
+    
     **transparency**
         For ``P`` images: Either the palette index for full transparent pixels,
         or a byte string with alpha values for each palette entry.
@@ -802,28 +829,28 @@ u"""*This is a copy from the Pillow docs.*
         documentation for details.
     """,
 'PPM':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and writes PBM, PGM and PPM files containing ``1``, ``L`` or ``RGB``
     data.
     """,
 'PSD':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
     
     """,
 'SGI':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     Pillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files.
     
     """,
 'SPIDER':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and writes SPIDER image files of 32-bit floating point data
@@ -866,22 +893,27 @@ u"""*This is a copy from the Pillow docs.*
 'SUN':
 u"""No docs for SUN.""",
 'TGA':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads 24- and 32-bit uncompressed and run-length encoded TGA files.
     """,
 'TIFF':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
-    PIL reads and writes TIFF files. It can read both striped and tiled images,
-    pixel and plane interleaved multi-band images, and either uncompressed, or
-    Packbits, LZW, or JPEG compressed images.
+    Pillow reads and writes TIFF files. It can read both striped and tiled
+    images, pixel and plane interleaved multi-band images. If you have
+    libtiff and its headers installed, PIL can read and write many kinds
+    of compressed TIFF files. If not, PIL will only read and write
+    uncompressed files.
+    
+    .. note::
     
-    If you have libtiff and its headers installed, PIL can read and write many more
-    kinds of compressed TIFF files. If not, PIL will always write uncompressed
-    files.
+        Beginning in version 5.0.0, Pillow requires libtiff to read or
+        write compressed files. Prior to that release, Pillow had buggy
+        support for reading Packbits, LZW and JPEG compressed TIFFs
+        without using libtiff.
     
     The :py:meth:`~PIL.Image.Image.write` method sets the following
     :py:attr:`~PIL.Image.Image.info` properties:
@@ -1000,7 +1032,7 @@ u"""*This is a copy from the Pillow docs.*
     
     """,
 'WMF':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL can identify playable WMF files.
@@ -1032,13 +1064,13 @@ u"""*This is a copy from the Pillow docs.*
     
         im = Image.open("sample.wmf")""",
 'XBM':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads and writes X bitmap files (mode ``1``).
     """,
 'XPM':
-u"""*This is a copy from the Pillow docs.*
+u"""*From the Pillow docs:*
 
     
     PIL reads X pixmap files (mode ``P``) with 256 colors or less.
diff --git a/setup.py b/setup.py
index 578ec64..405ab4b 100644
--- a/setup.py
+++ b/setup.py
@@ -73,7 +73,7 @@ for line in open(initFile).readlines():
         elif docStatus == 1:
             docStatus = 2
     if docStatus == 1:
-        __doc__ += line
+        __doc__ += line.rstrip() + '\n'
 
 # Template for long description. __doc__ gets inserted here
 long_description = """
@@ -85,7 +85,7 @@ long_description = """
 
 __doc__
 
-Release notes: http://imageio.readthedocs.org/en/latest/releasenotes.html
+Release notes: http://imageio.readthedocs.io/en/latest/releasenotes.html
 
 Example:
 
@@ -97,8 +97,8 @@ Example:
     (512, 512, 3)
     >>> imageio.imwrite('astronaut-gray.jpg', im[:, :, 0])
 
-See the `user API <http://imageio.readthedocs.org/en/latest/userapi.html>`_
-or `examples <http://imageio.readthedocs.org/en/latest/examples.html>`_
+See the `user API <http://imageio.readthedocs.io/en/latest/userapi.html>`_
+or `examples <http://imageio.readthedocs.io/en/latest/examples.html>`_
 for more information.
 """
 
diff --git a/tasks/docs.py b/tasks/docs.py
index 252b96d..7d24344 100644
--- a/tasks/docs.py
+++ b/tasks/docs.py
@@ -44,6 +44,7 @@ def sphinx_build(src_dir, build_dir):
            ]
     
     if sphinx.version_info > (1, 7):
+        import sphinx.cmd.build
         ret = sphinx.cmd.build.build_main(cmd)
     else:
         ret = sphinx.build_main(['sphinx-build'] + cmd)
diff --git a/tasks/install_python.ps1 b/tasks/install_python.ps1
index 6ec6634..652aa91 100644
--- a/tasks/install_python.ps1
+++ b/tasks/install_python.ps1
@@ -96,10 +96,10 @@ function InstallPip ($python_home) {
 
 function DownloadMiniconda ($python_version, $platform_suffix) {
     $webclient = New-Object System.Net.WebClient
-    if ($python_version -eq "3.5") {
-        $filename = "Miniconda3-4.0.5-Windows-" + $platform_suffix + ".exe"
+    if ($python_version -eq "3.6") {
+        $filename = "Miniconda3-4.4.10-Windows-" + $platform_suffix + ".exe"
     } else {
-        $filename = "Miniconda2-4.0.5-Windows-" + $platform_suffix + ".exe"
+        $filename = "Miniconda2-4.4.10-Windows-" + $platform_suffix + ".exe"
     }
     $url = $MINICONDA_URL + $filename
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 707569f..d5b1c2f 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -487,7 +487,14 @@ def test_util_image_as_uint():
         (np.array([0, 1], 'float64'), 16, np.uint16([0, 65535])),
         (np.array([-1.0, 1.0], 'float16'), 16, np.uint16([0, 65535])),
         (np.array([-1.0, 1.0], 'float32'), 16, np.uint16([0, 65535])),
-        (np.array([-1.0, 1.0], 'float64'), 16, np.uint16([0, 65535])),)
+        (np.array([-1.0, 1.0], 'float64'), 16, np.uint16([0, 65535])),
+        # Rounding
+        (np.array([1.4/255, 1.6/255], 'float32'), 8, np.uint8([1, 2])),
+        (np.array([254.4/255, 254.6/255], 'float32'), 8, np.uint8([254, 255])),
+        (np.array([1.4/65535, 1.6/65535], 'float32'), 16, np.uint16([1, 2])),
+        (np.array([65534.4/65535, 65534.6/65535], 'float32'), 16,
+            np.uint16([65534, 65535])),  # noqa
+        )
 
     for tup in test_arrays:
         res = core.image_as_uint(tup[0], bitdepth=tup[1])
diff --git a/tests/test_meta.py b/tests/test_meta.py
index f35626c..6623869 100644
--- a/tests/test_meta.py
+++ b/tests/test_meta.py
@@ -143,7 +143,8 @@ def test_import_dependencies():
         else:
             print(modname, mod.__file__)
     
-    # Check that only imageio is left
+    # Check that only imageio is left (Windows needs a little help)
+    extra_modules.difference_update(['pythoncom', 'pywintypes', 'win32com'])
     assert extra_modules == {'imageio'}
 
 
diff --git a/tests/test_pillow.py b/tests/test_pillow.py
index 9c6c510..c0c558a 100644
--- a/tests/test_pillow.py
+++ b/tests/test_pillow.py
@@ -329,6 +329,46 @@ def test_images_with_transparency():
     assert im.shape == (24, 30, 4)
 
 
+def test_regression_302():
+    # When using gamma correction, the result should keep the same dtype
+    need_internet()
+    
+    fname = get_remote_file('images/kodim03.png')
+    im = imageio.imread(fname)
+    assert im.shape == (512, 768, 3) and im.dtype == 'uint8'
+
+    
+def test_scipy_imread_compat():
+    # https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html
+    # https://github.com/scipy/scipy/blob/41a3e69ca3141d8bf996bccb5eca5fc7bbc21a51/scipy/misc/pilutil.py#L111
+    
+    im = imageio.imread('imageio:chelsea.png')
+    assert im.shape == (300, 451, 3) and im.dtype == 'uint8'
+    
+    # Scipy users may default to using "mode", but our getreader() already has
+    # a "mode" argument, so they should use pilmode instead.
+    try:
+        im = imageio.imread('imageio:chelsea.png', mode='L')
+    except TypeError as err:
+        assert 'pilmode' in str(err)
+    
+    im = imageio.imread('imageio:chelsea.png', pilmode='RGBA')
+    assert im.shape == (300, 451, 4) and im.dtype == 'uint8'
+    
+    im = imageio.imread('imageio:chelsea.png', pilmode='L')
+    assert im.shape == (300, 451) and im.dtype == 'uint8'
+    
+    im = imageio.imread('imageio:chelsea.png', pilmode='F')
+    assert im.shape == (300, 451) and im.dtype == 'float32'
+    
+    im = imageio.imread('imageio:chelsea.png', as_gray=True)
+    assert im.shape == (300, 451) and im.dtype == 'float32'
+    
+    # Force using pillow (but really, Pillow's imageio's first choice! Except
+    # for tiff)
+    im = imageio.imread('imageio:chelsea.png', 'PNG-PIL')
+
+
 if __name__ == '__main__':
     # test_png()
     # test_animated_gif()