[imageio] 02/05: Imported Upstream version 1.1

Ghislain Vaillant ghisvail-guest at moszumanska.debian.org
Wed Feb 4 23:24:18 UTC 2015


This is an automated email from the git hooks/post-receive script.

ghisvail-guest pushed a commit to branch master
in repository imageio.

commit 1167d7680ae08ef3ec05d23a7c128ddafcf1f5cc
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date:   Wed Feb 4 23:14:47 2015 +0000

    Imported Upstream version 1.1
---
 CONTRIBUTORS.txt                            |  11 +
 LICENSE                                     |  24 +
 PKG-INFO                                    |  12 +-
 README.md                                   | 101 ++++
 docs/.requirements                          |   1 +
 docs/Makefile                               | 153 +++++
 docs/conf.py                                | 252 +++++++++
 docs/devapi.rst                             |  17 +
 docs/examples.rst                           | 117 ++++
 docs/ext/docscrape.py                       | 515 +++++++++++++++++
 docs/ext/docscrape_sphinx.py                | 238 ++++++++
 docs/ext/imageio_ext.py                     | 221 ++++++++
 docs/ext/numpydoc.py                        | 178 ++++++
 docs/index.rst                              |  14 +
 docs/installation.rst                       |  25 +
 docs/make.bat                               | 190 +++++++
 docs/releasenotes.rst                       | 172 ++++++
 docs/sec_developer.rst                      |   9 +
 docs/sec_gettingstarted.rst                 |  10 +
 docs/sec_reference.rst                      |  10 +
 docs/userapi.rst                            |  49 ++
 imageio/__init__.py                         |  13 +-
 imageio/core/__init__.py                    |   6 +-
 imageio/core/fetching.py                    | 118 ++--
 imageio/core/findlib.py                     |   2 +-
 imageio/core/format.py                      |  65 +--
 imageio/core/functions.py                   | 119 ++--
 imageio/core/request.py                     |   8 +-
 imageio/core/util.py                        |  72 ++-
 imageio/plugins/__init__.py                 |  10 +-
 imageio/plugins/_freeimage.py               |  76 ++-
 imageio/plugins/_swf.py                     |   4 +-
 imageio/plugins/avbin.py                    |  29 +-
 imageio/plugins/dicom.py                    |  10 +-
 imageio/plugins/example.py                  |  39 +-
 imageio/plugins/ffmpeg.py                   |  55 +-
 imageio/plugins/freeimage.py                |  20 +-
 imageio/plugins/freeimagemulti.py           |  23 +-
 imageio/plugins/npz.py                      |  10 +-
 imageio/plugins/swf.py                      |  11 +-
 imageio/resources/shipped_resources_go_here |   0
 imageio/testing.py                          |   8 +-
 make/README.md                              |  12 +
 make/__init__.py                            |   6 +
 make/__main__.py                            |  15 +
 make/maker.py                               | 257 +++++++++
 setup.py                                    | 212 ++++++-
 tests/README.md                             |  16 +
 tests/test_avbin.py                         | 184 ++++++
 tests/test_core.py                          | 849 ++++++++++++++++++++++++++++
 tests/test_dicom.py                         | 191 +++++++
 tests/test_ffmpeg.py                        | 269 +++++++++
 tests/test_freeimage.py                     | 466 +++++++++++++++
 tests/test_freeimage_suite.py               |  95 ++++
 tests/test_meta.py                          | 147 +++++
 tests/test_npz.py                           |  85 +++
 tests/test_swf.py                           | 181 ++++++
 57 files changed, 5722 insertions(+), 280 deletions(-)

diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
new file mode 100644
index 0000000..2989817
--- /dev/null
+++ b/CONTRIBUTORS.txt
@@ -0,0 +1,11 @@
+
+- Almar Klein
+  Plugin system
+  Most plugins so far
+
+- Rob Reilink
+  AvBin plugin
+
+- Zach Pincus and others from the scikit-image team
+  Initial version of freeimage plugin
+  Initial code for findlib and lib downloading
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c580e3c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2014, imageio developers
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/PKG-INFO b/PKG-INFO
index 20b9124..aba2c38 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: imageio
-Version: 1.0
+Version: 1.1
 Summary: Library for reading and writing a wide range of image formats.
 Home-page: http://imageio.github.io/
 Author: imageio contributors
@@ -33,13 +33,19 @@ Description:
             >>> im = imageio.imread('astronaut.png')
             >>> im.shape  # im is a numpy array
             (512, 512, 3)
-            >>> imageio.imsave('astronaut-gray.jpg', im[:, :, 0])
+            >>> 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>`_
         for more information.
         
-Keywords: image imread imsave io animation volume FreeImage ffmpeg
+        All distribution files are independent of the Python version. The
+        platform-specific archives contain a few images and the freeimage
+        library for that platform. These are recommended if you do not want to
+        rely on an internet connection at runtime / install-time.
+        
+        
+Keywords: image imread imwrite io animation volume FreeImage ffmpeg
 Platform: any
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Science/Research
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4964738
--- /dev/null
+++ b/README.md
@@ -0,0 +1,101 @@
+# IMAGEIO
+
+[![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)
+[![Pypi downloads](https://pypip.in/d/imageio/badge.png)](https://pypi.python.org/pypi/imageio)
+[![Documentation Status](https://readthedocs.org/projects/imageio/badge/?version=latest)](https://readthedocs.org/projects/imageio/?badge=latest)
+   
+Website: http://imageio.github.io
+
+<!-- From below ends up on the website Keep this ---- DIVIDER ---- -->
+
+<p class='summary'>
+Imageio is a Python library that provides an easy interface to read and
+write a wide range of image data, including animated images, volumetric
+data, and scientific formats. It is cross-platform, runs on Python 2.x
+and 3.x, and is easy to install.
+</p>
+
+<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>.
+<pre>
+>>> import imageio
+>>> im = imageio.imread('chelsea.png')
+>>> im.shape  # im is a numpy array
+(300, 451, 3)
+>>> imageio.imwrite('chelsea-gray.jpg', im[:, :, 0])
+</pre>
+
+<h2>API in a nutshell</h2>
+As a user, you just have to remember a handfull of functions:
+
+<ul>
+    <li>imread() and imwrite() - for single images</li>
+    <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>
+</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>Pure Python, runs on Python 2.6+, 3.x, 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>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>
+</ul>
+
+
+<h2>Details</h2>
+<p>
+Imageio has a relatively simple core that provides a common interface
+to different file formats. This core takes care of reading from different
+sources (like http), and exposes a simple API for the plugins to access
+the raw data. All file formats are implemented in plugins. Additional
+plugins can easily be registered.
+</p><p>
+Some plugins rely on external libraries (e.g. freeimage). These are
+automatically downloaded when needed and 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!
+</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).
+</p>
+
+
+<h2>Origin and outlook</h2>
+<p>
+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 for a good image io library, which is an easy dependency, easy to
+maintain, and scalable to exotic file formats.
+</p><p>
+Imageio started out with the FreeImage plugin of the scikit-image
+project, through which it was able to support a lot of common formats.
+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
+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.
+</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
+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/docs/.requirements b/docs/.requirements
new file mode 100644
index 0000000..50daa83
--- /dev/null
+++ b/docs/.requirements
@@ -0,0 +1 @@
+numpy>=1.6
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..39a0782
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/imageio.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/imageio.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/imageio"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/imageio"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..4ae29c6
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+#
+# imageio documentation build configuration file, created by
+# sphinx-quickstart on Thu May 17 16:58:47 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('ext'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
+              #'sphinx.ext.autosummary', 'sphinx.ext.pngmath',
+              'numpydoc',
+              'imageio_ext', ]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'imageio'
+copyright = u'2015, 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
+# built documents.
+#
+# The short X.Y version.
+import imageio
+# The short X.Y version.
+version = imageio.__version__[:3]
+# The full version, including alpha/beta/rc tags.
+release = imageio.__version__
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []  # ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'imageiodoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'imageio.tex', u'imageio Documentation',
+   u'imageio contributors', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'imageio', u'imageio Documentation',
+     [u'imageio contributors'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'imageio', u'imageio Documentation',
+   u'imageio contributors', 'imageio', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
\ No newline at end of file
diff --git a/docs/devapi.rst b/docs/devapi.rst
new file mode 100644
index 0000000..9a69886
--- /dev/null
+++ b/docs/devapi.rst
@@ -0,0 +1,17 @@
+-----------------------
+Imageio's developer API
+-----------------------
+
+..
+    We just import the whole of imageio.core 
+    The imageio.core docstring and __all__ are modified in
+    at the init of documenation building to make this show
+    all members and give a nice overview.
+
+This page lists the developer documentation for imageio. Normal users
+will generally not need this, except perhaps the :class:`.Format` class.
+All these functions and classes are available in the ``imageio.core``
+namespace.
+
+.. automodule:: imageio.core
+    :members:
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000..5e61343
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,117 @@
+Imageio usage examples
+======================
+
+Some of these examples use Visvis to visualize the image data. This
+should soon be replaced with Vispy. One can also use Matplotlib to show
+the images.
+
+Imageio provides a range of :doc:`example images <standardimages>`, 
+which are automatically downloaded. Therefore most examples below should
+just work.
+
+
+Read an image of a cat
+----------------------
+
+Probably the most important thing you ever need. 
+
+.. code-block:: python
+
+    import imageio
+    
+    im = imageio.imread('chelsea.png')
+    print(im.shape)
+
+
+Read from fancy sources
+-----------------------
+
+Imageio can read from filenames, file objects, http, zipfiles and bytes.
+
+.. code-block:: python
+
+    import imageio
+    import visvis as vv
+    
+    im = imageio.imread('http://upload.wikimedia.org/wikipedia/commons/d/de/Wikipedia_Logo_1.0.png')
+    vv.imshow(im)
+
+
+Iterate over frames in a movie
+------------------------------
+
+.. code-block:: python
+
+    import imageio
+    
+    reader = imageio.get_reader('cockatoo.mp4')
+    for i, im in enumerate(reader):
+        print('Mean of frame %i is %1.1f' % (i, im.mean()))
+
+
+Grab frames from your webcam
+----------------------------
+
+Use the special ``<video0>`` uri to read frames from your webcam (via
+the ffmpeg plugin). You can replace the zero with another index in case
+you have multiple cameras attached.
+
+.. code-block:: python
+
+    import imageio
+    import visvis as vv
+    
+    reader = imageio.get_reader('<video0>')
+    t = vv.imshow(reader.get_next_data(), clim=(0, 255))
+    for im in reader:
+        vv.processEvents()
+        t.SetData(im)
+
+
+Convert a movie
+------------------------------
+
+Here we take a movie and convert it to gray colors. Of course, you
+can apply any kind of (image) processing to the image here ...
+
+.. code-block:: python
+
+    import imageio
+    
+    reader = imageio.get_reader('cockatoo.mp4')
+    fps = reader.get_meta_data()['fps']
+    
+    writer = imageio.get_writer('~/cockatoo_gray.mp4', fps=fps)
+    
+    for im in reader:
+        writer.append_data(im[:, :, 1])
+    writer.close()
+
+
+
+Read medical data (DICOM)
+-------------------------
+
+.. code-block:: python
+
+    import imageio
+    dirname = 'path/to/dicom/files'
+    
+    # Read as loose images
+    ims = imageio.mimread(dirname, 'DICOM')
+    # Read as volume
+    vol = imageio.volread(dirname, 'DICOM')
+    # Read multiple volumes (multiple DICOM series)
+    vols = imageio.mvolread(dirname, 'DICOM')
+
+
+Volume data
+-----------
+
+.. code-block:: python
+    
+    import imageio
+    import visvis as vv
+    
+    vol = imageio.volread('stent.npz')
+    vv.volshow(vol)
diff --git a/docs/ext/docscrape.py b/docs/ext/docscrape.py
new file mode 100644
index 0000000..1b4757c
--- /dev/null
+++ b/docs/ext/docscrape.py
@@ -0,0 +1,515 @@
+"""Extract reference documentation from the NumPy source tree.
+
+"""
+
+import inspect
+import textwrap
+import re
+import pydoc
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
+from warnings import warn
+
+
+class Reader(object):
+
+    """A line-based string reader.
+
+    """
+
+    def __init__(self, data):
+        """
+        Parameters
+        ----------
+        data : str
+           String with lines separated by '\n'.
+
+        """
+        if isinstance(data, list):
+            self._str = data
+        else:
+            self._str = data.split('\n')  # store string as list of lines
+
+        self.reset()
+
+    def __getitem__(self, n):
+        return self._str[n]
+
+    def reset(self):
+        self._l = 0  # current line nr
+
+    def read(self):
+        if not self.eof():
+            out = self[self._l]
+            self._l += 1
+            return out
+        else:
+            return ''
+
+    def seek_next_non_empty_line(self):
+        for l in self[self._l:]:
+            if l.strip():
+                break
+            else:
+                self._l += 1
+
+    def eof(self):
+        return self._l >= len(self._str)
+
+    def read_to_condition(self, condition_func):
+        start = self._l
+        for line in self[start:]:
+            if condition_func(line):
+                return self[start:self._l]
+            self._l += 1
+            if self.eof():
+                return self[start:self._l + 1]
+        return []
+
+    def read_to_next_empty_line(self):
+        self.seek_next_non_empty_line()
+
+        def is_empty(line):
+            return not line.strip()
+        return self.read_to_condition(is_empty)
+
+    def read_to_next_unindented_line(self):
+        def is_unindented(line):
+            return (line.strip() and (len(line.lstrip()) == len(line)))
+        return self.read_to_condition(is_unindented)
+
+    def peek(self, n=0):
+        if self._l + n < len(self._str):
+            return self[self._l + n]
+        else:
+            return ''
+
+    def is_empty(self):
+        return not ''.join(self._str).strip()
+
+
+class NumpyDocString(object):
+
+    def __init__(self, docstring, config={}):
+        docstring = textwrap.dedent(docstring).split('\n')
+
+        self._doc = Reader(docstring)
+        self._parsed_data = {
+            'Signature': '',
+            'Summary': [''],
+            'Extended Summary': [],
+            'Parameters': [],
+            'Returns': [],
+            'Raises': [],
+            'Warns': [],
+            'Other Parameters': [],
+            'Attributes': [],
+            'Methods': [],
+            'See Also': [],
+            'Notes': [],
+            'Warnings': [],
+            'References': '',
+            'Examples': '',
+            'index': {}
+        }
+
+        self._parse()
+
+    def __getitem__(self, key):
+        return self._parsed_data[key]
+
+    def __setitem__(self, key, val):
+        if not key in self._parsed_data:
+            warn("Unknown section %s" % key)
+        else:
+            self._parsed_data[key] = val
+
+    def _is_at_section(self):
+        self._doc.seek_next_non_empty_line()
+
+        if self._doc.eof():
+            return False
+
+        l1 = self._doc.peek().strip()  # e.g. Parameters
+
+        if l1.startswith('.. index::'):
+            return True
+
+        l2 = self._doc.peek(1).strip()  # ---------- or ==========
+        return l2.startswith('-' * len(l1)) or l2.startswith('=' * len(l1))
+
+    def _strip(self, doc):
+        i = 0
+        j = 0
+        for i, line in enumerate(doc):
+            if line.strip():
+                break
+
+        for j, line in enumerate(doc[::-1]):
+            if line.strip():
+                break
+
+        return doc[i:len(doc) - j]
+
+    def _read_to_next_section(self):
+        section = self._doc.read_to_next_empty_line()
+
+        while not self._is_at_section() and not self._doc.eof():
+            if not self._doc.peek(-1).strip():  # previous line was empty
+                section += ['']
+
+            section += self._doc.read_to_next_empty_line()
+
+        return section
+
+    def _read_sections(self):
+        while not self._doc.eof():
+            data = self._read_to_next_section()
+            name = data[0].strip()
+
+            if name.startswith('..'):  # index section
+                yield name, data[1:]
+            elif len(data) < 2:
+                yield StopIteration
+            else:
+                yield name, self._strip(data[2:])
+
+    def _parse_param_list(self, content):
+        r = Reader(content)
+        params = []
+        while not r.eof():
+            header = r.read().strip()
+            if ' : ' in header:
+                arg_name, arg_type = header.split(' : ')[:2]
+            else:
+                arg_name, arg_type = header, ''
+
+            desc = r.read_to_next_unindented_line()
+            desc = dedent_lines(desc)
+
+            params.append((arg_name, arg_type, desc))
+
+        return params
+
+    _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|"
+                           r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
+
+    def _parse_see_also(self, content):
+        """
+        func_name : Descriptive text
+            continued text
+        another_func_name : Descriptive text
+        func_name1, func_name2, :meth:`func_name`, func_name3
+
+        """
+        items = []
+
+        def parse_item_name(text):
+            """Match ':role:`name`' or 'name'"""
+            m = self._name_rgx.match(text)
+            if m:
+                g = m.groups()
+                if g[1] is None:
+                    return g[3], None
+                else:
+                    return g[2], g[1]
+            raise ValueError("%s is not a item name" % text)
+
+        def push_item(name, rest):
+            if not name:
+                return
+            name, role = parse_item_name(name)
+            items.append((name, list(rest), role))
+            del rest[:]
+
+        current_func = None
+        rest = []
+
+        for line in content:
+            if not line.strip():
+                continue
+
+            m = self._name_rgx.match(line)
+            if m and line[m.end():].strip().startswith(':'):
+                push_item(current_func, rest)
+                current_func, line = line[:m.end()], line[m.end():]
+                rest = [line.split(':', 1)[1].strip()]
+                if not rest[0]:
+                    rest = []
+            elif not line.startswith(' '):
+                push_item(current_func, rest)
+                current_func = None
+                if ',' in line:
+                    for func in line.split(','):
+                        push_item(func, [])
+                elif line.strip():
+                    current_func = line
+            elif current_func is not None:
+                rest.append(line.strip())
+        push_item(current_func, rest)
+        return items
+
+    def _parse_index(self, section, content):
+        """
+        .. index: default
+           :refguide: something, else, and more
+
+        """
+        def strip_each_in(lst):
+            return [s.strip() for s in lst]
+
+        out = {}
+        section = section.split('::')
+        if len(section) > 1:
+            out['default'] = strip_each_in(section[1].split(','))[0]
+        for line in content:
+            line = line.split(':')
+            if len(line) > 2:
+                out[line[1]] = strip_each_in(line[2].split(','))
+        return out
+
+    def _parse_summary(self):
+        """Grab signature (if given) and summary"""
+        if self._is_at_section():
+            return
+
+        summary = self._doc.read_to_next_empty_line()
+        summary_str = " ".join([s.strip() for s in summary]).strip()
+        if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
+            self['Signature'] = summary_str
+            if not self._is_at_section():
+                self['Summary'] = self._doc.read_to_next_empty_line()
+        else:
+            self['Summary'] = summary
+
+        if not self._is_at_section():
+            self['Extended Summary'] = self._read_to_next_section()
+
+    def _parse(self):
+        self._doc.reset()
+        self._parse_summary()
+
+        for (section, content) in self._read_sections():
+            if not section.startswith('..'):
+                section = ' '.join([s.capitalize()
+                                   for s in section.split(' ')])
+            if section in ('Parameters', 'Attributes', 'Methods',
+                           'Returns', 'Raises', 'Warns'):
+                self[section] = self._parse_param_list(content)
+            elif section.startswith('.. index::'):
+                self['index'] = self._parse_index(section, content)
+            elif section == 'See Also':
+                self['See Also'] = self._parse_see_also(content)
+            else:
+                self[section] = content
+
+    # string conversion routines
+
+    def _str_header(self, name, symbol='-'):
+        return [name, len(name) * symbol]
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' ' * indent + line]
+        return out
+
+    def _str_signature(self):
+        if self['Signature']:
+            return [self['Signature'].replace('*', '\*')] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        if self['Summary']:
+            return self['Summary'] + ['']
+        else:
+            return []
+
+    def _str_extended_summary(self):
+        if self['Extended Summary']:
+            return self['Extended Summary'] + ['']
+        else:
+            return []
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            for param, param_type, desc in self[name]:
+                out += ['%s : %s' % (param, param_type)]
+                out += self._str_indent(desc)
+            out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += self[name]
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        if not self['See Also']:
+            return []
+        out = []
+        out += self._str_header("See Also")
+        last_had_desc = True
+        for func, desc, role in self['See Also']:
+            if role:
+                link = ':%s:`%s`' % (role, func)
+            elif func_role:
+                link = ':%s:`%s`' % (func_role, func)
+            else:
+                link = "`%s`_" % func
+            if desc or last_had_desc:
+                out += ['']
+                out += [link]
+            else:
+                out[-1] += ", %s" % link
+            if desc:
+                out += self._str_indent([' '.join(desc)])
+                last_had_desc = True
+            else:
+                last_had_desc = False
+        out += ['']
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        out += ['.. index:: %s' % idx.get('default', '')]
+        for section, references in idx.items():
+            if section == 'default':
+                continue
+            out += ['   :%s: %s' % (section, ', '.join(references))]
+        return out
+
+    def __str__(self, func_role=''):
+        out = []
+        out += self._str_signature()
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters', 'Returns', 'Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_section('Warnings')
+        out += self._str_see_also(func_role)
+        for s in ('Notes', 'References', 'Examples'):
+            out += self._str_section(s)
+        for param_list in ('Attributes', 'Methods'):
+            out += self._str_param_list(param_list)
+        out += self._str_index()
+        return '\n'.join(out)
+
+
+def indent(str, indent=4):
+    indent_str = ' ' * indent
+    if str is None:
+        return indent_str
+    lines = str.split('\n')
+    return '\n'.join(indent_str + l for l in lines)
+
+
+def dedent_lines(lines):
+    """Deindent a list of lines maximally"""
+    return textwrap.dedent("\n".join(lines)).split("\n")
+
+
+def header(text, style='-'):
+    return text + '\n' + style * len(text) + '\n'
+
+
+class FunctionDoc(NumpyDocString):
+
+    def __init__(self, func, role='func', doc=None, config={}):
+        self._f = func
+        self._role = role  # e.g. "func" or "meth"
+
+        if doc is None:
+            if func is None:
+                raise ValueError("No function or docstring given")
+            doc = inspect.getdoc(func) or ''
+        NumpyDocString.__init__(self, doc)
+
+        if not self['Signature'] and func is not None:
+            func, func_name = self.get_func()
+            try:
+                # try to read signature
+                argspec = inspect.getargspec(func)
+                argspec = inspect.formatargspec(*argspec)
+                argspec = argspec.replace('*', '\*')
+                signature = '%s%s' % (func_name, argspec)
+            except TypeError as e:
+                signature = '%s()' % func_name
+            self['Signature'] = signature
+
+    def get_func(self):
+        func_name = getattr(self._f, '__name__', self.__class__.__name__)
+        if inspect.isclass(self._f):
+            func = getattr(self._f, '__call__', self._f.__init__)
+        else:
+            func = self._f
+        return func, func_name
+
+    def __str__(self):
+        out = ''
+
+        func, func_name = self.get_func()
+        signature = self['Signature'].replace('*', '\*')
+
+        roles = {'func': 'function',
+                 'meth': 'method'}
+
+        if self._role:
+            if not self._role in roles:
+                print("Warning: invalid role %s" % self._role)
+            out += '.. %s:: %s\n    \n\n' % (roles.get(self._role, ''),
+                                             func_name)
+
+        out += super(FunctionDoc, self).__str__(func_role=self._role)
+        return out
+
+
+class ClassDoc(NumpyDocString):
+
+    def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc,
+                 config={}):
+        if not inspect.isclass(cls) and cls is not None:
+            raise ValueError("Expected a class or None, but got %r" % cls)
+        self._cls = cls
+
+        if modulename and not modulename.endswith('.'):
+            modulename += '.'
+        self._mod = modulename
+
+        if doc is None:
+            if cls is None:
+                raise ValueError("No class or documentation string given")
+            doc = pydoc.getdoc(cls)
+
+        NumpyDocString.__init__(self, doc)
+
+        if config.get('show_class_members', True):
+            if not self['Methods']:
+                self['Methods'] = [(name, '', '')
+                                   for name in sorted(self.methods)]
+            if not self['Attributes']:
+                self['Attributes'] = [(name, '', '')
+                                      for name in sorted(self.properties)]
+
+    @property
+    def methods(self):
+        if self._cls is None:
+            return []
+        return [name for name, func in inspect.getmembers(self._cls)
+                if not name.startswith('_') and callable(func)]
+
+    @property
+    def properties(self):
+        if self._cls is None:
+            return []
+        return [name for name, func in inspect.getmembers(self._cls)
+                if not name.startswith('_') and func is None]
diff --git a/docs/ext/docscrape_sphinx.py b/docs/ext/docscrape_sphinx.py
new file mode 100644
index 0000000..906c4fd
--- /dev/null
+++ b/docs/ext/docscrape_sphinx.py
@@ -0,0 +1,238 @@
+import re
+import inspect
+import textwrap
+import pydoc
+import sphinx
+from docscrape import NumpyDocString, FunctionDoc, ClassDoc
+
+
+class SphinxDocString(NumpyDocString):
+
+    def __init__(self, docstring, config={}):
+        self.use_plots = config.get('use_plots', False)
+        NumpyDocString.__init__(self, docstring, config=config)
+
+    # string conversion routines
+    def _str_header(self, name, symbol='`'):
+        return ['.. rubric:: ' + name, '']
+
+    def _str_field_list(self, name):
+        return [':' + name + ':']
+
+    def _str_indent(self, doc, indent=4):
+        out = []
+        for line in doc:
+            out += [' ' * indent + line]
+        return out
+
+    def _str_signature(self):
+        return ['']
+        if self['Signature']:
+            return ['``%s``' % self['Signature']] + ['']
+        else:
+            return ['']
+
+    def _str_summary(self):
+        return self['Summary'] + ['']
+
+    def _str_extended_summary(self):
+        return self['Extended Summary'] + ['']
+
+    def _str_param_list(self, name):
+        out = []
+        if self[name]:
+            out += self._str_field_list(name)
+            out += ['']
+            for param, param_type, desc in self[name]:
+                out += self._str_indent(['**%s** : %s' % (param.strip(),
+                                                          param_type)])
+                out += ['']
+                out += self._str_indent(desc, 8)
+                out += ['']
+        return out
+
+    @property
+    def _obj(self):
+        if hasattr(self, '_cls'):
+            return self._cls
+        elif hasattr(self, '_f'):
+            return self._f
+        return None
+
+    def _str_member_list(self, name):
+        """
+        Generate a member listing, autosummary:: table where possible,
+        and a table where not.
+
+        """
+        out = []
+        if self[name]:
+            out += ['.. rubric:: %s' % name, '']
+            prefix = getattr(self, '_name', '')
+
+            if prefix:
+                prefix = '~%s.' % prefix
+
+            autosum = []
+            others = []
+            for param, param_type, desc in self[name]:
+                param = param.strip()
+                if not self._obj or hasattr(self._obj, param):
+                    autosum += ["   %s%s" % (prefix, param)]
+                else:
+                    others.append((param, param_type, desc))
+
+            if False and autosum:
+                out += ['.. autosummary::', '   :toctree:', '']
+                out += autosum
+
+            if others:
+                maxlen_0 = max([len(x[0]) for x in others])
+                maxlen_1 = max([len(x[1]) for x in others])
+                hdr = "=" * maxlen_0 + "  " + "=" * maxlen_1 + "  " + "=" * 10
+                fmt = '%%%ds  %%%ds  ' % (maxlen_0, maxlen_1)
+                n_indent = maxlen_0 + maxlen_1 + 4
+                out += [hdr]
+                for param, param_type, desc in others:
+                    out += [fmt % (param.strip(), param_type)]
+                    out += self._str_indent(desc, n_indent)
+                out += [hdr]
+            out += ['']
+        return out
+
+    def _str_section(self, name):
+        out = []
+        if self[name]:
+            out += self._str_header(name)
+            out += ['']
+            content = textwrap.dedent("\n".join(self[name])).split("\n")
+            out += content
+            out += ['']
+        return out
+
+    def _str_see_also(self, func_role):
+        out = []
+        if self['See Also']:
+            see_also = super(SphinxDocString, self)._str_see_also(func_role)
+            out = ['.. seealso::', '']
+            out += self._str_indent(see_also[2:])
+        return out
+
+    def _str_warnings(self):
+        out = []
+        if self['Warnings']:
+            out = ['.. warning::', '']
+            out += self._str_indent(self['Warnings'])
+        return out
+
+    def _str_index(self):
+        idx = self['index']
+        out = []
+        if len(idx) == 0:
+            return out
+
+        out += ['.. index:: %s' % idx.get('default', '')]
+        for section, references in idx.items():
+            if section == 'default':
+                continue
+            elif section == 'refguide':
+                out += ['   single: %s' % (', '.join(references))]
+            else:
+                out += ['   %s: %s' % (section, ','.join(references))]
+        return out
+
+    def _str_references(self):
+        out = []
+        if self['References']:
+            out += self._str_header('References')
+            if isinstance(self['References'], str):
+                self['References'] = [self['References']]
+            out.extend(self['References'])
+            out += ['']
+            # Latex collects all references to a separate bibliography,
+            # so we need to insert links to it
+            if sphinx.__version__ >= "0.6":
+                out += ['.. only:: latex', '']
+            else:
+                out += ['.. latexonly::', '']
+            items = []
+            for line in self['References']:
+                m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I)
+                if m:
+                    items.append(m.group(1))
+            out += ['   ' + ", ".join(["[%s]_" % item for item in items]), '']
+        return out
+
+    def _str_examples(self):
+        examples_str = "\n".join(self['Examples'])
+
+        if (self.use_plots and 'import matplotlib' in examples_str
+                and 'plot::' not in examples_str):
+            out = []
+            out += self._str_header('Examples')
+            out += ['.. plot::', '']
+            out += self._str_indent(self['Examples'])
+            out += ['']
+            return out
+        else:
+            return self._str_section('Examples')
+
+    def __str__(self, indent=0, func_role="obj"):
+        out = []
+        out += self._str_signature()
+        out += self._str_index() + ['']
+        out += self._str_summary()
+        out += self._str_extended_summary()
+        for param_list in ('Parameters', 'Returns', 'Raises'):
+            out += self._str_param_list(param_list)
+        out += self._str_warnings()
+        out += self._str_see_also(func_role)
+        out += self._str_section('Notes')
+        out += self._str_references()
+        out += self._str_examples()
+        for param_list in ('Attributes', 'Methods'):
+            out += self._str_member_list(param_list)
+        out = self._str_indent(out, indent)
+        return '\n'.join(out)
+
+
+class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
+
+    def __init__(self, obj, doc=None, config={}):
+        self.use_plots = config.get('use_plots', False)
+        FunctionDoc.__init__(self, obj, doc=doc, config=config)
+
+
+class SphinxClassDoc(SphinxDocString, ClassDoc):
+
+    def __init__(self, obj, doc=None, func_doc=None, config={}):
+        self.use_plots = config.get('use_plots', False)
+        ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config)
+
+
+class SphinxObjDoc(SphinxDocString):
+
+    def __init__(self, obj, doc=None, config={}):
+        self._f = obj
+        SphinxDocString.__init__(self, doc, config=config)
+
+
+def get_doc_object(obj, what=None, doc=None, config={}):
+    if what is None:
+        if inspect.isclass(obj):
+            what = 'class'
+        elif inspect.ismodule(obj):
+            what = 'module'
+        elif callable(obj):
+            what = 'function'
+        else:
+            what = 'object'
+    if what == 'class':
+        return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc,
+                              config=config)
+    elif what in ('function', 'method'):
+        return SphinxFunctionDoc(obj, doc=doc, config=config)
+    else:
+        if doc is None:
+            doc = pydoc.getdoc(obj)
+        return SphinxObjDoc(obj, doc, config=config)
diff --git a/docs/ext/imageio_ext.py b/docs/ext/imageio_ext.py
new file mode 100644
index 0000000..88fbf21
--- /dev/null
+++ b/docs/ext/imageio_ext.py
@@ -0,0 +1,221 @@
+""" Invoke various functionality for imageio docs.
+"""
+
+import os
+import sys
+
+import imageio
+
+THIS_DIR = os.path.dirname(os.path.abspath(__file__))
+DOC_DIR = os.path.dirname(THIS_DIR)
+
+
+files_to_remove = []
+
+
+def setup(app):
+    init()
+    app.connect('build-finished', clean)
+
+
+def init():
+    
+    print('Special preparations for imageio docs:')
+    
+    for func in [prepare_reader_and_witer,
+                 prepare_core_docs,
+                 create_plugin_docs,
+                 create_format_docs,
+                 create_standard_images_docs,
+                 ]:
+        print('  ' + func.__doc__.strip())
+        func()
+
+
+def clean(app, *args):
+    for fname in files_to_remove:
+        os.remove(os.path.join(DOC_DIR, fname))
+
+
+def _write(fname, text):
+    files_to_remove.append(fname)
+    with open(os.path.join(DOC_DIR, fname), 'wb') as f:
+        f.write(text.encode('utf-8'))
+
+
+##
+
+
+def prepare_reader_and_witer():
+    """ Prepare Format.Reader and Format.Writer for doc generation.
+    """
+    
+    # Create Reader and Writer subclasses that are going to be placed
+    # in the format module so that autoclass can find them. They need
+    # to be new classes, otherwise sphinx considers them aliases.
+    class Reader(imageio.core.format.Format.Reader):
+        pass
+    class Writer(imageio.core.format.Format.Writer):
+        pass
+    imageio.core.format.Reader = Reader
+    imageio.core.format.Writer = Writer
+    
+    # We set the docs of the original classes, and remove the docstring
+    # from the original classes so that Reader and Writer do not show
+    # up in the docs of the Format class.
+    Reader.__doc__ = imageio.core.format.Format.Reader.__doc__
+    Writer.__doc__ = imageio.core.format.Format.Writer.__doc__
+    imageio.core.format.Format.Reader.__doc__ = ''
+    imageio.core.format.Format.Writer.__doc__ = ''
+
+
+def prepare_core_docs():
+    """ Prepare imageio.core for doc generation.
+    """
+    # Set __all__ and add to __doc__ in imageio.core module,
+    # so that the documentation gets generated correctly.
+    
+    D = imageio.core.__dict__
+    
+    # Collect functions and classes in imageio.core
+    functions, classes = [], []
+    for name in D:
+        func_type = type(prepare_core_docs)
+        if name.startswith('_'):
+            continue
+        ob = D[name]
+        if isinstance(ob, type):
+            classes.append(name)
+        elif isinstance(ob, func_type):
+            functions.append(name)
+    
+    # Write summaries
+    classes.sort()
+    functions.sort()
+    extradocs = '\nFunctions: '
+    extradocs += ', '.join([':func:`.%s`' % n for n in functions])
+    extradocs += '\n\nClasses: '
+    extradocs += ', '.join([':class:`.%s`' % n for n in classes])
+    extradocs += '\n\n----\n'
+    
+    # Update
+    D['__doc__'] += extradocs
+    D['__all__'] = functions + classes
+
+
+
+def create_plugin_docs():
+    """ Create docs for creating plugins.
+    """
+    
+    # Build main plugin dir
+    title = "Creating imageio plugins"
+    text = '%s\n%s\n\n' % (title, '=' * len(title))
+    
+    text += '.. automodule:: imageio.plugins\n\n'
+    
+    # Insert code from example plugin
+    text += 'Example / template plugin\n-------------------------\n\n'
+    text += ".. code-block:: python\n    :linenos:\n\n"
+    filename = imageio.plugins.example.__file__
+    code = open(filename, 'rb').read().decode('utf-8')
+    code = '\n'.join(['    ' + line.rstrip() for line in code.splitlines()])
+    text += code
+    
+    # Write
+    _write('plugins.rst', text)
+
+format_doc_text = """
+This page lists all formats currently supported by imageio. Each format
+can support extra keyword arguments for reading and writing, which can be 
+specified in the call to ``get_reader()``, ``get_writer()``, ``imread()``,
+``imwrite()`` etc. Further, formats are free to provide additional
+methods on their Reader and Writer objects. These parameters and extra
+methods are specified in the documentation for each format.
+"""
+
+def create_format_docs():
+    """ Create documentation for the formats.
+    """
+    
+    # Build main plugin dir
+    title = "Imageio formats"
+    text = '%s\n%s\n%s\n\n' % ('=' * len(title), title, '=' * len(title))
+    
+    text += format_doc_text
+    
+    # Get bullet list of all formats
+    ss = ['\n']
+    covered_formats = []
+    modemap = {'i': 'Single images', 'I': 'Multiple images',
+               'v': 'Single volumes', 'V': 'Multiple volumes', }
+    for mode in 'iIvV-':
+        subtitle = modemap.get(mode, 'Unsorted')
+        ss.append('%s\n%s\n' % (subtitle, '^' * len(subtitle)))
+        for format in imageio.formats: 
+            if ((mode in format.modes) or 
+                (mode == '-' and format not in covered_formats)):
+                covered_formats.append(format)
+                s = '  * :ref:`%s <%s>` - %s' % (format.name, 
+                                                 format.name, 
+                                                 format.description)
+                ss.append(s)
+    text += '\n'.join(ss) + '\n\n'
+    _write('formats.rst', text)
+    
+    
+    # Get more docs for each format
+    for format in imageio.formats:
+        
+        title = '%s %s' % (format.name, format.description)
+        ext = ', '.join(['``%s``' % e for e in format.extensions])
+        ext = ext or 'None'
+        #
+        text = ':orphan:\n\n'
+        text += '.. _%s:\n\n' % format.name
+        text += '%s\n%s\n\n' % (title, '='*len(title))
+        #
+        text += 'Extensions: %s\n\n' % ext
+        docs = '    ' + format.__doc__.lstrip()
+        docs = '\n'.join([x[4:].rstrip() for x in docs.splitlines()])
+        #
+        text += docs + '\n\n'
+        
+        #members = '\n  :members:\n\n'
+        #text += '.. autoclass:: %s.Reader%s' % (format.__module__, members)
+        #text += '.. autoclass:: %s.Writer%s' % (format.__module__, members)
+        
+        _write('format_%s.rst' % format.name.lower(), text)
+
+
+def create_standard_images_docs():
+    """ Create documentation for imageio's standard images.
+    """
+    
+    title = "Imageio standard images"
+    text = '%s\n%s\n%s\n\n' % ('=' * len(title), title, '=' * len(title))
+    
+    docs = """
+        Imageio provides a number of standard images. These include classic
+        2D images, as well as animated and volumetric images. To the best
+        of our knowledge, all the listed images are in public domain.
+        
+        The image names can simply be used as a URI 
+        (e.g. ``imread('astronaut.png')``.
+        The images are automatically downloaded (and cached in your appdata
+        directory). If 'astronaut.png' happens to be a valid filename in the
+        current directory, that file is used instead.
+        """
+    text += '\n'.join([line.strip() for line in docs.splitlines()])
+    text += '\n\n'
+    
+    from imageio.core.request import EXAMPLE_IMAGES
+    baseurl = 'https://github.com/imageio/imageio-binaries/raw/master/images/'
+    
+    
+    sort_by_ext_and_name = lambda x: tuple(reversed(x.rsplit('.', 1)))
+    for name in sorted(EXAMPLE_IMAGES, key=sort_by_ext_and_name):
+        description = EXAMPLE_IMAGES[name]
+        text += '* `%s <%s>`_: %s\n\n' % (name,  baseurl + name, description)
+    
+    _write('standardimages.rst', text)
diff --git a/docs/ext/numpydoc.py b/docs/ext/numpydoc.py
new file mode 100644
index 0000000..fbc26c3
--- /dev/null
+++ b/docs/ext/numpydoc.py
@@ -0,0 +1,178 @@
+"""
+========
+numpydoc
+========
+
+Sphinx extension that handles docstrings in the Numpy standard format. [1]
+
+It will:
+
+- Convert Parameters etc. sections to field lists.
+- Convert See Also section to a See also entry.
+- Renumber references.
+- Extract the signature from the docstring, if it can't be determined otherwise.
+
+.. [1] http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard
+
+"""
+
+import os
+import re
+import pydoc
+import sys
+from docscrape_sphinx import get_doc_object, SphinxDocString
+from sphinx.util.compat import Directive
+import inspect
+if sys.version_info > (3,):
+    unicode = str
+
+
+def mangle_docstrings(app, what, name, obj, options, lines,
+                      reference_offset=[0]):
+
+    cfg = dict(use_plots=app.config.numpydoc_use_plots,
+               show_class_members=app.config.numpydoc_show_class_members)
+
+    if what == 'module':
+        # Strip top title
+        title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
+                              re.I | re.S)
+        lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n")
+    else:
+        doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg)
+        lines[:] = unicode(doc).split(u"\n")
+
+    if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \
+            obj.__name__:
+        if hasattr(obj, '__module__'):
+            v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__))
+        else:
+            v = dict(full_name=obj.__name__)
+        lines += [u'', u'.. htmlonly::', '']
+        lines += [u'    %s' % x for x in
+                  (app.config.numpydoc_edit_link % v).split("\n")]
+
+    # replace reference numbers so that there are no duplicates
+    references = []
+    for line in lines:
+        line = line.strip()
+        m = re.match(r'^.. \[([a-z0-9_.-])\]', line, re.I)
+        if m:
+            references.append(m.group(1))
+
+    # start renaming from the longest string, to avoid overwriting parts
+    references.sort(key=lambda x: -len(x))
+    if references:
+        for i, line in enumerate(lines):
+            for r in references:
+                if re.match(r'^\d+$', r):
+                    new_r = u"R%d" % (reference_offset[0] + int(r))
+                else:
+                    new_r = u"%s%d" % (r, reference_offset[0])
+                lines[i] = lines[i].replace(u'[%s]_' % r,
+                                            u'[%s]_' % new_r)
+                lines[i] = lines[i].replace(u'.. [%s]' % r,
+                                            u'.. [%s]' % new_r)
+
+    reference_offset[0] += len(references)
+
+
+def mangle_signature(app, what, name, obj, options, sig, retann):
+    # Do not try to inspect classes that don't define `__init__`
+    if (inspect.isclass(obj) and
+        (not hasattr(obj, '__init__') or
+         'initializes x; see ' in pydoc.getdoc(obj.__init__))):
+        return '', ''
+
+    if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')):
+        return
+    if not hasattr(obj, '__doc__'):
+        return
+
+    doc = SphinxDocString(pydoc.getdoc(obj))
+    if doc['Signature']:
+        sig = re.sub(u"^[^(]*", u"", doc['Signature'])
+        return sig, u''
+
+
+def setup(app, get_doc_object_=get_doc_object):
+    global get_doc_object
+    get_doc_object = get_doc_object_
+
+    app.connect('autodoc-process-docstring', mangle_docstrings)
+    app.connect('autodoc-process-signature', mangle_signature)
+    app.add_config_value('numpydoc_edit_link', None, False)
+    app.add_config_value('numpydoc_use_plots', None, False)
+    app.add_config_value('numpydoc_show_class_members', True, True)
+
+    # Extra mangling domains
+    app.add_domain(NumpyPythonDomain)
+    app.add_domain(NumpyCDomain)
+
+#------------------------------------------------------------------------------
+# Docstring-mangling domains
+#------------------------------------------------------------------------------
+
+from docutils.statemachine import ViewList
+from sphinx.domains.c import CDomain
+from sphinx.domains.python import PythonDomain
+
+
+class ManglingDomainBase(object):
+    directive_mangling_map = {}
+
+    def __init__(self, *a, **kw):
+        super(ManglingDomainBase, self).__init__(*a, **kw)
+        self.wrap_mangling_directives()
+
+    def wrap_mangling_directives(self):
+        for name, objtype in self.directive_mangling_map.items():
+            self.directives[name] = wrap_mangling_directive(
+                self.directives[name], objtype)
+
+
+class NumpyPythonDomain(ManglingDomainBase, PythonDomain):
+    name = 'np'
+    directive_mangling_map = {
+        'function': 'function',
+        'class': 'class',
+        'exception': 'class',
+        'method': 'function',
+        'classmethod': 'function',
+        'staticmethod': 'function',
+        'attribute': 'attribute',
+    }
+
+
+class NumpyCDomain(ManglingDomainBase, CDomain):
+    name = 'np-c'
+    directive_mangling_map = {
+        'function': 'function',
+        'member': 'attribute',
+        'macro': 'function',
+        'type': 'class',
+        'var': 'object',
+    }
+
+
+def wrap_mangling_directive(base_directive, objtype):
+    class directive(base_directive):
+
+        def run(self):
+            env = self.state.document.settings.env
+
+            name = None
+            if self.arguments:
+                m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0])
+                name = m.group(2).strip()
+
+            if not name:
+                name = self.arguments[0]
+
+            lines = list(self.content)
+            mangle_docstrings(env.app, objtype, name, None, None, lines)
+            self.content = ViewList(lines, self.content.parent)
+
+            return base_directive.run(self)
+
+    return directive
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..6525f6c
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,14 @@
+Welcome to imageio's documentation!
+===================================
+
+.. automodule:: imageio
+
+
+Contents:
+
+.. toctree::
+  :maxdepth: 2
+  
+  sec_gettingstarted
+  sec_reference
+  sec_developer
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644
index 0000000..ce54ad1
--- /dev/null
+++ b/docs/installation.rst
@@ -0,0 +1,25 @@
+Installing imageio
+==================
+
+Imageio is written in pure Python, so installation is easy. 
+Imageio works on Python 2.6 and up (including Python 3). It also works
+on Pypy. For some formats, imageio needs
+additional libraries (e.g. freeimage), which are downloaded
+automatically and stored in a folder in your application data.
+
+To install imageio, use one of the following methods:
+    
+* If you are in a conda env: ``conda install -c pyzo imageio``
+* If you have pip: ``pip install imageio``
+* Good old ``python setup.py install``
+
+To install imageio with the freeimage library and a few images included,
+you can force downloading the libs when building, or use one of the
+provided archives from pypi:
+    
+* ``python setup.py build_with_fi install``
+* ``pip install https://pypi.python.org/packages/source/i/imageio/imageio-x.y-linux64.zip``
+
+For developers, we provide a simple mechanism to allow importing 
+imageio from the cloned repository. See the file ``imageio.proxy.io`` for
+details.
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..ae72e18
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,190 @@
+ at ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  texinfo    to make Texinfo files
+	echo.  gettext    to make PO message catalogs
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\imageio.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\imageio.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "texinfo" (
+	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+	goto end
+)
+
+if "%1" == "gettext" (
+	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end
diff --git a/docs/releasenotes.rst b/docs/releasenotes.rst
new file mode 100644
index 0000000..b74ac13
--- /dev/null
+++ b/docs/releasenotes.rst
@@ -0,0 +1,172 @@
+-------------
+Release notes
+-------------
+
+Version 1.1 (04-02-2015)
+========================
+
+Imageio is now a dependency of `Moviepy <https://github.com/Zulko/moviepy/>`_, 
+which exposed a few issues to fix. Imageio is now also available as a
+Debian package (thanks Ghislain!). Furher, we tweaked our function names
+to be cleared and more consistent (the old names still work).
+
+* All ``Xsave()`` functions are renamed to ``Xwrite()``. 
+  Also ``read()`` and ``save()`` are now ``get_reader()`` and ``get_writer()``.
+  The old names are available as aliases (and will be for the foreseable
+  future) for backward compatibility.
+* Protect user from bringing computer in swap-mode by doing e.g.
+  ``mimread('hunger games.avi')``.
+* Continuous integration for Windows via Appveyor.
+* All imports are relative, so imageio can be used as a subpackage in
+  a larger project.
+* FFMPEG is the default plugin for reading video (since AVBIN has issues).
+* Better handling on NaN and Inf when converting to uint8.
+* Provide dist packages that include freeimage lib and a few example images.
+* Several changes to ease building into Debian package.
+* Fixed segfault when saving gif 
+  (thanks levskaya, https://github.com/imageio/imageio/pull/53).
+* Don't fail when userdir is not writable.
+* Gif plugin writer has fps param for consistency with avi/mp4 etc.
+
+
+Version 1.0 (13-11-2014)
+========================
+
+In this release we did a lot of work to push imageio to a new level.
+The code is now properly tested, and we have several more formats.
+
+The big changes:
+
+* Many unit tests were written to cover over 95% of the code base.
+  (the core of imageio has 100% coverage).
+* Setup continuous integration (CI) using Travis.
+* Imageio now follows PEP8 style guides (and this is tested with CI).
+* Refactoring of the code base. Resulting in a cleaner namespace.
+* Many improvements to the documementation.
+
+Plugins:
+
+* The FFMPEG format is now well supported. Binaries are provided.
+* New AVBIN format for more efficient reading of video files.
+* New NPZ format that can store (a series of) arbitrarily shaped numpy arrays.
+* New SWF format (shockwave flash) for lossless animated images.
+* Improvements to the GIF format. The GIF and ANIGIF formats are now merged.
+
+Further:
+
+* New simple website to act as a front page (http://imageio.github.io).
+* Compatibility with Pypy.
+* We provide a range of :doc:`standard images <standardimages>` that are 
+  automatically downloaded.
+* Binaries (libs and executables) that plugins of imageio uses are now
+  downloaded at runtime, not at build/install time. This simplifies
+  things a lot.
+* freeimage plugin now fully functional on pypy
+* Added utilities for developers (run ``python make`` from the repo root).
+* PNG, JPEG, BMP,GIF and other plugins can now handle float data (pixel
+  values are assumed to be between 0 and 1.
+* Imageio now expand the user dir when filename start with '~/'.
+* Many improvements and fixes overall.
+
+
+Version 0.5.1 (23-06-2014)
+==========================
+
+* DICOM reader closes file after reading pixel data 
+  (avoid too-many-open-files error)
+* Support for video data (import and export) via ffmpeg
+* Read images from usb camera via ffmpeg (experimental)
+
+
+Version 0.4.1 (26-10-2013)
+==========================
+
+* We moved to github!
+* Raise error if URI could not be understood.
+* Small improvement for better error reporting.
+* FIxes in mvolread and DICOM plugin
+
+
+Version 0.4 (27-03-2013)
+========================
+
+Some more thorough testing resulted in several fixes and improvements over
+the last release.
+
+* Fixes to reading of meta data in freeimage plugin which could
+  cause errors when reading a file.
+* Support for reading 4 bpp images.
+* The color table for index images is now applied to yield an RGBA image.
+* Basic support for Pypy.
+* Better __repr__ for the Image class.
+
+
+Version 0.3.2
+=============
+
+* Fix in dicom reader (RescaleSlope and RescaleIntercept were not found)
+* Fixed that progress indicator made things slow
+
+
+Version 0.3.1
+=============
+
+* Fix installation/distribution issue.
+
+
+Version 0.3.0
+=============
+
+This was a long haul. Implemented several plugins for animation and
+volumetric data to give an idea of what sort of API's work and which 
+do not. 
+
+* Refactored for more conventional package layout 
+  (but importing without installing still supported)
+* Put Reader and Writer classes in the namespace of the format. This
+  makes a format a unified whole, and gets rid of the
+  _get_reader_class and _get_write_class methods (at the cost of
+  some extra indentation).
+* Refactored Reader and Writer classes to come up with a better API
+  for both users as plugins.
+* The Request class acts as a smart bridging object. Therefore all
+  plugins can now read from a zipfile, http/ftp, and bytes. And they
+  don't have to do a thing.
+* Implemented specific BMP, JPEG, PNG, GIF, ICON formats.
+* Implemented animated gif plugin (based on freeimage).
+* Implemented standalone DICOM plugin.
+
+
+Version 0.2.3
+=============
+
+* Fixed issue 2 (fail at instal, introduced when implementing freezing)
+
+
+Version 0.2.2
+=============
+
+* Improved documentation.
+* Worked on distribution.
+* Freezing should work now.
+
+
+Version 0.2.1
+=============
+
+* Introduction of the imageio.help function.
+* Wrote a lot of documentation.
+* Added example (dummy) plugin.
+
+
+Version 0.2
+===========
+
+* New plugin system implemented after discussions in group.
+* Access to format information.
+
+
+Version 0.1
+===========
+
+* First version with a preliminary plugin system.
diff --git a/docs/sec_developer.rst b/docs/sec_developer.rst
new file mode 100644
index 0000000..7f772ac
--- /dev/null
+++ b/docs/sec_developer.rst
@@ -0,0 +1,9 @@
+=======================
+Developer documentation
+=======================
+
+.. toctree::
+  :maxdepth: 2
+  
+  Developer API <devapi>
+  Writing plugins <plugins>
diff --git a/docs/sec_gettingstarted.rst b/docs/sec_gettingstarted.rst
new file mode 100644
index 0000000..60bc20a
--- /dev/null
+++ b/docs/sec_gettingstarted.rst
@@ -0,0 +1,10 @@
+===============
+Getting started
+===============
+
+.. toctree::
+  :maxdepth: 2
+  
+  Installation <installation>
+  Usage examples <examples>
+  Release notes <releasenotes>
diff --git a/docs/sec_reference.rst b/docs/sec_reference.rst
new file mode 100644
index 0000000..fea0e84
--- /dev/null
+++ b/docs/sec_reference.rst
@@ -0,0 +1,10 @@
+=========
+Reference
+=========
+
+.. toctree::
+  :maxdepth: 2
+
+  User API <userapi>
+  Docs for the formats <formats>
+  Standard images <standardimages>
diff --git a/docs/userapi.rst b/docs/userapi.rst
new file mode 100644
index 0000000..2315df3
--- /dev/null
+++ b/docs/userapi.rst
@@ -0,0 +1,49 @@
+------------------
+Imageio's user API
+------------------
+
+.. automodule:: imageio.core.functions
+
+----
+
+.. autofunction:: imageio.help
+
+----
+    
+.. autofunction:: imageio.imread
+
+.. autofunction:: imageio.imwrite
+
+----
+
+.. autofunction:: imageio.mimread
+
+.. autofunction:: imageio.mimwrite
+
+----
+
+.. autofunction:: imageio.volread
+
+.. autofunction:: imageio.volwrite
+
+----
+
+.. autofunction:: imageio.mvolread
+
+.. autofunction:: imageio.mvolwrite
+
+----
+
+.. autofunction:: imageio.get_reader
+
+.. autofunction:: imageio.get_writer
+
+----
+
+.. autoclass:: imageio.core.format.Reader
+    :inherited-members:
+    :members: 
+    
+.. autoclass:: imageio.core.format.Writer
+    :inherited-members:
+    :members: 
diff --git a/imageio/__init__.py b/imageio/__init__.py
index b0db1f5..0980a6e 100644
--- a/imageio/__init__.py
+++ b/imageio/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 # This docstring is used at the index of the documentation pages, and
@@ -14,7 +14,7 @@ and 3.x, and is easy to install.
 Main website: http://imageio.github.io
 """
 
-__version__ = '1.0' 
+__version__ = '1.1' 
 
 # Load some bits from core
 from .core import FormatManager, RETURN_BYTES  # noqa
@@ -24,8 +24,13 @@ formats = FormatManager()
 
 # Load the functions
 from .core.functions import help  # noqa
-from .core.functions import read, imread, mimread, volread, mvolread  # noqa
-from .core.functions import save, imsave, mimsave, volsave, mvolsave  # noqa
+from .core.functions import get_reader, get_writer  # noqa
+from .core.functions import imread, mimread, volread, mvolread  # noqa
+from .core.functions import imwrite, mimwrite, volwrite, mvolwrite  # noqa
+
+# Load function aliases
+from .core.functions import read, save  # noqa
+from .core.functions import imsave, mimsave, volsave, mvolsave  # noqa
 
 # Load all the plugins
 from . import plugins  # noqa
diff --git a/imageio/core/__init__.py b/imageio/core/__init__.py
index ae6b62f..0602a01 100644
--- a/imageio/core/__init__.py
+++ b/imageio/core/__init__.py
@@ -1,15 +1,15 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
 """ This subpackage provides the core functionality of imageio
 (everything but the plugins).
 """
 
-from .util import Image, Dict, asarray, appdata_dir, urlopen  # noqa
+from .util import Image, Dict, asarray, image_as_uint8, urlopen  # noqa
 from .util import BaseProgressIndicator, StdoutProgressIndicator  # noqa
 from .util import string_types, text_type, binary_type, IS_PYPY  # noqa
-from .util import get_platform  # noqa
+from .util import get_platform, appdata_dir, resource_dirs  # noqa
 from .findlib import load_lib  # noqa
 from .fetching import get_remote_file  # noqa
 from .request import Request, read_n_bytes, RETURN_BYTES  # noqa
diff --git a/imageio/core/fetching.py b/imageio/core/fetching.py
index 459ae80..82614ac 100644
--- a/imageio/core/fetching.py
+++ b/imageio/core/fetching.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # Based on code from the vispy project
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
@@ -15,7 +15,8 @@ import sys
 import shutil
 import time
 
-from . import appdata_dir, StdoutProgressIndicator, string_types, urlopen
+from . import appdata_dir, resource_dirs
+from . import StdoutProgressIndicator, string_types, urlopen
 
 
 def get_remote_file(fname, directory=None, force_download=False):
@@ -24,12 +25,14 @@ def get_remote_file(fname, directory=None, force_download=False):
     Parameters
     ----------
     fname : str
-        The filename on the remote data repository to download. These
-        correspond to paths on 
+        The relative filename on the remote data repository to download.
+        These correspond to paths on
         ``https://github.com/imageio/imageio-binaries/``.
     directory : str | None
-        Directory to use to save the file. By default, the appdata
-        directory is used.
+        The directory where the file will be cached if a download was
+        required to obtain the file. By default, the appdata directory
+        is used. This is also the first directory that is checked for
+        a local version of the file.
     force_download : bool | str
         If True, the file will be downloaded even if a local copy exists
         (and this copy will be overwritten). Can also be a YYYY-MM-DD date
@@ -43,36 +46,48 @@ def get_remote_file(fname, directory=None, force_download=False):
     """
     _url_root = 'https://github.com/imageio/imageio-binaries/raw/master/'
     url = _url_root + fname
+    fname = op.normcase(fname)  # convert to native
+    # Get dirs to look for the resource
     directory = directory or appdata_dir('imageio')
-
-    fname = op.join(directory, op.normcase(fname))  # convert to native
-    if op.isfile(fname):
-        if not force_download:  # we're done
-            return fname
-        if isinstance(force_download, string_types):
-            ntime = time.strptime(force_download, '%Y-%m-%d')
-            ftime = time.gmtime(op.getctime(fname))
-            if ftime >= ntime:
-                return fname
-            else:
-                print('File older than %s, updating...' % force_download)
-    if not op.isdir(op.dirname(fname)):
-        os.makedirs(op.abspath(op.dirname(fname)))
+    dirs = resource_dirs()
+    dirs.insert(0, directory)  # Given dir has preference
+    # Try to find the resource locally
+    for dir in dirs:
+        filename = op.join(dir, fname)
+        if op.isfile(filename):
+            if not force_download:  # we're done
+                return filename
+            if isinstance(force_download, string_types):
+                ntime = time.strptime(force_download, '%Y-%m-%d')
+                ftime = time.gmtime(op.getctime(filename))
+                if ftime >= ntime:
+                    return filename
+                else:
+                    print('File older than %s, updating...' % force_download)
+                    break
+    
+    # If we get here, we're going to try to download the file
+    if os.getenv('IMAGEIO_NO_INTERNET', '').lower() in ('1', 'true', 'yes'):
+        raise IOError('Cannot download resource from the internet')
+    # Get filename to store to and make sure the dir exists
+    filename = op.join(directory, fname)
+    if not op.isdir(op.dirname(filename)):
+        os.makedirs(op.abspath(op.dirname(filename)))
     # let's go get the file
     if os.getenv('CONTINUOUS_INTEGRATION', False):  # pragma: no cover
         # On Travis, we retry a few times ...
         for i in range(2):
             try:
-                _fetch_file(url, fname)
-                return fname
+                _fetch_file(url, filename)
+                return filename
             except IOError:
                 time.sleep(0.5)
         else:
-            _fetch_file(url, fname)
-            return fname
+            _fetch_file(url, filename)
+            return filename
     else:  # pragma: no cover
-        _fetch_file(url, fname)
-        return fname
+        _fetch_file(url, filename)
+        return filename
 
 
 def _fetch_file(url, file_name, print_destination=True):
@@ -92,31 +107,42 @@ def _fetch_file(url, file_name, print_destination=True):
     """
     # Adapted from NISL:
     # https://github.com/nisl/tutorial/blob/master/nisl/datasets.py
-
+    
+    print('Imageio: %r was not found on your computer; '
+          'downloading it now.' % os.path.basename(file_name))
+    
     temp_file_name = file_name + ".part"
     local_file = None
     initial_size = 0
-    try:
-        # Checking file size and displaying it alongside the download url
-        remote_file = urlopen(url, timeout=5.)
-        file_size = int(remote_file.headers['Content-Length'].strip())
-        print('Downloading data from %s (%s)' % (url, _sizeof_fmt(file_size)))
-        # Downloading data (can be extended to resume if need be)
-        local_file = open(temp_file_name, "wb")
-        _chunk_read(remote_file, local_file, initial_size=initial_size)
-        # temp file must be closed prior to the move
-        if not local_file.closed:
-            local_file.close()
-        shutil.move(temp_file_name, file_name)
-        if print_destination is True:
-            sys.stdout.write('File saved as %s.\n' % file_name)
-    except Exception as e:
-        raise IOError('Error while fetching file %s.\n'
-                      'Dataset fetching aborted (%s)' % (url, e))
-    finally:
-        if local_file is not None:
+    errors = []
+    for tries in range(4):
+        try:
+            # Checking file size and displaying it alongside the download url
+            remote_file = urlopen(url, timeout=5.)
+            file_size = int(remote_file.headers['Content-Length'].strip())
+            size_str = _sizeof_fmt(file_size)
+            print('Try %i. Download from %s (%s)' % (tries+1, url, size_str))
+            # Downloading data (can be extended to resume if need be)
+            local_file = open(temp_file_name, "wb")
+            _chunk_read(remote_file, local_file, initial_size=initial_size)
+            # temp file must be closed prior to the move
             if not local_file.closed:
                 local_file.close()
+            shutil.move(temp_file_name, file_name)
+            if print_destination is True:
+                sys.stdout.write('File saved as %s.\n' % file_name)
+            break
+        except Exception as e:
+            errors.append(e)
+            print('Error while fetching file: %s.' % str(e))
+        finally:
+            if local_file is not None:
+                if not local_file.closed:
+                    local_file.close()
+    else:
+        raise IOError('Unable to download %r. Perhaps there is a no internet '
+                      'connection? If there is, please report this problem.' %
+                      os.path.basename(file_name))
 
 
 def _chunk_read(response, local_file, chunk_size=8192, initial_size=0):
diff --git a/imageio/core/findlib.py b/imageio/core/findlib.py
index 1f303da..91b4785 100644
--- a/imageio/core/findlib.py
+++ b/imageio/core/findlib.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # Copyright (C) 2013, Zach Pincus, Almar Klein and others
 
 """ This module contains generic code to find and load a dynamic library.
diff --git a/imageio/core/format.py b/imageio/core/format.py
index 185d6b8..0180ca7 100644
--- a/imageio/core/format.py
+++ b/imageio/core/format.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ 
@@ -29,12 +29,8 @@ from __future__ import absolute_import, print_function, division
 # Some notes:
 #
 # The classes in this module use the Request object to pass filename and
-# related info around. This request object is instantiated in imageio.read
-# and imageio.save.
-#
-# We use the verbs read and save throughout imageio. However, for the 
-# associated classes we use the nouns "reader" and "writer", since 
-# "saver" feels so awkward. 
+# related info around. This request object is instantiated in
+# imageio.get_reader and imageio.get_writer.
 
 from __future__ import with_statement
 
@@ -47,15 +43,15 @@ from . import string_types, text_type, binary_type  # noqa
 
 
 class Format:
-    """ Represents an implementation to read/save a particular file format
+    """ Represents an implementation to read/write a particular file format
     
     A format instance is responsible for 1) providing information about
-    a format; 2) determining whether a certain file can be read/saved
+    a format; 2) determining whether a certain file can be read/written
     with this format; 3) providing a reader/writer class.
     
     Generally, imageio will select the right format and use that to
-    read/save an image. A format can also be explicitly chosen in all
-    read/save functios. Use ``print(format)``, or ``help(format_name)``
+    read/write an image. A format can also be explicitly chosen in all
+    read/write functions. Use ``print(format)``, or ``help(format_name)``
     to see its documentation.
     
     To implement a specific format, one should create a subclass of
@@ -147,12 +143,12 @@ class Format:
         """
         return self._modes
     
-    def read(self, request):
-        """ read(request)
+    def get_reader(self, request):
+        """ get_reader(request)
         
         Return a reader object that can be used to read data and info
-        from the given file. Users are encouraged to use imageio.read()
-        instead.
+        from the given file. Users are encouraged to use
+        imageio.get_reader() instead.
         """
         select_mode = request.mode[1] if request.mode[1] in 'iIvV' else ''
         if select_mode not in self.modes:
@@ -160,15 +156,16 @@ class Format:
                                (self.name, select_mode))
         return self.Reader(self, request)
     
-    def save(self, request):
-        """ save(request)
+    def get_writer(self, request):
+        """ get_writer(request)
         
-        Return a writer object that can be used to save data and info
-        to the given file. Users are encouraged to use imageio.save() instead.
+        Return a writer object that can be used to write data and info
+        to the given file. Users are encouraged to use
+        imageio.get_writer() instead.
         """
         select_mode = request.mode[1] if request.mode[1] in 'iIvV' else ''
         if select_mode not in self.modes:
-            raise RuntimeError('Format %s cannot save in mode %r' % 
+            raise RuntimeError('Format %s cannot write in mode %r' % 
                                (self.name, select_mode))
         return self.Writer(self, request)
     
@@ -179,17 +176,17 @@ class Format:
         """
         return self._can_read(request)
     
-    def can_save(self, request):
-        """ can_save(request)
+    def can_write(self, request):
+        """ can_write(request)
         
-        Get whether this format can save data to the speciefed uri.
+        Get whether this format can write data to the speciefed uri.
         """
-        return self._can_save(request)
+        return self._can_write(request)
     
     def _can_read(self, request):
         return None  # Plugins must implement this
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         return None  # Plugins must implement this
 
     # -----
@@ -211,14 +208,14 @@ class Format:
         @property
         def format(self):
             """ The :class:`.Format` object corresponding to the current
-            read/save operation.
+            read/write operation.
             """
             return self._format
         
         @property
         def request(self):
             """ The :class:`.Request` object corresponding to the
-            current read/save operation.
+            current read/write operation.
             """
             return self._request
         
@@ -292,7 +289,7 @@ class Format:
     class Reader(_BaseReaderWriter):
         """
         The purpose of a reader object is to read data from an image
-        resource, and should be obtained by calling :func:`.read`. 
+        resource, and should be obtained by calling :func:`.get_reader`. 
         
         A reader can be used as an iterator to read multiple images,
         and (if the format permits) only reads data from the file when
@@ -427,8 +424,8 @@ class Format:
     
     class Writer(_BaseReaderWriter):
         """ 
-        The purpose of a writer object is to save data to an image
-        resource, and should be obtained by calling :func:`.save`. 
+        The purpose of a writer object is to write data to an image
+        resource, and should be obtained by calling :func:`.get_writer`. 
         
         A writer will (if the format permits) write data to the file
         as soon as new data is provided (i.e. streaming). A writer can
@@ -588,14 +585,14 @@ class FormatManager:
                 if format.can_read(request):
                     return format
     
-    def search_save_format(self, request):
-        """ search_save_format(request)
+    def search_write_format(self, request):
+        """ search_write_format(request)
         
-        Search a format that can save a file according to the given request. 
+        Search a format that can write a file according to the given request. 
         Returns None if no appropriate format was found. (used internally)
         """
         select_mode = request.mode[1] if request.mode[1] in 'iIvV' else ''
         for format in self._formats:
             if select_mode in format.modes:
-                if format.can_save(request):
+                if format.can_write(request):
                     return format
diff --git a/imageio/core/functions.py b/imageio/core/functions.py
index c676fe2..ee87338 100644
--- a/imageio/core/functions.py
+++ b/imageio/core/functions.py
@@ -1,11 +1,11 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ 
 These functions represent imageio's main interface for the user. They
-provide a common API to read and save image data for a large
-variety of formats. All read and save functions accept keyword
+provide a common API to read and write image data for a large
+variety of formats. All read and write functions accept keyword
 arguments, which are passed on to the format that does the actual work.
 To see what keyword arguments are supported by a specific format, use
 the :func:`.help` function.
@@ -15,21 +15,21 @@ Functions for reading:
   * :func:`.imread` - read an image from the specified uri
   * :func:`.mimread` - read a series of images from the specified uri
   * :func:`.volread` - read a volume from the specified uri
-  * :func:`.mvolsave` - save a series of volumes to the specified uri
+  * :func:`.mvolread` - read a series of volumes from the specified uri
 
 Functions for saving:
 
-  * :func:`.imsave` - save an image to the specified uri
-  * :func:`.mimsave` - save a series of images to the specified uri
-  * :func:`.volsave` - save a volume to the specified uri
-  * :func:`.mvolread` - read a series of volumes from the specified uri
+  * :func:`.imwrite` - write an image to the specified uri
+  * :func:`.mimwrite` - write a series of images to the specified uri
+  * :func:`.volwrite` - write a volume to the specified uri
+  * :func:`.mvolwrite` - write a series of volumes to the specified uri
 
 More control:
 
-For a larger degree of control, imageio provides the functions
-:func:`.read` and :func:`.save`. They respectively return an
+For a larger degree of control, imageio provides functions
+:func:`.get_reader` and :func:`.get_writer`. They respectively return an
 :class:`.Reader` and an :class:`.Writer` object, which can
-be used to read/save data and meta data in a more controlled manner.
+be used to read/write data and meta data in a more controlled manner.
 This also allows specific scientific formats to be exposed in a way
 that best suits that file-format.
 
@@ -63,8 +63,9 @@ def help(name=None):
 
 ## Base functions that return a reader/writer
 
-def read(uri, format=None, mode='?', **kwargs):
-    """ read(uri, format=None, mode='?', **kwargs)
+
+def get_reader(uri, format=None, mode='?', **kwargs):
+    """ get_reader(uri, format=None, mode='?', **kwargs)
     
     Returns a :class:`.Reader` object which can be used to read data
     and meta data from the specified file.
@@ -100,19 +101,19 @@ def read(uri, format=None, mode='?', **kwargs):
                          'in mode %r' % mode)
     
     # Return its reader object
-    return format.read(request)
+    return format.get_reader(request)
 
 
-def save(uri, format=None, mode='?', **kwargs):
-    """ save(uri, format=None, mode='?', **kwargs)
+def get_writer(uri, format=None, mode='?', **kwargs):
+    """ get_writer(uri, format=None, mode='?', **kwargs)
     
-    Returns a :class:`.Writer` object which can be used to save data
+    Returns a :class:`.Writer` object which can be used to write data
     and meta data to the specified file.
     
     Parameters
     ----------
     uri : {str, file}
-        The resource to save the image to. This can be a normal
+        The resource to write the image to. This can be a normal
         filename, a file in a zipfile, a file object, or
         ``imageio.RETURN_BYTES``, in which case the raw bytes are
         returned.
@@ -135,13 +136,13 @@ def save(uri, format=None, mode='?', **kwargs):
     if format is not None:
         format = formats[format]
     else:
-        format = formats.search_save_format(request)
+        format = formats.search_write_format(request)
     if format is None:
-        raise ValueError('Could not find a format to save the specified file '
+        raise ValueError('Could not find a format to write the specified file '
                          'in mode %r' % mode)
     
     # Return its writer object
-    return format.save(request)
+    return format.get_writer(request)
 
 
 ## Images
@@ -172,15 +173,15 @@ def imread(uri, format=None, **kwargs):
         return reader.get_data(0)
 
 
-def imsave(uri, im, format=None, **kwargs):
-    """ imsave(uri, im, format=None, **kwargs)
+def imwrite(uri, im, format=None, **kwargs):
+    """ imwrite(uri, im, format=None, **kwargs)
     
-    Save an image to the specified file.
+    Write an image to the specified file.
     
     Parameters
     ----------
     uri : {str, file}
-        The resource to save the image to. This can be a normal
+        The resource to write the image to. This can be a normal
         filename, a file in a zipfile, a file object, or
         ``imageio.RETURN_BYTES``, in which case the raw bytes are
         returned.
@@ -206,7 +207,7 @@ def imsave(uri, im, format=None, **kwargs):
         raise ValueError('Image must be a numpy array.')
     
     # Get writer and write first
-    writer = save(uri, format, 'i', **kwargs)
+    writer = get_writer(uri, format, 'i', **kwargs)
     with writer:
         writer.append_data(im)
     
@@ -234,23 +235,43 @@ def mimread(uri, format=None, **kwargs):
     kwargs : ...
         Further keyword arguments are passed to the reader. See :func:`.help`
         to see what arguments are available for a particular format.
+    
+    Memory consumption
+    ------------------
+    This function will raise a RuntimeError when the read data consumes
+    over 256 MB of memory. This is to protect the system using so much
+    memory that it needs to resort to swapping, and thereby stall the
+    computer. E.g. ``mimread('hunger_games.avi')``.
     """ 
     
-    # Get reader and read all
+    # Get reader
     reader = read(uri, format, 'I', **kwargs)
-    with reader:
-        return [im for im in reader]
+    
+    # Read
+    ims = []
+    nbytes = 0
+    for im in reader:
+        ims.append(im)
+        # Memory check
+        nbytes += im.nbytes
+        if nbytes > 256 * 1024 * 1024:
+            ims[:] = []  # clear to free the memory
+            raise RuntimeError('imageio.mimread() has read over 256 MiB of '
+                               'image data.\nStopped to avoid memory problems.'
+                               ' Use imageio.get_reader() instead.')
+    
+    return ims
 
 
-def mimsave(uri, ims, format=None, **kwargs):
-    """ mimsave(uri, ims, format=None, **kwargs)
+def mimwrite(uri, ims, format=None, **kwargs):
+    """ mimwrite(uri, ims, format=None, **kwargs)
     
-    Save multiple images to the specified file.
+    Write multiple images to the specified file.
     
     Parameters
     ----------
     uri : {str, file}
-        The resource to save the images to. This can be a normal
+        The resource to write the images to. This can be a normal
         filename, a file in a zipfile, a file object, or
         ``imageio.RETURN_BYTES``, in which case the raw bytes are
         returned.
@@ -265,7 +286,7 @@ def mimsave(uri, ims, format=None, **kwargs):
     """ 
     
     # Get writer
-    writer = save(uri, format, 'I', **kwargs)
+    writer = get_writer(uri, format, 'I', **kwargs)
     with writer:
         
         # Iterate over images (ims may be a generator)
@@ -318,15 +339,15 @@ def volread(uri, format=None, **kwargs):
         return reader.get_data(0)
 
 
-def volsave(uri, im, format=None, **kwargs):
-    """ volsave(uri, vol, format=None, **kwargs)
+def volwrite(uri, im, format=None, **kwargs):
+    """ volwrite(uri, vol, format=None, **kwargs)
     
-    Save a volume to the specified file.
+    Write a volume to the specified file.
     
     Parameters
     ----------
     uri : {str, file}
-        The resource to save the image to. This can be a normal
+        The resource to write the image to. This can be a normal
         filename, a file in a zipfile, a file object, or
         ``imageio.RETURN_BYTES``, in which case the raw bytes are
         returned.
@@ -353,7 +374,7 @@ def volsave(uri, im, format=None, **kwargs):
         raise ValueError('Image must be a numpy array.')
     
     # Get writer and write first
-    writer = save(uri, format, 'v', **kwargs)
+    writer = get_writer(uri, format, 'v', **kwargs)
     with writer:
         writer.append_data(im)
     
@@ -389,15 +410,15 @@ def mvolread(uri, format=None, **kwargs):
         return [im for im in reader]
 
 
-def mvolsave(uri, ims, format=None, **kwargs):
-    """ mvolsave(uri, vols, format=None, **kwargs)
+def mvolwrite(uri, ims, format=None, **kwargs):
+    """ mvolwrite(uri, vols, format=None, **kwargs)
     
-    Save multiple volumes to the specified file.
+    Write multiple volumes to the specified file.
     
     Parameters
     ----------
     uri : {str, file}
-        The resource to save the volumes to. This can be a normal
+        The resource to write the volumes to. This can be a normal
         filename, a file in a zipfile, a file object, or
         ``imageio.RETURN_BYTES``, in which case the raw bytes are
         returned.
@@ -413,7 +434,7 @@ def mvolsave(uri, ims, format=None, **kwargs):
     """ 
     
     # Get writer
-    writer = save(uri, format, 'V', **kwargs)
+    writer = get_writer(uri, format, 'V', **kwargs)
     with writer:
         
         # Iterate over images (ims may be a generator)
@@ -436,3 +457,13 @@ def mvolsave(uri, ims, format=None, **kwargs):
     
     # Return a result if there is any
     return writer.request.get_result()
+
+
+## Aliases
+
+read = get_reader
+save = get_writer
+imsave = imwrite
+mimsave = mimwrite
+volsave = volwrite
+mvolsave = mvolwrite
diff --git a/imageio/core/request.py b/imageio/core/request.py
index 66bb0ee..6e3b8c2 100644
--- a/imageio/core/request.py
+++ b/imageio/core/request.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ 
@@ -16,7 +16,7 @@ import zipfile
 import tempfile
 import shutil
 
-from imageio.core import string_types, binary_type, urlopen, get_remote_file
+from ..core import string_types, binary_type, urlopen, get_remote_file
 
 # URI types
 URI_BYTES = 1
@@ -66,8 +66,8 @@ class Request(object):
     a simple interface to the plugins via ``get_file()`` and
     ``get_local_filename()``.
     
-    For each read/save operation a single Request instance is used and passed
-    to the can_read/can_save method of a format, and subsequently to
+    For each read/write operation a single Request instance is used and passed
+    to the can_read/can_write method of a format, and subsequently to
     the Reader/Writer class. This allows rudimentary passing of
     information between different formats and between a format and
     associated reader/writer.
diff --git a/imageio/core/util.py b/imageio/core/util.py
index 7df725a..d325d87 100644
--- a/imageio/core/util.py
+++ b/imageio/core/util.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ 
@@ -17,6 +17,7 @@ import struct
 import numpy as np
 
 IS_PYPY = '__pypy__' in sys.builtin_module_names
+THIS_DIR = os.path.abspath(os.path.dirname(__file__))
 
 # Taken from six.py
 PY3 = sys.version_info[0] == 3
@@ -45,6 +46,38 @@ def urlopen(*args, **kwargs):
     return urlopen(*args, **kwargs)
 
 
+def image_as_uint8(im):
+    """ Convert the given image to uint8
+    
+    If the dtype is already uint8, it is returned as-is. If the image
+    is float, and all values are between 0 and 1, the values are
+    multiplied by 255. In all other situations, the values are scaled
+    such that the minimum value becomes 0 and the maximum value becomes
+    255.
+    """
+    if not isinstance(im, np.ndarray):
+        raise ValueError('image must be a numpy array')
+    dtype_str = str(im.dtype)
+    # Already uint8?
+    if dtype_str == 'uint8':
+        return im
+    # Handle float
+    mi, ma = np.nanmin(im), np.nanmax(im)
+    if dtype_str.startswith('float'):
+        if mi >= 0 and ma <= 1:
+            mi, ma = 0, 1
+    # Now make float copy before we scale
+    im = im.astype('float32')
+    # Scale the values between 0 and 255
+    if np.isfinite(mi) and np.isfinite(ma):
+        if mi:
+            im -= mi
+        if ma != 255:
+            im *= 255.0 / (ma - mi)
+        assert np.nanmax(im) < 256
+    return im.astype(np.uint8)
+
+
 # currently not used ... the only use it to easly provide the global meta info
 class ImageList(list):
     def __init__(self, meta=None):
@@ -128,7 +161,7 @@ def asarray(a):
     function does not.
     """
     if isinstance(a, np.ndarray):
-        if IS_PYPY:
+        if IS_PYPY:  # pragma: no cover 
             a = a.copy()  # pypy has issues with base views
         plain = a.view(type=np.ndarray)
         return plain
@@ -373,6 +406,8 @@ def appdata_dir(appname=None, roaming=False):
     
     # Define default user directory
     userDir = os.path.expanduser('~')
+    if not os.path.isdir(userDir):  # pragma: no cover
+        userDir = '/var/tmp'  # issue #54
     
     # Get system app data dir
     path = None
@@ -412,8 +447,37 @@ def appdata_dir(appname=None, roaming=False):
     
     # Done
     return path
-   
-    
+
+
+def resource_dirs():
+    """ resource_dirs()
+    
+    Get a list of directories where imageio resources may be located.
+    The first directory in this list is the "resources" directory in
+    the package itself. The second directory is the appdata directory
+    (~/.imageio on Linux). The list further contains the application
+    directory (for frozen apps), and may include additional directories
+    in the future.
+    """
+    dirs = []
+    # Resource dir baked in the package
+    dirs.append(os.path.abspath(os.path.join(THIS_DIR, '..', 'resources')))
+    # Appdata directory
+    try:
+        dirs.append(appdata_dir('imageio'))
+    except Exception:
+        pass  # The home dir may not be writable
+    # Directory where the app is located (mainly for frozen apps)
+    if sys.path and sys.path[0]:
+        # Get the path. If frozen, sys.path[0] is the name of the executable,
+        # otherwise it is the path to the directory that contains the script.
+        thepath = sys.path[0]
+        if getattr(sys, 'frozen', None):
+            thepath = os.path.dirname(thepath)
+        dirs.append(os.path.abspath(thepath))
+    return dirs
+
+
 def get_platform():
     """ get_platform()
     
diff --git a/imageio/plugins/__init__.py b/imageio/plugins/__init__.py
index b2dfab5..4e0074c 100644
--- a/imageio/plugins/__init__.py
+++ b/imageio/plugins/__init__.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """
@@ -15,12 +15,12 @@ What is a plugin
 
 In imageio, a plugin provides one or more :class:`.Format` objects, and 
 corresponding :class:`.Reader` and :class:`.Writer` classes.
-Each Format object represents an implementation to read/save a 
+Each Format object represents an implementation to read/write a 
 particular file format. Its Reader and Writer classes do the actual
 reading/saving.
 
 The reader and writer objects have a ``request`` attribute that can be
-used to obtain information about the read or save :class:`.Request`, such as
+used to obtain information about the read or write :class:`.Request`, such as
 user-provided keyword arguments, as well get access to the raw image
 data.
 
@@ -55,7 +55,7 @@ For the Format class, the following needs to be implemented/specified:
     can supply via keyword arguments.
   * Implement ``_can_read(request)``, return a bool. 
     See also the :class:`.Request` class.
-  * Implement ``_can_save(request)``, dito.
+  * Implement ``_can_write(request)``, dito.
 
 For the Format.Reader class:
   
@@ -82,7 +82,7 @@ from . import freeimage  # noqa
 from . import freeimagemulti  # noqa
 from . import example  # noqa
 from . import dicom  # noqa
-from . import avbin  # noqa
 from . import ffmpeg  # noqa
+from . import avbin  # noqa
 from . import npz  # noqa
 from . import swf  # noqa
diff --git a/imageio/plugins/_freeimage.py b/imageio/plugins/_freeimage.py
index 4ef9a23..13205c5 100644
--- a/imageio/plugins/_freeimage.py
+++ b/imageio/plugins/_freeimage.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 # styletest: ignore E261
@@ -21,31 +21,34 @@ import ctypes
 import threading
 import numpy
 
-from imageio.core import get_remote_file, load_lib, Dict, appdata_dir
-from imageio.core import string_types, binary_type, IS_PYPY, get_platform
+from ..core import get_remote_file, load_lib, Dict, resource_dirs
+from ..core import string_types, binary_type, IS_PYPY, get_platform
 
 TEST_NUMPY_NO_STRIDES = False  # To test pypy fallback
 
+FNAME_PER_PLATFORM = {
+    'osx32': 'libfreeimage-3.16.0-osx10.6.dylib',  # universal library
+    'osx64': 'libfreeimage-3.16.0-osx10.6.dylib',
+    'win32': 'FreeImage-3.15.4-win32.dll',
+    'win64': 'FreeImage-3.15.1-win64.dll',
+    'linux32': 'libfreeimage-3.16.0-linux32.so',
+    'linux64': 'libfreeimage-3.16.0-linux64.so',
+}
+
 
 def get_freeimage_lib():
     """ Ensure we have our version of the binary freeimage lib.
     """ 
-    
-    LIBRARIES = {
-        'osx32': 'libfreeimage-3.16.0-osx10.6.dylib',  # universal library
-        'osx64': 'libfreeimage-3.16.0-osx10.6.dylib',
-        'win32': 'FreeImage-3.15.4-win32.dll',
-        'win64': 'FreeImage-3.15.1-win64.dll',
-        'linux32': 'libfreeimage-3.16.0-linux32.so',
-        'linux64': 'libfreeimage-3.16.0-linux64.so',
-    }
+    # todo: maybe use shipped if shipped is downloaded.
+    # download if system lib not found, or ... manually? how?
+    # Maybe place a symlink in place?
     
     # Get filename to load
     # If we do not provide a binary, the system may still do ...
     plat = get_platform()
     if plat:
         try:
-            return get_remote_file('freeimage/' + LIBRARIES[plat])
+            return get_remote_file('freeimage/' + FNAME_PER_PLATFORM[plat])
         except RuntimeError as e:  # pragma: no cover
             print(str(e))
 
@@ -317,7 +320,8 @@ class Freeimage(object):
         'FreeImage_GetPalette': (ctypes.c_void_p, None),
         'FreeImage_GetTagKey': (ctypes.c_char_p, None),
         'FreeImage_GetTagValue': (ctypes.c_void_p, None),
-        
+        'FreeImage_CreateTag': (ctypes.c_void_p, None),
+
         'FreeImage_Save': (ctypes.c_void_p, None),
         'FreeImage_Load': (ctypes.c_void_p, None),
         'FreeImage_LoadFromMemory': (ctypes.c_void_p, None),
@@ -335,6 +339,8 @@ class Freeimage(object):
         'FreeImage_GetFormatFromFIF': (ctypes.c_char_p, None),
         'FreeImage_GetFIFDescription': (ctypes.c_char_p, None),
         
+        'FreeImage_ColorQuantizeEx': (ctypes.c_void_p, None),
+
         # Pypy wants some extra definitions, so here we go ...
         'FreeImage_IsLittleEndian': (ctypes.c_int, None),
         'FreeImage_SetOutputMessage': (ctypes.c_void_p, None),
@@ -391,30 +397,42 @@ class Freeimage(object):
         self._error_handler = error_handler
         
         # Load library and register API
-        self._load_freeimage()        
-        self._register_api()
+        success = False
+        try:
+            # Try without forcing a download, but giving preference
+            # to the imageio-provided lib (if previously downloaded)
+            self._load_freeimage()
+            self._register_api()
+            if self._lib.FreeImage_GetVersion().decode('utf-8') >= '3.15':
+                success = True
+        except OSError:
+            pass
         
-        # Register logger for output messages
-        self._lib.FreeImage_SetOutputMessage(self._error_handler)
+        if not success:
+            # Ensure we have our own lib, try again
+            get_freeimage_lib()
+            self._load_freeimage()
+            self._register_api()
         
-        # Store version
+        # Wrap up
+        self._lib.FreeImage_SetOutputMessage(self._error_handler)
         self._lib_version = self._lib.FreeImage_GetVersion().decode('utf-8')
     
     def _load_freeimage(self):
         
-        # todo: we want to load from location relative to exe in frozen apps
-        # Get lib dirs
-        lib_dirs = [appdata_dir('imageio')]
-        
-        # Make sure that we have our binary version of the libary
-        lib_filename = get_freeimage_lib() or 'notavalidlibname'
-        
-        # Load library
+        # Define names
         lib_names = ['freeimage', 'libfreeimage']
-        exact_lib_names = [lib_filename, 'FreeImage', 'libfreeimage.dylib', 
+        exact_lib_names = ['FreeImage', 'libfreeimage.dylib', 
                            'libfreeimage.so', 'libfreeimage.so.3']
+        # Add names of libraries that we provide (that file may not exist)
+        fname = FNAME_PER_PLATFORM[get_platform()]
+        res_dirs = resource_dirs()
+        for dir in res_dirs:
+            exact_lib_names.insert(0, os.path.join(dir, 'freeimage', fname))
+        
+        # Load
         try:
-            lib, fname = load_lib(exact_lib_names, lib_names, lib_dirs)
+            lib, fname = load_lib(exact_lib_names, lib_names, res_dirs)
         except OSError:  # pragma: no cover
             # Could not load. Get why
             e_type, e_value, e_tb = sys.exc_info()
diff --git a/imageio/plugins/_swf.py b/imageio/plugins/_swf.py
index 430f0e7..6e602de 100644
--- a/imageio/plugins/_swf.py
+++ b/imageio/plugins/_swf.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 # This code was taken from visvis/vvmovy/images2swf.py
 
@@ -45,7 +45,7 @@ import time  # noqa
 
 import numpy as np
 
-from imageio.core import string_types, binary_type
+from ..core import string_types, binary_type
 
 PY3 = sys.version_info >= (3, )
 
diff --git a/imageio/plugins/avbin.py b/imageio/plugins/avbin.py
index 081d46f..224e00e 100644
--- a/imageio/plugins/avbin.py
+++ b/imageio/plugins/avbin.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Plugin for reading videos via AvBin
@@ -14,8 +14,17 @@ import numpy as np
 import ctypes
 import sys
 
-from imageio import formats
-from imageio.core import Format, get_platform, get_remote_file
+from .. import formats
+from ..core import Format, get_platform, get_remote_file
+
+
+FNAME_PER_PLATFORM = {
+    'osx64': 'libavbin-11alpha4-osx.dylib',
+    'win32': 'avbin-10-win32.dll',
+    'win64': 'avbin-10-win64.dll',
+    'linux32': 'libavbin-10-linux32.so',
+    'linux64': 'libavbin-10-linux64.so',
+}
 
 
 AVBIN_RESULT_ERROR = -1
@@ -129,19 +138,11 @@ def timestamp_from_avbin(timestamp):
 def get_avbin_lib():
     """ Get avbin .dll/.dylib/.so
     """
-
-    LIBRARIES = {
-        'osx64': 'libavbin-11alpha4-osx.dylib',
-        'win32': 'avbin-10-win32.dll',
-        'win64': 'avbin-10-win64.dll',
-        'linux32': 'libavbin-10-linux32.so',
-        'linux64': 'libavbin-10-linux64.so',
-    }
     
     platform = get_platform()
     
     try:
-        lib = LIBRARIES[platform]
+        lib = FNAME_PER_PLATFORM[platform]
     except KeyError:  # pragma: no cover
         raise RuntimeError('Avbin plugin is not supported on platform %s' % 
                            platform)
@@ -210,7 +211,7 @@ class AvBinFormat(Format):
                 if request.filename.endswith('.' + ext):
                     return True
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         return False  # AvBin does not support writing videos
     
     def avbinlib(self, libpath=None):
@@ -436,7 +437,7 @@ class AvBinFormat(Format):
 # Register. You register an *instance* of a Format class. Here specify:
 format = AvBinFormat('avbin',  # short name
                      'Many video formats (via AvBin, i.e. libav library)',  
-                     'mov avi mp4 mpg mpeg',  # list of extensions
+                     'mov avi mp4 mpg mpeg mkv',  # list of extensions
                      'I'  # modes, characters in iIvV
                      )
 formats.add_format(format)
diff --git a/imageio/plugins/dicom.py b/imageio/plugins/dicom.py
index 12d162d..cf12c53 100644
--- a/imageio/plugins/dicom.py
+++ b/imageio/plugins/dicom.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Plugin for reading DICOM files.
@@ -21,9 +21,9 @@ import struct
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format, BaseProgressIndicator, StdoutProgressIndicator
-from imageio.core import string_types, read_n_bytes
+from .. import formats
+from ..core import Format, BaseProgressIndicator, StdoutProgressIndicator
+from ..core import string_types, read_n_bytes
 
 
 # Determine endianity of system
@@ -72,7 +72,7 @@ class DicomFormat(Format):
         # Check
         return request.firstbytes[128:132] == b'DICM'
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         # We cannot save yet. May be possible if we will used pydicom as
         # a backend.
         return False
diff --git a/imageio/plugins/example.py b/imageio/plugins/example.py
index 2756001..c0d0f0f 100644
--- a/imageio/plugins/example.py
+++ b/imageio/plugins/example.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Example plugin. You can use this as a template for your own plugin.
@@ -9,15 +9,15 @@ from __future__ import absolute_import, print_function, division
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format
+from .. import formats
+from ..core import Format
 
 
 class DummyFormat(Format):
     """ The dummy format is an example format that does nothing.
-    It will never indicate that it can read or save a file. When
+    It will never indicate that it can read or write a file. When
     explicitly asked to read, it will simply read the bytes. When 
-    explicitly asked to save, it will raise an error.
+    explicitly asked to write, it will raise an error.
     
     This documentation is shown when the user does ``help('thisformat')``.
     
@@ -53,9 +53,9 @@ class DummyFormat(Format):
                 if request.filename.endswith('.' + ext):
                     return True
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         # This method is called when the format manager is searching
-        # for a format to save a certain image. Return True if the
+        # for a format to write a certain image. Return True if the
         # format can do it.
         #
         # In most cases, the code does suffice.
@@ -69,7 +69,7 @@ class DummyFormat(Format):
     
     class Reader(Format.Reader):
     
-        def _open(self, some_option=False):
+        def _open(self, some_option=False, length=1):
             # Specify kwargs here. Optionally, the user-specified kwargs
             # can also be accessed via the request.kwargs object.
             #
@@ -78,7 +78,9 @@ class DummyFormat(Format):
             #  - Use request.get_file() for a file object (preferred)
             #  - Use request.get_local_filename() for a file on the system
             self._fp = self.request.get_file()
-        
+            self._length = length  # passed as an arg in this case for testing
+            self._data = None
+            
         def _close(self):
             # Close the reader. 
             # Note that the request object will close self._fp
@@ -86,16 +88,17 @@ class DummyFormat(Format):
         
         def _get_length(self):
             # Return the number of images. Can be np.inf
-            return 1
+            return self._length
         
         def _get_data(self, index):
             # Return the data and meta data for the given index
-            if index != 0:
-                raise IndexError('Dummy format only supports singleton images')
+            if index >= self._length:
+                raise IndexError('Image index %i > %i' % (index, self._length))
             # Read all bytes
-            data = self._fp.read()
+            if self._data is None:
+                self._data = self._fp.read()
             # Put in a numpy array
-            im = np.frombuffer(data, 'uint8')
+            im = np.frombuffer(self._data, 'uint8')
             im.shape = len(im), 1
             # Return array and dummy meta data
             return im, {}
@@ -126,18 +129,18 @@ class DummyFormat(Format):
         
         def _append_data(self, im, meta):
             # Process the given data and meta data.
-            raise RuntimeError('The dummy format cannot save image data.')
+            raise RuntimeError('The dummy format cannot write image data.')
         
         def set_meta_data(self, meta):
             # Process the given meta data (global for all images)
             # It is not mandatory to support this.
-            raise RuntimeError('The dummy format cannot save meta data.')
+            raise RuntimeError('The dummy format cannot write meta data.')
 
 
 # Register. You register an *instance* of a Format class. Here specify:
 format = DummyFormat('dummy',  # short name
                      'An example format that does nothing.',  # one line descr.
-                     '',  # list of extensions as a space separated string
-                     ''  # modes, characters in iIvV
+                     '.png',  # list of extensions as a space separated string
+                     'iI'  # modes, characters in iIvV
                      )
 formats.add_format(format)
diff --git a/imageio/plugins/ffmpeg.py b/imageio/plugins/ffmpeg.py
index 0d1f5f3..45eb927 100644
--- a/imageio/plugins/ffmpeg.py
+++ b/imageio/plugins/ffmpeg.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Plugin that uses ffmpeg to read and write series of images to
@@ -18,33 +18,43 @@ import stat
 import re
 import time
 import threading
-import struct
 import subprocess as sp
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format, get_remote_file, string_types, read_n_bytes
+from .. import formats
+from ..core import (Format, get_remote_file, string_types, read_n_bytes, 
+                    image_as_uint8, get_platform)
+
+FNAME_PER_PLATFORM = {
+    'osx32': 'ffmpeg.osx.snowleopardandabove',
+    'osx64': 'ffmpeg.osx.snowleopardandabove',
+    'win32': 'ffmpeg.win32.exe',
+    'win64': 'ffmpeg.win32.exe',
+    'linux32': 'ffmpeg.linux32',
+    'linux64': 'ffmpeg.linux64',
+}
 
 
 def get_exe():
     """ Get ffmpeg exe
     """
-    NBYTES = struct.calcsize('P') * 8
-    if sys.platform.startswith('linux'):
-        fname = 'ffmpeg.linux%i' % NBYTES
-    elif sys.platform.startswith('win'):
-        fname = 'ffmpeg.win32.exe'
-    elif sys.platform.startswith('darwin'):
-        fname = 'ffmpeg.osx.snowleopardandabove'
-    else:  # pragma: no cover
-        fname = 'ffmpeg'  # hope for the best
-    #
-    FFMPEG_EXE = 'ffmpeg'
-    if fname:
-        FFMPEG_EXE = get_remote_file('ffmpeg/' + fname)
-        os.chmod(FFMPEG_EXE, os.stat(FFMPEG_EXE).st_mode | stat.S_IEXEC)  # exe
-    return FFMPEG_EXE
+    plat = get_platform()
+    
+    if plat and plat in FNAME_PER_PLATFORM:
+        try:
+            exe = get_remote_file('ffmpeg/' + FNAME_PER_PLATFORM[plat])
+            os.chmod(exe, os.stat(exe).st_mode | stat.S_IEXEC)  # executable
+            return exe
+        except OSError:  # pragma: no cover
+            print("Warning: could not load imageio's ffmpeg library.")
+            e_type, e_value, e_tb = sys.exc_info()
+            del e_tb
+            print(str(e_value))
+    
+    # Fallback, let's hope the system has ffmpeg
+    return 'ffmpeg'
+
 
 # Get camera format
 if sys.platform.startswith('win'):
@@ -113,7 +123,7 @@ class FfmpegFormat(Format):
             if request.filename.endswith('.' + ext):
                 return True
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         if request.mode[1] in (self.modes + '?'):
             for ext in self.extensions:
                 if request.filename.endswith('.' + ext):
@@ -462,8 +472,7 @@ class FfmpegFormat(Format):
             depth = 1 if im.ndim == 2 else im.shape[2]
             
             # Ensure that image is in uint8
-            if im.dtype != np.uint8:
-                im = im.astype(np.uint8)  # pypy: no support copy=False
+            im = image_as_uint8(im)
             
             # Set size and initialize if not initialized yet
             if self._size is None:
@@ -655,5 +664,5 @@ class StreamCatcher(threading.Thread):
 
 # Register. You register an *instance* of a Format class.
 format = FfmpegFormat('ffmpeg', 'Many video formats and cameras (via ffmpeg)', 
-                      '.mov .avi .mpg .mpeg .mp4', 'I')
+                      '.mov .avi .mpg .mpeg .mp4 .mkv', 'I')
 formats.add_format(format)
diff --git a/imageio/plugins/freeimage.py b/imageio/plugins/freeimage.py
index d3b26d6..fd16bb3 100644
--- a/imageio/plugins/freeimage.py
+++ b/imageio/plugins/freeimage.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Plugin that wraps the freeimage lib. The wrapper for Freeimage is
@@ -11,9 +11,10 @@ from __future__ import absolute_import, print_function, division
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format
-from ._freeimage import fi, IO_FLAGS
+
+from .. import formats
+from ..core import Format, image_as_uint8
+from ._freeimage import fi, IO_FLAGS, FNAME_PER_PLATFORM  # noqa
 
 
 # todo: support files with only meta data
@@ -56,7 +57,7 @@ class FreeimageFormat(Format):
             if request._fif == self.fif:
                 return True
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         modes = self._modes + '?'
         if fi and request.mode[1] in modes:
             if not hasattr(request, '_fif'):
@@ -165,8 +166,7 @@ class FreeimageBmpFormat(FreeimageFormat):
             return FreeimageFormat.Writer._open(self, flags)
         
         def _append_data(self, im, meta):
-            if im.dtype in (np.float32, np.float64):
-                im = (im * 255).astype(np.uint8)
+            im = image_as_uint8(im)
             return FreeimageFormat.Writer._append_data(self, im, meta)
 
 
@@ -223,8 +223,7 @@ class FreeimagePngFormat(FreeimageFormat):
             return FreeimageFormat.Writer._open(self, flags)
         
         def _append_data(self, im, meta):
-            if im.dtype in (np.float32, np.float64):
-                im = (im * 255).astype(np.uint8)
+            im = image_as_uint8(im)
             FreeimageFormat.Writer._append_data(self, im, meta)
             # Quantize?
             q = int(self.request.kwargs.get('quantize', False))
@@ -337,8 +336,7 @@ class FreeimageJpegFormat(FreeimageFormat):
         def _append_data(self, im, meta):
             if im.ndim == 3 and im.shape[-1] == 4:
                 raise IOError('JPEG does not support alpha channel.')
-            if im.dtype in (np.float32, np.float64):
-                im = (im * 255).astype(np.uint8)
+            im = image_as_uint8(im)
             return FreeimageFormat.Writer._append_data(self, im, meta)
 
 
diff --git a/imageio/plugins/freeimagemulti.py b/imageio/plugins/freeimagemulti.py
index 9446513..7475e44 100644
--- a/imageio/plugins/freeimagemulti.py
+++ b/imageio/plugins/freeimagemulti.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Plugin for multi-image freeimafe formats, like animated GIF and ico.
@@ -9,8 +9,8 @@ from __future__ import absolute_import, print_function, division
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format
+from .. import formats
+from ..core import Format, image_as_uint8
 from ._freeimage import fi, IO_FLAGS
 from .freeimage import FreeimageFormat
 
@@ -73,8 +73,7 @@ class FreeimageMulti(FreeimageFormat):
             # Prepare data
             if im.ndim == 3 and im.shape[-1] == 1:
                 im = im[:, :, 0]
-            if im.dtype in (np.float32, np.float64):
-                im = (im * 255).astype(np.uint8)
+            im = image_as_uint8(im)
             # Create sub bitmap
             sub1 = fi.create_bitmap(self._bm._filename, self.format.fif)
             # Let subclass add data to bitmap, optionally return new
@@ -105,7 +104,7 @@ class MngFormat(FreeimageMulti):
     
     _fif = 6
     
-    def _can_save(self, request):  # pragma: no cover
+    def _can_write(self, request):  # pragma: no cover
         return False
     
 
@@ -155,7 +154,9 @@ class GifFormat(FreeimageMulti):
     duration : {float, list}
         The duration (in seconds) of each frame. Either specify one value
         that is used for all frames, or one value for each frame.
-        Default 0.1
+    fps : float
+        The number of frames per second. If duration is not given, the
+        duration for each frame is set to 1/fps. Default 10.
     palettesize : int
         The number of colors to quantize the image to. Is rounded to
         the nearest power of two. Default 256.
@@ -196,8 +197,8 @@ class GifFormat(FreeimageMulti):
         # todo: subrectangles
         # todo: global palette
         
-        def _open(self, flags=0, loop=0, duration=0.1, palettesize=256, 
-                  quantizer='Wu', subrectangles=False):
+        def _open(self, flags=0, loop=0, duration=None, fps=10, 
+                  palettesize=256, quantizer='Wu', subrectangles=False):
             # Check palettesize
             if palettesize < 2 or palettesize > 256:
                 raise ValueError('PNG quantize param must be 2..256')
@@ -211,7 +212,9 @@ class GifFormat(FreeimageMulti):
             if self._quantizer is None:
                 raise ValueError('Invalid quantizer, must be "wu" or "nq".')
             # Check frametime
-            if isinstance(duration, list):
+            if duration is None:
+                self._frametime = [int(1000 / float(fps))]
+            elif isinstance(duration, list):
                 self._frametime = [int(1000 * d) for d in duration]
             elif isinstance(duration, (float, int)):
                 self._frametime = [int(1000 * duration)]
diff --git a/imageio/plugins/npz.py b/imageio/plugins/npz.py
index da52629..ef7db3c 100644
--- a/imageio/plugins/npz.py
+++ b/imageio/plugins/npz.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ Storage of image data in npz format. Not a great format, but at least
@@ -10,8 +10,8 @@ from __future__ import absolute_import, print_function, division
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format
+from .. import formats
+from ..core import Format
 
 
 class NpzFormat(Format):
@@ -20,7 +20,7 @@ class NpzFormat(Format):
     shape, and also supports multiple images per file. 
     
     However, the npz format does not provide streaming; all data is
-    read/saved at once. Further, there is no support for meta data.
+    read/written at once. Further, there is no support for meta data.
 
     Beware that the numpy npz format has a bug on a certain combination
     of Python 2.7 and numpy, which can cause the resulting files to
@@ -42,7 +42,7 @@ class NpzFormat(Format):
         else:
             return False
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         if request.filename.lower().endswith('.npz'):
             return True  # We support any kind of image data
         else:
diff --git a/imageio/plugins/swf.py b/imageio/plugins/swf.py
index 9f31f5e..3122498 100644
--- a/imageio/plugins/swf.py
+++ b/imageio/plugins/swf.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # imageio is distributed under the terms of the (new) BSD License.
 
 """ SWF plugin. Most of the actual work is done in _swf.py.
@@ -13,8 +13,8 @@ from io import BytesIO
 
 import numpy as np
 
-from imageio import formats
-from imageio.core import Format, read_n_bytes
+from .. import formats
+from ..core import Format, read_n_bytes, image_as_uint8
 
 from . import _swf
 
@@ -62,7 +62,7 @@ class SWFFormat(Format):
             if tmp in ('FWS', 'CWS'):
                 return True
     
-    def _can_save(self, request):
+    def _can_write(self, request):
         if request.mode[1] in (self.modes + '?'):
             for ext in self.extensions:
                 if request.filename.endswith('.' + ext):
@@ -290,8 +290,7 @@ class SWFFormat(Format):
             # Correct shape and type
             if im.ndim == 3 and im.shape[-1] == 1:
                 im = im[:, :, 0]
-            if im.dtype in (np.float32, np.float64):
-                im = (im * 255).astype(np.uint8)
+            im = image_as_uint8(im)
             # Get frame size
             wh = im.shape[1], im.shape[0]
             # Write header on first frame
diff --git a/imageio/resources/shipped_resources_go_here b/imageio/resources/shipped_resources_go_here
new file mode 100644
index 0000000..e69de29
diff --git a/imageio/testing.py b/imageio/testing.py
index 41ac91a..d27dd31 100644
--- a/imageio/testing.py
+++ b/imageio/testing.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2014, imageio contributors
+# Copyright (c) 2015, imageio contributors
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
 """ Functionality used for testing. This code itself is not covered in tests.
@@ -61,6 +61,7 @@ def get_test_dir():
         # Clear and create it now
         clean_test_dir(True)
         os.makedirs(_the_test_dir)
+        os.makedirs(os.path.join(_the_test_dir, 'images'))
         # And later
         atexit.register(clean_test_dir)
     return _the_test_dir
@@ -75,6 +76,11 @@ def clean_test_dir(strict=False):
                 raise
         
 
+def need_internet():
+    if os.getenv('IMAGEIO_NO_INTERNET', '').lower() in ('1', 'true', 'yes'):
+        pytest.skip('No internet')
+
+
 ## Functions to use from make
 
 def test_unit(cov_report='term'):
diff --git a/make/README.md b/make/README.md
new file mode 100644
index 0000000..31c0410
--- /dev/null
+++ b/make/README.md
@@ -0,0 +1,12 @@
+This provides tools for developers to work with imageio.
+Use like this:
+    
+    python make command arg  
+
+To get available commands:
+    
+    python make
+
+To get help on a command (e.g. on the `test` command):
+    
+    python make test
diff --git a/make/__init__.py b/make/__init__.py
new file mode 100644
index 0000000..ba02d1c
--- /dev/null
+++ b/make/__init__.py
@@ -0,0 +1,6 @@
+"""
+By putting make.py in a package, we can do "python make" instead of
+"python make.py".
+"""
+
+from .maker import Maker  # noqa
diff --git a/make/__main__.py b/make/__main__.py
new file mode 100644
index 0000000..3b927dc
--- /dev/null
+++ b/make/__main__.py
@@ -0,0 +1,15 @@
+# Launch maker
+
+import os
+import sys
+
+START_DIR = os.path.abspath(os.getcwd())
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, ROOT_DIR)
+
+from make import Maker
+
+try:
+    Maker(sys.argv)
+finally:
+    os.chdir(START_DIR)
diff --git a/make/maker.py b/make/maker.py
new file mode 100644
index 0000000..999a8ac
--- /dev/null
+++ b/make/maker.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, imageio contributors
+# Distributed under the (new) BSD License. 
+
+# Originally developed under the Vispy project.
+
+"""
+Convenience tools for developers
+
+    python make command [arg]
+
+"""
+
+from __future__ import absolute_import, print_function, division
+
+import sys
+import os
+from os import path as op
+import time
+import shutil
+import webbrowser
+
+
+# Save where we came frome and where this module lives
+START_DIR = op.abspath(os.getcwd())
+THIS_DIR = op.abspath(op.dirname(__file__))
+
+# Get root directory of the package, by looking for setup.py
+for subdir in ['.', '..']:
+    ROOT_DIR = op.abspath(op.join(THIS_DIR, subdir))
+    if op.isfile(op.join(ROOT_DIR, 'setup.py')):
+        break
+else:
+    sys.exit('Cannot find root dir')
+
+
+# Define directories and repos of interest
+DOC_DIR = op.join(ROOT_DIR, 'docs')
+#
+WEBSITE_DIR = op.join(ROOT_DIR, '_website')
+WEBSITE_REPO = 'git at github.com:imageio/imageio'
+#
+PAGES_DIR = op.join(ROOT_DIR, '_gh-pages')
+PAGES_REPO = 'git at github.com:imageio/imageio.github.io.git'
+
+
+class Maker:
+    """ Collection of make commands.
+
+    To create a new command, create a method with a short name, give it
+    a docstring, and make it do something useful :)
+
+    """
+
+    def __init__(self, argv):
+        """ Parse command line arguments. """
+        # Get function to call
+        if len(argv) == 1:
+            func, arg = self.help, ''
+        else:
+            command = argv[1].strip()
+            arg = ' '.join(argv[2:]).strip()
+            func = getattr(self, command, None)
+        # Call it if we can
+        if func is not None:
+            func(arg)
+        else:
+            sys.exit('Invalid command: "%s"' % command)
+    
+    def help(self, arg):
+        """ Show help message. Use 'help X' to get more help on command X. """
+        if arg:
+            command = arg
+            func = getattr(self, command, None)
+            if func is not None:
+                doc = getattr(self, command).__doc__.strip()
+                print('make %s [arg]\n\n        %s' % (command, doc))
+                print()
+            else:
+                sys.exit('Cannot show help on unknown command: "%s"' % command)
+
+        else:
+            print(__doc__.strip() + '\n\nCommands:\n')
+            for command in sorted(dir(self)):
+                if command.startswith('_'):
+                    continue
+                preamble = command.ljust(11)  # longest command is 9 or 10
+                # doc = getattr(self, command).__doc__.splitlines()[0].strip()
+                doc = getattr(self, command).__doc__.strip()
+                print(' %s  %s' % (preamble, doc))
+            print()
+    
+    def clean(self, arg):
+        """ Clean the repo of .pyc files and __pycache__ directories.
+        """
+        # Remove files
+        for dir, dirnames, filenames in os.walk(ROOT_DIR):
+            if dir.startswith('.'):
+                continue
+            for fname in filenames:
+                if fname.endswith('.pyc'):
+                    filename = os.path.join(dir, fname)
+                    fname_r = os.path.relpath(filename, ROOT_DIR)
+                    try:
+                        os.remove(filename)
+                        print('Removed %r' % fname_r)
+                    except Exception as err:
+                        print('Could not remove %r: %s' % (fname_r, str(err)))
+        # Remove directories
+        for dir, dirnames, filenames in os.walk(ROOT_DIR):
+            if dir.startswith('.'):
+                continue
+            for dname in dirnames:
+                if dname == '__pycache__':
+                    dirname = os.path.join(dir, dname)
+                    dname_r = os.path.relpath(dirname, ROOT_DIR)
+                    try:
+                        os.rmdir(dirname)
+                        print('Removed dir %r' % dname_r)
+                    except Exception as err:
+                        print('Could not remove %r: %s' % (dname_r, str(err)))
+    
+    def doc(self, arg):
+        """ Make API documentation:
+                * clean - clean html
+                * html - build html
+                * show - show the docs in your browser
+        """
+        # Prepare
+        build_dir = op.join(DOC_DIR, '_build')
+        if not arg:
+            return self.help('doc')
+        # Go
+        for a in arg.split(' '):
+            if 'clean' == a:
+                sphinx_clean(build_dir)
+            elif 'html' == a:
+                sphinx_build(DOC_DIR, build_dir)
+            elif 'show' == a:
+                index_html = op.join(build_dir, 'html', 'index.html')
+                if not op.isfile(index_html):
+                    sys.exit('Cannot show pages, build the html first.')
+                webbrowser.open_new_tab(index_html)
+            else:
+                sys.exit('Command "doc" does not have subcommand "%s"' % arg)
+    
+    def test(self, arg):
+        """ Run tests:
+                * unit - run unit tests
+                * installed - run unit tests using installed version
+                * style - flake style testing (PEP8 and more)
+                * cover - show coverage html report
+                
+        """
+        if not arg:
+            return self.help('test')
+        
+        from imageio import testing
+        
+        if arg in ('flake', 'style'):
+            try:
+                testing.test_style()
+            except RuntimeError as err:
+                sys.exit(str(err))
+        
+        elif arg == 'unit':
+            sys.exit(testing.test_unit())
+        
+        elif arg == 'installed':
+            # Like unit, but give preference to installed package.
+            # And do not allow the use of an internet connection.
+            for p in list(sys.path):
+                if p in ('', '.'):
+                    sys.path.remove(p)
+                elif p == ROOT_DIR or p == os.path.dirname(ROOT_DIR):
+                    sys.path.remove(p)
+            os.environ['IMAGEIO_NO_INTERNET'] = '1'
+            sys.exit(testing.test_unit())
+        
+        elif arg == 'cover':
+            res = testing.test_unit(cov_report='html')
+            if res:
+                raise RuntimeError('Cannot show coverage, tests failed.')
+            print('Launching browser.')
+            fname = op.join(os.getcwd(), 'htmlcov', 'index.html')
+            if not op.isfile(fname):
+                raise IOError('Generated file not found: %s' % fname)
+            webbrowser.open_new_tab(fname)
+        
+        else:
+            raise RuntimeError('Invalid arg for make test: %r' % arg)
+    
+    def copyright(self, arg):
+        """ Update all copyright notices to the current year.
+        """
+        # Initialize
+        TEMPLATE = "# Copyright (c) %i, imageio contributors"
+        CURYEAR = int(time.strftime('%Y'))
+        OLDTEXT = TEMPLATE % (CURYEAR - 1)
+        NEWTEXT = TEMPLATE % CURYEAR
+        # Initialize counts
+        count_ok, count_replaced = 0, 0
+
+        # Processing the whole root directory
+        for dirpath, dirnames, filenames in os.walk(ROOT_DIR):
+            # Check if we should skip this directory
+            reldirpath = op.relpath(dirpath, ROOT_DIR)
+            if reldirpath[0] in '._' or reldirpath.endswith('__pycache__'):
+                continue
+            if reldirpath.startswith('build') or reldirpath.startswith('dist'):
+                continue
+            # Process files
+            for fname in filenames:
+                if not fname.endswith('.py'):
+                    continue
+                # Open and check
+                filename = op.join(dirpath, fname)
+                text = open(filename, 'rt').read()
+                if NEWTEXT in text:
+                    count_ok += 1
+                elif OLDTEXT in text:
+                    text = text.replace(OLDTEXT, NEWTEXT)
+                    open(filename, 'wt').write(text)
+                    print(
+                        '  Update copyright year in %s/%s' %
+                        (reldirpath, fname))
+                    count_replaced += 1
+                elif 'copyright' in text[:200].lower():
+                    print(
+                        '  Unknown copyright mentioned in %s/%s' %
+                        (reldirpath, fname))
+        # Report
+        print('Replaced %i copyright statements' % count_replaced)
+        print('Found %i copyright statements up to date' % count_ok)
+
+
+## Helper functions
+
+def sphinx_clean(build_dir):
+    if op.isdir(build_dir):
+        shutil.rmtree(build_dir)
+    os.mkdir(build_dir)
+    print('Cleared build directory.')
+
+
+def sphinx_build(src_dir, build_dir):
+    import sphinx
+    ret = sphinx.main(('sphinx-build',  # Dummy
+                       '-b', 'html',
+                       '-d', op.join(build_dir, 'doctrees'),
+                       src_dir,  # Source
+                       op.join(build_dir, 'html'),  # Dest
+                       ))
+    if ret != 0:
+        raise RuntimeError('Sphinx error: %s' % ret)
+    print("Build finished. The HTML pages are in %s/html." % build_dir)
diff --git a/setup.py b/setup.py
index 7a3d0c7..b99f91c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2014, imageio contributors
+# Copyright (C) 2015, imageio contributors
 #
 # imageio is distributed under the terms of the (new) BSD License.
 # The full license can be found in 'license.txt'.
@@ -16,6 +16,7 @@ Before release:
   * Run test suite on OS X
   * Write release notes
   * Check if docs are still good
+  * Maybe test pypi page via "python setup.py register -r test"
 
 Release:
 
@@ -23,11 +24,9 @@ Release:
   * git tag the release
   * Upload to Pypi:
     * python setup.py register
-    * python setup.py sdist upload
+    * python setup.py sdist_all upload
   * Update, build and upload conda package
 
-Add "-r testpypi" to use the test repo. 
-
 After release:
 
   * Set __version__ to dev
@@ -36,18 +35,29 @@ After release:
 """
 
 import os
+import os.path as op
 import sys
-from distutils.core import setup
+import shutil
+from distutils.core import Command
+from distutils.command.sdist import sdist
+from distutils.command.build_py import build_py
+
+try:
+    from setuptools import setup  # Supports wheels
+except ImportError:
+    from distutils.core import setup  # Supports anything else
+
 
 name = 'imageio'
 description = 'Library for reading and writing a wide range of image formats.'
 
+THIS_DIR = os.path.dirname(os.path.abspath(__file__))
 
 # Get version and docstring
 __version__ = None
 __doc__ = ''
 docStatus = 0 # Not started, in progress, done
-initFile = os.path.join(os.path.dirname(__file__), 'imageio',  '__init__.py')
+initFile = os.path.join(THIS_DIR, 'imageio',  '__init__.py')
 for line in open(initFile).readlines():
     if (line.startswith('__version__')):
         exec(line.strip())
@@ -80,14 +90,191 @@ Example:
     >>> im = imageio.imread('astronaut.png')
     >>> im.shape  # im is a numpy array
     (512, 512, 3)
-    >>> imageio.imsave('astronaut-gray.jpg', im[:, :, 0])
+    >>> 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>`_
 for more information.
+
+All distribution files are independent of the Python version. The
+platform-specific archives contain a few images and the freeimage
+library for that platform. These are recommended if you do not want to
+rely on an internet connection at runtime / install-time.
+
 """
 
+# Collect files to more or less reproduce the repo in the dist package.
+# In that way the tests can be run and docs be build for Debian packaging.
+#
+# Collect docs
+docs_files = [os.path.join('docs', fn) 
+              for fn in os.listdir(op.join(THIS_DIR, 'docs'))]
+docs_files += [op.join('docs', 'ext', fn) 
+               for fn in os.listdir(op.join(THIS_DIR, 'docs', 'ext'))]
+docs_files = [fn for fn in docs_files if op.isfile(op.join(THIS_DIR, fn))]
+# Collect test files
+test_files = [os.path.join('tests', fn)
+              for fn in os.listdir(os.path.join(THIS_DIR, 'tests'))
+              if (fn.endswith('.py') or fn.endswith('.md'))]
+# Collect make files
+make_files = [os.path.join('make', fn)
+              for fn in os.listdir(os.path.join(THIS_DIR, 'make'))
+              if (fn.endswith('.py') or fn.endswith('.md'))]
+
+# Prepare resources dir
+package_data = []
+package_data.append('resources/shipped_resources_go_here')
+package_data.append('resources/*.*')
+package_data.append('resources/images/*.*')
+package_data.append('resources/freeimage/*.*')
+package_data.append('resources/ffmpeg/*.*')
+package_data.append('resources/avbin/*.*')
+
+
+def _set_crossplatform_resources(resource_dir):
+    import imageio
+    
+    # Clear now
+    if op.isdir(resource_dir):
+        shutil.rmtree(resource_dir)
+    os.mkdir(resource_dir)
+    open(op.join(resource_dir, 'shipped_resources_go_here'), 'wb')
+    
+    # Load images
+    for fname in ['images/chelsea.png',
+                  'images/chelsea.zip',
+                  'images/astronaut.png',
+                  'images/newtonscradle.gif',
+                  'images/cockatoo.mp4',
+                  'images/realshort.mp4',
+                  'images/stent.npz',
+                  ]:
+        imageio.core.get_remote_file(fname, resource_dir, 
+                                     force_download=True)
+
+
+def _set_platform_resources(resource_dir, platform):
+    import imageio
+    
+    # Create file to show platform
+    open(op.join(resource_dir, 'platform_%s' % platform), 'wb')
+    
+    # Load freeimage
+    fname = imageio.plugins.freeimage.FNAME_PER_PLATFORM[platform]
+    imageio.core.get_remote_file('freeimage/'+fname, resource_dir,
+                                    force_download=True)
+    
+    # Load ffmpeg
+    #fname = imageio.plugins.ffmpeg.FNAME_PER_PLATFORM[platform]
+    #imageio.core.get_remote_file('ffmpeg/'+fname, resource_dir, 
+    #                             force_download=True)
+
+
+class test_command(Command):
+    user_options = []
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        from imageio import testing
+        os.environ['IMAGEIO_NO_INTERNET'] = '1'  # run tests without inet
+        sys.exit(testing.test_unit())
+
+
+class build_with_fi(build_py):
+    def run(self):
+        # Download images and libs
+        import imageio
+        resource_dir = imageio.core.resource_dirs()[0]
+        _set_crossplatform_resources(resource_dir)
+        _set_platform_resources(resource_dir, imageio.core.get_platform())
+        # Build as  normal
+        build_py.run(self)
+
+
+class sdist_all(sdist):
+    """ Build all platform specific dist files, that contain
+    a few images and the freeimage lib of the platform.
+    """
+
+    def run(self):
+        sdist.run(self)
+        
+        import imageio
+        
+        # Get base tarbal
+        import tarfile
+        distdir = op.join(THIS_DIR, 'dist')
+        tarfilename = op.join(distdir, 'imageio-%s.tar.gz' % __version__)
+        assert op.isfile(tarfilename)
+        
+        # Create/clean build dir
+        build_dir = op.join(distdir, 'temp')
+        if op.isdir(build_dir):
+            shutil.rmtree(build_dir)
+        os.mkdir(build_dir)
+        
+        # Extract, get resource dir
+        with tarfile.open(tarfilename, 'r:gz') as tf:
+            tf.extractall(build_dir)
+        resource_dir = op.join(build_dir, 'imageio-%s' % __version__, 
+                               'imageio', 'resources')
+        assert os.path.isdir(resource_dir)
+        
+        # Prepare the libs resource directory with cross-platform
+        # resources, so we can copy these for each platform
+        _set_crossplatform_resources(imageio.core.resource_dirs()[0])
+        
+        # Create archives
+        dist_files = self.distribution.dist_files
+        for plat in ['', 'linux64', 'linux32', 'win64', 'win32', 'osx64']:
+            fname = self._create_dists_for_platform(resource_dir, plat)
+            dist_files.append(('sdist', 'any', 'dist/'+fname))
+        
+        # Clean up
+        shutil.rmtree(build_dir)
+    
+    
+    def _create_dists_for_platform(self, resource_dir, plat):
+        import zipfile
+        import imageio
+        
+        # Copy over crossplatform resources and add platform specifics
+        shutil.rmtree(resource_dir)
+        if plat:
+            shutil.copytree(imageio.core.resource_dirs()[0], resource_dir)
+            _set_platform_resources(resource_dir, plat)
+        else:
+            os.mkdir(resource_dir)
+            open(op.join(resource_dir, 'shipped_resources_go_here'), 'wb')
+        
+        # Zip it
+        distdir = op.join(THIS_DIR, 'dist')
+        build_dir = op.join(distdir, 'temp')
+        zipfname = 'imageio-%s.zip' % __version__
+        if plat:
+            zipfname = 'imageio-%s-%s.zip' % (__version__, plat)
+        zipfilename = op.join(distdir, zipfname)
+        zf = zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED)
+        for root, dirs, files in os.walk(build_dir):
+            for fname in files:
+                filename = op.join(root, fname)
+                relpath = op.relpath(filename, build_dir)
+                relpath = relpath.replace('imageio-%s' % __version__,
+                                          zipfname[:-4])
+                zf.write(filename, relpath)
+        zf.close()
+        return zipfname
+
+
 setup(
+    cmdclass={'sdist_all': sdist_all, 
+              'build_with_fi': build_with_fi,
+              'test': test_command},
+    
     name = name,
     version = __version__,
     author = 'imageio contributors',
@@ -96,7 +283,7 @@ setup(
     
     url = 'http://imageio.github.io/',
     download_url = 'http://pypi.python.org/pypi/imageio',    
-    keywords = "image imread imsave io animation volume FreeImage ffmpeg",
+    keywords = "image imread imwrite io animation volume FreeImage ffmpeg",
     description = description,
     long_description = long_description.replace('__doc__', __doc__),
     
@@ -107,6 +294,15 @@ setup(
     packages = ['imageio', 'imageio.core', 'imageio.plugins'],
     package_dir = {'imageio': 'imageio'}, 
     
+    # Data in the package
+    package_data = {'imageio': package_data},
+    
+    # Data in the dist package
+    data_files = [('tests', test_files),
+                  ('docs', docs_files), 
+                  ('make', make_files), 
+                  ('', ['LICENSE', 'README.md', 'CONTRIBUTORS.txt'])],
+    
     classifiers = [
         'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Science/Research',
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..9e9a276
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,16 @@
+In imageio all tests are contained in a single directory. There should
+be one test module per plugin.
+
+We aim for 100% test coverage for imageio.core, and over 90% for each plugin.
+
+We use the excelent test.py package, use a hook to Travis for continuous 
+integration, and to Coveralls for coverage reporting.
+
+To run tests locally run:
+    
+    python make test unit
+    python make test style
+
+To show coverage locally, run:
+    
+    python make test cover
diff --git a/tests/test_avbin.py b/tests/test_avbin.py
new file mode 100644
index 0000000..1bac5d1
--- /dev/null
+++ b/tests/test_avbin.py
@@ -0,0 +1,184 @@
+""" Test imageio avbin functionality.
+"""
+
+from pytest import raises
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import get_remote_file
+
+# if IS_PYPY:
+#     skip('AVBIn not supported on pypy')
+
+test_dir = get_test_dir()
+
+mean = lambda x: x.sum() / x.size  # pypy-compat mean
+
+
+def test_select():
+    
+    fname1 = get_remote_file('images/cockatoo.mp4', test_dir)
+    
+    F = imageio.formats['avbin']
+    assert F.name == 'AVBIN'
+    
+    assert F.can_read(core.Request(fname1, 'rI'))
+    assert not F.can_write(core.Request(fname1, 'wI'))
+    assert not F.can_read(core.Request(fname1, 'ri'))
+    assert not F.can_read(core.Request(fname1, 'rv'))
+    
+    # ffmpeg is default
+    #formats = imageio.formats
+    #assert formats['.mp4'] is F
+    #assert formats.search_write_format(core.Request(fname1, 'wI')) is F
+    #assert formats.search_read_format(core.Request(fname1, 'rI')) is F
+
+
+def test_read():
+    need_internet()
+    
+    R = imageio.read(get_remote_file('images/cockatoo.mp4'), 'avbin')
+    assert R.format is imageio.formats['avbin']
+    
+    fname = get_remote_file('images/cockatoo.mp4', force_download='2014-11-05')
+    reader = imageio.read(fname, 'avbin')
+    assert reader.get_length() == 280
+    assert 'fps' in reader.get_meta_data()
+    raises(Exception, imageio.save, '~/foo.mp4', 'abin')
+    #assert not reader.format.can_write(core.Request('test.mp4', 'wI'))
+    
+    for i in range(10):
+        im = reader.get_next_data()
+        assert im.shape == (720, 1280, 3)
+        # todo: fix this
+        #assert mean(im) > 100 and mean(im) < 115  KNOWN FAIL
+    
+    # We can rewind
+    reader.get_data(0)
+    
+    # But not seek
+    with raises(IndexError):    
+        reader.get_data(4)
+
+
+def test_reader_more():
+    need_internet()
+    
+    fname1 = get_remote_file('images/cockatoo.mp4')
+    fname3 = fname1[:-4] + '.stub.mp4'
+    
+    # Get meta data
+    R = imageio.read(fname1, 'avbin', loop=True)
+    meta = R.get_meta_data()
+    assert isinstance(meta, dict)
+    assert 'fps' in meta
+    R.close()
+    
+    # Read all frames and test length
+    R = imageio.read(get_remote_file('images/realshort.mp4'), 'avbin')
+    count = 0
+    while True:
+        try:
+            R.get_next_data()
+        except IndexError:
+            break
+        else:
+            count += 1
+    assert count == len(R)
+    assert count in (35, 36)  # allow one frame off size that we know
+    # Test index error -1
+    raises(IndexError, R.get_data, -1)
+    
+    # Test loop
+    R = imageio.read(get_remote_file('images/realshort.mp4'), 'avbin', loop=1)
+    im1 = R.get_next_data()
+    for i in range(1, len(R)):
+        R.get_next_data()
+    im2 = R.get_next_data()
+    im3 = R.get_data(0)
+    assert (im1 == im2).all()
+    assert (im1 == im3).all()
+    R.close()
+    
+    # Test size when skipping empty frames, are there *any* valid frames?
+    # todo: use mimread once 1) len(R) == inf, or 2) len(R) is correct
+    R = imageio.read(get_remote_file('images/realshort.mp4'), 
+                     'avbin', skipempty=True)
+    ims = []
+    with R:
+        try: 
+            while True:
+                ims.append(R.get_next_data())
+        except IndexError:
+            pass
+    assert len(ims) > 20  # todo: should be 35/36 but with skipempty ...
+    
+    # Read invalid
+    open(fname3, 'wb')
+    raises(IOError, imageio.read, fname3, 'avbin')
+
+
+def test_read_format():
+    need_internet()
+    
+    # Set videofomat
+    # Also set skipempty, so we can test mean
+    reader = imageio.read(get_remote_file('images/cockatoo.mp4'), 'avbin',
+                          videoformat='mp4', skipempty=True)
+    for i in range(10):
+        im = reader.get_next_data()
+        assert im.shape == (720, 1280, 3)
+        assert mean(im) > 100 and mean(im) < 115
+
+
+def test_stream():
+    need_internet()
+    
+    with raises(IOError):
+        imageio.read(get_remote_file('images/cockatoo.mp4'), 'avbin', stream=5)
+  
+  
+def test_invalidfile():
+    need_internet()
+    
+    filename = test_dir+'/empty.mp4'
+    with open(filename, 'w'):
+        pass
+    
+    with raises(IOError):
+        imageio.read(filename, 'avbin')
+    
+    # Check AVbinResult
+    imageio.plugins.avbin.AVbinResult(imageio.plugins.avbin.AVBIN_RESULT_OK)
+    for i in (2, 3, 4):
+        with raises(RuntimeError):
+            imageio.plugins.avbin.AVbinResult(i)
+
+
+def show_in_mpl():
+    reader = imageio.read('cockatoo.mp4', 'avbin')
+    for i in range(10):
+        reader.get_next_data()
+       
+    import pylab
+    pylab.ion()
+    pylab.show(reader.get_next_data())
+
+
+def show_in_visvis():
+    reader = imageio.read('cockatoo.mp4', 'avbin')
+    #reader = imageio.read('<video0>')
+    
+    import visvis as vv
+    im = reader.get_next_data()
+    f = vv.clf()
+    f.title = reader.format.name
+    t = vv.imshow(im, clim=(0, 255))
+    
+    while not f._destroyed:
+        t.SetData(reader.get_next_data())
+        vv.processEvents()
+    
+
+run_tests_if_main()
diff --git a/tests/test_core.py b/tests/test_core.py
new file mode 100644
index 0000000..edb2c90
--- /dev/null
+++ b/tests/test_core.py
@@ -0,0 +1,849 @@
+""" Test imageio core functionality.
+"""
+
+import time
+import sys
+import os
+import gc
+import shutil
+import ctypes.util
+from zipfile import ZipFile
+from io import BytesIO
+
+import numpy as np
+
+from pytest import raises
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import Format, FormatManager, Request
+from imageio.core import get_remote_file, IS_PYPY
+
+test_dir = get_test_dir()
+
+
+class MyFormat(Format):
+    """ TEST DOCS """
+    _closed = []
+    
+    class Reader(Format.Reader):
+        _failmode = False
+        _stream_mode = False
+        
+        def _open(self):
+            self._read_frames = 0
+        
+        def _close(self):
+            self.format._closed.append(id(self))
+        
+        def _get_length(self):
+            if self._stream_mode:
+                return np.inf
+            return 3
+        
+        def _get_data(self, index):
+            if self._failmode == 2:
+                raise IndexError()
+            elif self._failmode:
+                return 'not an array', {}
+            elif self._stream_mode and self._read_frames >= 5:
+                raise IndexError()  # Mark end of stream
+            else:
+                self._read_frames += 1
+                return np.ones((10, 10)) * index, self._get_meta_data(index)
+        
+        def _get_meta_data(self, index):
+            if self._failmode:
+                return 'not a dict'
+            return {'index': index}
+            
+    class Writer(Format.Writer):
+        
+        def _open(self):
+            self._written_data = []
+            self._written_meta = []
+            self._meta = None
+        
+        def _close(self):
+            self.format._closed.append(id(self))
+        
+        def _append_data(self, im, meta):
+            self._written_data.append(im)
+            self._written_meta.append(meta)
+        
+        def _set_meta_data(self, meta):
+            self._meta = meta
+
+
+def test_format():
+    """ Test the working of the Format class """
+    
+    filename1 = get_remote_file('images/chelsea.png', test_dir)
+    filename2 = filename1 + '.out'
+    
+    # Test basic format creation
+    F = Format('testname', 'test description', 'foo bar spam')
+    assert F.name == 'TESTNAME'
+    assert F.description == 'test description'
+    assert F.name in repr(F)
+    assert F.name in F.doc
+    assert str(F) == F.doc
+    assert set(F.extensions) == set(['foo', 'bar', 'spam'])
+    
+    # Test setting extensions
+    F1 = Format('test', '', 'foo bar spam')
+    F2 = Format('test', '', 'foo, bar,spam')
+    F3 = Format('test', '', ['foo', 'bar', 'spam'])
+    F4 = Format('test', '', '.foo .bar .spam')
+    for F in (F1, F2, F3, F4):
+        assert set(F.extensions) == set(['foo', 'bar', 'spam'])
+    # Fail
+    raises(ValueError, Format, 'test', '', 3)  # not valid ext
+    raises(ValueError, Format, 'test', '', '', 3)  # not valid mode
+    raises(ValueError, Format, 'test', '', '', 'x')  # not valid mode
+    
+    # Test subclassing
+    F = MyFormat('test', '', modes='i')
+    assert 'TEST DOCS' in F.doc
+    
+    # Get and check reader and write classes
+    R = F.get_reader(Request(filename1, 'ri'))
+    W = F.get_writer(Request(filename2, 'wi'))
+    assert isinstance(R, MyFormat.Reader)
+    assert isinstance(W, MyFormat.Writer)
+    assert R.format is F
+    assert W.format is F
+    assert R.request.filename == filename1
+    assert W.request.filename == filename2
+    # Fail
+    raises(RuntimeError, F.get_reader, Request(filename1, 'rI'))
+    raises(RuntimeError, F.get_writer, Request(filename2, 'wI'))
+    
+    # Use as context manager
+    with R:
+        pass
+    with W:
+        pass
+    # Objects are now closed, cannot be used
+    assert R.closed
+    assert W.closed
+    #
+    raises(RuntimeError, R.__enter__)
+    raises(RuntimeError, W.__enter__)
+    #
+    raises(RuntimeError, R.get_data, 0)
+    raises(RuntimeError, W.append_data, np.zeros((10, 10)))
+    
+    # Test __del__
+    R = F.get_reader(Request(filename1, 'ri'))
+    W = F.get_writer(Request(filename2, 'wi'))
+    ids = id(R), id(W)
+    F._closed[:] = []
+    del R
+    del W
+    gc.collect()  # Invoke __del__
+    assert set(ids) == set(F._closed)
+
+
+def test_reader_and_writer():
+    
+    # Prepare
+    filename1 = get_remote_file('images/chelsea.png', test_dir)
+    filename2 = filename1 + '.out'
+    F = MyFormat('test', '', modes='i')
+    
+    # Test using reader
+    n = 3
+    R = F.get_reader(Request(filename1, 'ri'))
+    assert len(R) == n
+    ims = [im for im in R]
+    assert len(ims) == n
+    for i in range(3):
+        assert ims[i][0, 0] == i
+        assert ims[i].meta['index'] == i
+    for i in range(3):
+        assert R.get_meta_data(i)['index'] == i
+    # Read next
+    assert R.get_data(0)[0, 0] == 0
+    assert R.get_next_data()[0, 0] == 1
+    assert R.get_next_data()[0, 0] == 2
+    # Fail
+    R._failmode = 1
+    raises(ValueError, R.get_data, 0)
+    raises(ValueError, R.get_meta_data, 0)
+    R._failmode = 2
+    with raises(IndexError):
+        [im for im in R]
+    
+    # Test streaming reader
+    R = F.get_reader(Request(filename1, 'ri'))
+    R._stream_mode = True
+    assert R.get_length() == np.inf
+    ims = [im for im in R]
+    assert len(ims) == 5
+    
+    # Test using writer
+    im1 = np.zeros((10, 10))
+    im2 = imageio.core.Image(im1, {'foo': 1})
+    W = F.get_writer(Request(filename2, 'wi'))
+    W.append_data(im1)
+    W.append_data(im2)
+    W.append_data(im1, {'bar': 1})
+    W.append_data(im2, {'bar': 1})
+    # Test that no data is copies (but may be different views)
+    assert len(W._written_data) == 4 
+    for im in W._written_data:
+        assert (im == im1).all()
+    im1[2, 2] == 99
+    for im in W._written_data:
+        assert (im == im1).all()
+    # Test meta
+    assert W._written_meta[0] == {}
+    assert W._written_meta[1] == {'foo': 1}
+    assert W._written_meta[2] == {'bar': 1}
+    assert W._written_meta[3] == {'foo': 1, 'bar': 1}
+    #
+    W.set_meta_data({'spam': 1})
+    assert W._meta == {'spam': 1}
+    # Fail
+    raises(ValueError, W.append_data, 'not an array')
+    raises(ValueError, W.append_data, im, 'not a dict')
+    raises(ValueError, W.set_meta_data, 'not a dict')
+
+
+def test_default_can_read_and_can_write():
+    
+    F = imageio.plugins.example.DummyFormat('test', '', 'foo bar', 'v')
+    
+    # Prepare files
+    filename1 = os.path.join(test_dir, 'test')
+    open(filename1 + '.foo', 'wb')
+    open(filename1 + '.bar', 'wb')
+    open(filename1 + '.spam', 'wb')
+    
+    # Test _can_read()
+    assert F.can_read(Request(filename1 + '.foo', 'rv'))
+    assert F.can_read(Request(filename1 + '.bar', 'r?'))
+    assert not F.can_read(Request(filename1 + '.spam', 'r?'))
+    assert not F.can_read(Request(filename1 + '.foo', 'ri'))
+    
+    # Test _can_write()
+    assert F.can_write(Request(filename1 + '.foo', 'wv'))
+    assert F.can_write(Request(filename1 + '.bar', 'w?'))
+    assert not F.can_write(Request(filename1 + '.spam', 'w?'))
+    assert not F.can_write(Request(filename1 + '.foo', 'wi'))
+
+
+def test_format_manager():
+    """ Test working of the format manager """
+    
+    formats = imageio.formats
+    
+    # Test basics of FormatManager
+    assert isinstance(formats, FormatManager)
+    assert len(formats) > 0
+    assert 'FormatManager' in repr(formats)
+    
+    # Get docs
+    smalldocs = str(formats)
+    #fulldocs = formats.create_docs_for_all_formats()
+    
+    # Check each format ...
+    for format in formats:
+        #  That each format is indeed a Format
+        assert isinstance(format, Format)
+        # That they are mentioned
+        assert format.name in smalldocs
+        #assert format.name in fulldocs
+    
+    fname = get_remote_file('images/chelsea.png', test_dir)
+    fname2 = fname[:-3] + 'noext'
+    shutil.copy(fname, fname2)
+    
+    # Check getting
+    F1 = formats['PNG']
+    F2 = formats['.png']
+    F3 = formats[fname2]  # will look in file itself
+    assert F1 is F2
+    assert F1 is F3
+    # Check getting
+    F1 = formats['DICOM']
+    F2 = formats['.dcm']
+    F3 = formats['dcm']  # If omitting dot, format is smart enough to try with
+    assert F1 is F2
+    assert F1 is F3
+    # Fail
+    raises(ValueError, formats.__getitem__, 678)  # must be str
+    raises(IndexError, formats.__getitem__, '.nonexistentformat')
+    
+    # Adding a format
+    myformat = Format('test', 'test description', 'testext1 testext2')
+    formats.add_format(myformat)
+    assert myformat in [f for f in formats]
+    assert formats['testext1'] is myformat
+    assert formats['testext2'] is myformat
+    # Fail
+    raises(ValueError, formats.add_format, 678)  # must be Format
+    raises(ValueError, formats.add_format, myformat)  # cannot add twice
+    
+    # Test searchinhg for read / write format
+    F = formats.search_read_format(Request(fname, 'ri'))
+    assert F is formats['PNG']
+    F = formats.search_write_format(Request(fname, 'wi'))
+    assert F is formats['PNG']
+#   # Potential
+#   bytes = b'x' * 300
+#   F = formats.search_read_format(Request(bytes, 'r?', dummy_potential=1))
+#   assert F is formats['DUMMY']
+
+
+def test_fetching():
+    """ Test fetching of files """
+    
+    need_internet()
+    
+    # Clear image files
+    if os.path.isdir(test_dir):
+        shutil.rmtree(test_dir)   
+    
+    # This should download the file (force download, because local cache)
+    fname1 = get_remote_file('images/chelsea.png', test_dir, True)
+    mtime1 = os.path.getmtime(fname1)
+    # This should reuse it
+    fname2 = get_remote_file('images/chelsea.png', test_dir)
+    mtime2 = os.path.getmtime(fname2)
+    # This should overwrite
+    fname3 = get_remote_file('images/chelsea.png', test_dir, True)
+    mtime3 = os.path.getmtime(fname3)
+    # This should too (update this if imageio is still around in 1000 years)
+    fname4 = get_remote_file('images/chelsea.png', test_dir, '3014-01-01')
+    mtime4 = os.path.getmtime(fname4)
+    # This should not
+    fname5 = get_remote_file('images/chelsea.png', test_dir, '2014-01-01')
+    mtime5 = os.path.getmtime(fname4)
+    # 
+    assert os.path.isfile(fname1)
+    assert fname1 == fname2
+    assert fname1 == fname3
+    assert fname1 == fname4
+    assert fname1 == fname5
+    if not sys.platform.startswith('darwin'):
+        # weird, but these often fail on my osx VM
+        assert mtime1 == mtime2
+        assert mtime1 < mtime3
+        assert mtime3 < mtime4  
+        assert mtime4 == mtime5
+    
+    # Test failures
+    _urlopen = core.fetching.urlopen
+    _chunk_read = core.fetching._chunk_read
+    #
+    raises(IOError, get_remote_file, 'this_does_not_exist', test_dir)
+    #
+    try:
+        core.fetching.urlopen = None
+        raises(IOError, get_remote_file, 'images/chelsea.png', None, True)
+    finally:
+        core.fetching.urlopen = _urlopen
+    #
+    try:
+        core.fetching._chunk_read = None
+        raises(IOError, get_remote_file, 'images/chelsea.png', None, True)
+    finally:
+        core.fetching._chunk_read = _chunk_read
+    #
+    try:
+        os.environ['IMAGEIO_NO_INTERNET'] = '1'
+        raises(IOError, get_remote_file, 'images/chelsea.png', None, True)
+    finally:
+        del os.environ['IMAGEIO_NO_INTERNET']
+    # Coverage miss
+    assert '0 bytes' == core.fetching._sizeof_fmt(0)
+
+
+def test_findlib():
+    """ Test finding of libs """
+    
+    if not sys.platform.startswith('linux'):
+        return
+    
+    # Candidate libs for common lib
+    dirs, paths = core.findlib.generate_candidate_libs(['libpython'])
+    assert paths
+    
+    # Candidate libs for common freeimage
+    fi_dir = os.path.join(core.appdata_dir('imageio'), 'freeimage')
+    if not os.path.isdir(fi_dir):
+        os.mkdir(fi_dir)
+    dirs, paths = core.findlib.generate_candidate_libs(['libfreeimage'], 
+                                                       [fi_dir])
+    #assert fi_dir in dirs -> Cannot test: lib may not exist
+    assert paths
+    
+    open(os.path.join(fi_dir, 'notalib.test.so'), 'wb')
+    
+    # Loading libs
+    gllib = ctypes.util.find_library('GL')
+    core.load_lib([gllib], [])
+    core.load_lib([], ['libfreeimage'], [fi_dir])
+    # Fail
+    raises(ValueError, core.load_lib, [], [])  # Nothing given
+    raises(ValueError, core.load_lib, ['', None], [])  # Nothing given
+    raises(OSError, core.load_lib, ['thislibdoesnotexist_foobar'], [])
+    raises(OSError, core.load_lib, [], ['notalib'], [fi_dir])
+
+
+def test_request():
+    """ Test request object """
+    
+    # Check uri-type, this is not a public property, so we test the private
+    R = Request('http://example.com', 'ri')
+    assert R._uri_type == core.request.URI_HTTP
+    R = Request('ftp://example.com', 'ri')
+    assert R._uri_type == core.request.URI_FTP
+    R = Request('file://example.com', 'wi')
+    assert R._uri_type == core.request.URI_FILENAME
+    R = Request('<video0>', 'rI')
+    assert R._uri_type == core.request.URI_BYTES
+    R = Request(imageio.RETURN_BYTES, 'wi')
+    assert R._uri_type == core.request.URI_BYTES
+    #
+    fname = get_remote_file('images/chelsea.png', test_dir)
+    R = Request(fname, 'ri')
+    assert R._uri_type == core.request.URI_FILENAME
+    R = Request('/file/that/does/not/exist', 'wi')
+    assert R._uri_type == core.request.URI_FILENAME  # Too short to be bytes
+    R = Request(b'x'*600, 'ri')
+    assert R._uri_type == core.request.URI_BYTES
+    R = Request(sys.stdin, 'ri')
+    assert R._uri_type == core.request.URI_FILE
+    R = Request(sys.stdout, 'wi')
+    assert R._uri_type == core.request.URI_FILE
+    # exapand user dir
+    R = Request('~/foo', 'wi')
+    assert R.filename == os.path.expanduser('~/foo')
+    # zip file
+    R = Request('/foo/bar.zip/spam.png', 'wi')
+    assert R._uri_type == core.request.URI_ZIPPED
+    
+    # Test failing inits
+    raises(ValueError, Request, '/some/file', None)  # mode must be str
+    raises(ValueError, Request, '/some/file', 3)  # mode must be str
+    raises(ValueError, Request, '/some/file', '')  # mode must be len 2
+    raises(ValueError, Request, '/some/file', 'r')  # mode must be len 2
+    raises(ValueError, Request, '/some/file', 'rii')  # mode must be len 2
+    raises(ValueError, Request, '/some/file', 'xi')  # mode[0] must be in rw
+    raises(ValueError, Request, '/some/file', 'rx')  # mode[1] must be in iIvV?
+    #
+    raises(IOError, Request, ['invalid', 'uri'] * 10, 'ri')  # invalid uri
+    raises(IOError, Request, 4, 'ri')  # invalid uri
+    raises(IOError, Request, '/does/not/exist', 'ri')  # reading nonexistent
+    raises(IOError, Request, '/does/not/exist.zip/spam.png', 'ri')  # dito
+    raises(IOError, Request, 'http://example.com', 'wi')  # no writing here
+    
+    # Test auto-download
+    R = Request('chelsea.png', 'ri')
+    assert R.filename == get_remote_file('images/chelsea.png')
+    #
+    R = Request('chelsea.zip/chelsea.png', 'ri')
+    assert R._filename_zip[0] == get_remote_file('images/chelsea.zip')
+    assert R.filename == get_remote_file('images/chelsea.zip') + '/chelsea.png'
+
+
+def test_request_read_sources():
+    
+    # Make an image available in many ways
+    fname = 'images/chelsea.png'
+    filename = get_remote_file(fname, test_dir)
+    bytes = open(filename, 'rb').read()
+    #
+    burl = 'https://raw.githubusercontent.com/imageio/imageio-binaries/master/'
+    z = ZipFile(os.path.join(test_dir, 'test.zip'), 'w')
+    z.writestr(fname, bytes)
+    z.close()
+    file = open(filename, 'rb')
+    
+    # Read that image from these different sources. Read data from file
+    # and from local file (the two main plugin-facing functions)
+    for uri in (filename, 
+                os.path.join(test_dir, 'test.zip', fname),
+                bytes,
+                file,
+                burl + fname):
+        # Init
+        firsbytes_list, bytes_list = [], []
+        # Via file
+        R = Request(uri, 'ri')
+        firsbytes_list.append(R.firstbytes)
+        bytes_list.append(R.get_file().read())
+        R.finish()
+        # Via local filename
+        R = Request(uri, 'ri')
+        firsbytes_list.append(R.firstbytes)
+        f = open(R.get_local_filename(), 'rb')
+        bytes_list.append(f.read())
+        R.finish()
+        # Test repeated
+        if uri == filename:
+            bytes_list.append(R.get_file().read())
+            f = open(R.get_local_filename(), 'rb')
+            bytes_list.append(f.read())
+            R.finish()
+        # Test
+        for i in range(len(firsbytes_list)):
+            assert len(firsbytes_list[i]) > 0
+            assert bytes.startswith(firsbytes_list[i])
+        for i in range(len(bytes_list)):
+            assert bytes == bytes_list[i]
+
+
+def test_request_save_sources():
+    
+    # Prepare desinations
+    fname = 'images/chelsea.png'
+    filename = get_remote_file(fname, test_dir)
+    bytes = open(filename, 'rb').read()
+    #
+    fname2 = fname + '.out'
+    filename2 = os.path.join(test_dir, fname2)
+    zipfilename2 = os.path.join(test_dir, 'test.zip')
+    file2 = BytesIO()
+    
+    # Write an image into many different destinations
+    # Do once via file and ones via local filename
+    for i in range(2):
+        # Clear
+        for xx in (filename2, zipfilename2):
+            if os.path.isfile(xx):
+                os.remove(xx)
+        # Write to three destinations
+        for uri in (filename2, 
+                    os.path.join(zipfilename2, fname2),
+                    file2,
+                    imageio.RETURN_BYTES  # This one last to fill `res`
+                    ):
+            R = Request(uri, 'wi')
+            if i == 0:
+                R.get_file().write(bytes)  # via file
+            else:
+                open(R.get_local_filename(), 'wb').write(bytes)  # via local
+            R.finish()
+            res = R.get_result()
+        # Test three results
+        assert open(filename2, 'rb').read() == bytes
+        assert ZipFile(zipfilename2, 'r').open(fname2).read() == bytes
+        assert res == bytes
+
+
+def test_request_file_no_seek():
+    
+    class File():
+        
+        def read(self, n):
+            return b'\x00' * n
+            
+        def seek(self, i):
+            raise IOError('Not supported')
+        
+        def tell(self):
+            raise Exception('Not supported')
+        
+        def close(self):
+            pass
+    
+    R = Request(File(), 'ri')
+    with raises(IOError):
+        R.firstbytes
+
+
+def test_util_imagelist():
+    meta = {'foo': 3, 'bar': {'spam': 1, 'eggs': 2}}
+    
+    # Image list
+    L = core.util.ImageList(meta)
+    assert isinstance(L, list)
+    assert L.meta == meta
+    # Fail
+    raises(ValueError, core.util.ImageList, 3)  # not a dict
+
+
+def test_util_image():
+    meta = {'foo': 3, 'bar': {'spam': 1, 'eggs': 2}}
+    # Image 
+    a = np.zeros((10, 10))
+    im = core.util.Image(a, meta)
+    isinstance(im, np.ndarray)
+    isinstance(im.meta, dict)
+    assert str(im) == str(a)
+    # Preserve after action
+    im2 = im + 1
+    assert isinstance(im2, core.util.Image)
+    assert im2.meta == im.meta
+    # Turn to normal array / scalar if shape none
+    im2 = im.sum(0)
+    if not IS_PYPY:  # known fail on Pypy
+        assert not isinstance(im2, core.util.Image)
+    s = im.sum()
+    assert not isinstance(s, core.util.Image)
+    # Repr !! no more
+    #assert '2D image' in repr(core.util.Image(np.zeros((10, 10))))
+    #assert '2D image' in repr(core.util.Image(np.zeros((10, 10, 3))))
+    #assert '3D image' in repr(core.util.Image(np.zeros((10, 10, 10))))
+    # Fail
+    raises(ValueError, core.util.Image, 3)  # not a ndarray
+    raises(ValueError, core.util.Image, a, 3)  # not a dict
+
+
+def test_util_dict():
+    # Dict class
+    D = core.Dict()
+    D['foo'] = 1
+    D['bar'] = 2
+    D['spam'] = 3
+    assert list(D.values()) == [1, 2, 3]
+    #
+    assert D.spam == 3
+    D.spam = 4
+    assert D['spam'] == 4
+    # Can still use copy etc.
+    assert D.copy() == D
+    assert 'spam' in D.keys()
+    # Can also insert non-identifiers
+    D[3] = 'not an identifier'
+    D['34a'] = 'not an identifier'
+    D[None] = 'not an identifier'
+    # dir
+    names = dir(D)
+    assert 'foo' in names
+    assert 'spam' in names
+    assert 3 not in names
+    assert '34a' not in names
+    # Fail
+    raises(AttributeError, D.__setattr__, 'copy', False)  # reserved
+    raises(AttributeError, D.__getattribute__, 'notinD')
+
+
+def test_util_get_platform():
+    # Test get_platform
+    platforms = 'win32', 'win64', 'linux32', 'linux64', 'osx32', 'osx64'
+    assert core.get_platform() in platforms
+
+
+def test_util_asarray():
+    # Test asarray
+    im1 = core.asarray([[1, 2, 3], [4, 5, 6]])
+    im2 = im1.view(type=core.Image)
+    im3 = core.asarray(im2)
+    assert type(im2) != np.ndarray
+    assert type(im3) == np.ndarray
+    if not IS_PYPY:
+        for i in (1, 2, 3):
+            im1[0, 0] = i
+            assert im2[0, 0] == i
+            assert im3[0, 0] == i
+
+
+def test_util_progres_bar(sleep=0):
+    """ Test the progress bar """
+    # This test can also be run on itself to *see* the result
+    
+    # Progress bar
+    for Progress in (core.StdoutProgressIndicator, core.BaseProgressIndicator):
+        B = Progress('test')
+        assert B.status() == 0
+        B.start(max=20)
+        assert B.status() == 1
+        B.start('Run to 10', max=10)  # Restart
+        for i in range(8):
+            time.sleep(sleep)
+            B.set_progress(i)
+            assert B._progress == i
+        B.increase_progress(1)
+        assert B._progress == i + 1
+        B.finish()  
+        assert B.status() == 2
+        # Without max
+        B.start('Run without max int')
+        for i in range(15):
+            time.sleep(sleep)
+            B.set_progress(i)
+        B.start('Run without float')
+        for i in range(15):
+            time.sleep(sleep)
+            B.set_progress(i+0.1)
+        B.start('Run without progress')
+        for i in range(15):
+            time.sleep(sleep)
+            B.set_progress(0)
+        B.write('some message')
+        B.fail('arg')
+        assert B.status() == 3
+        # Perc
+        B.start('Run percentage', unit='%', max=100)  # Restart
+        for i in range(0, 101, 5):
+            time.sleep(sleep)
+            B.set_progress(i)
+        B.finish('Done')
+        if sleep:
+            return
+
+
+def test_util_image_as_uint8():
+    
+    raises(ValueError, core.image_as_uint8, 4)
+    raises(ValueError, core.image_as_uint8, "not an image")
+    
+    res = core.image_as_uint8(np.array([0, 1], 'uint8'))
+    assert res[0] == 0 and res[1] == 1
+    res = core.image_as_uint8(np.array([4, 255], 'uint8'))
+    assert res[0] == 4 and res[1] == 255
+    
+    res = core.image_as_uint8(np.array([0, 1], 'int8'))
+    assert res[0] == 0 and res[1] == 255
+    res = core.image_as_uint8(np.array([-4, 100], 'int8'))
+    assert res[0] == 0 and res[1] == 255
+    
+    res = core.image_as_uint8(np.array([0, 1], 'int16'))
+    assert res[0] == 0 and res[1] == 255
+    res = core.image_as_uint8(np.array([-4, 100], 'int16'))
+    assert res[0] == 0 and res[1] == 255
+    res = core.image_as_uint8(np.array([-1000, 8000], 'int16'))
+    assert res[0] == 0 and res[1] == 255
+    
+    res = core.image_as_uint8(np.array([0, 1], 'float32'))
+    assert res[0] == 0 and res[1] == 255
+    res = core.image_as_uint8(np.array([0.099, 0.785], 'float32'))
+    assert res[0] == 25 and res[1] == 200
+    res = core.image_as_uint8(np.array([4, 200], 'float32'))
+    assert res[0] == 0 and res[1] == 255
+
+
+def test_functions():
+    """ Test the user-facing API functions """
+    
+    # Test help(), it prints stuff, so we just check whether that goes ok
+    imageio.help()  # should print overview
+    imageio.help('PNG')  # should print about PNG
+    
+    fname1 = get_remote_file('images/chelsea.png', test_dir)
+    fname2 = fname1[:-3] + 'jpg'
+    fname3 = fname1[:-3] + 'notavalidext'
+    open(fname3, 'wb')
+    
+    # Test read()
+    R1 = imageio.read(fname1)
+    R2 = imageio.read(fname1, 'png')
+    assert R1.format is R2.format
+    # Fail
+    raises(ValueError, imageio.read, fname3)  # existing but not readable
+    raises(IOError, imageio.read, 'notexisting.barf')
+    raises(IndexError, imageio.read, fname1, 'notexistingformat')
+    
+    # Test save()
+    W1 = imageio.save(fname2)
+    W2 = imageio.save(fname2, 'JPG')
+    assert W1.format is W2.format
+    # Fail
+    raises(ValueError, imageio.save, 'wtf.notexistingfile')
+    
+    # Test imread()
+    im1 = imageio.imread(fname1)
+    im2 = imageio.imread(fname1, 'png')
+    assert im1.shape[2] == 3
+    assert np.all(im1 == im2)
+    
+    # Test imsave()
+    if os.path.isfile(fname2):
+        os.remove(fname2)
+    assert not os.path.isfile(fname2)
+    imageio.imsave(fname2, im1[:, :, 0])
+    imageio.imsave(fname2, im1)
+    assert os.path.isfile(fname2)
+    
+    # Test mimread()
+    fname3 = get_remote_file('images/newtonscradle.gif', test_dir)
+    ims = imageio.mimread(fname3)
+    assert isinstance(ims, list)
+    assert len(ims) > 1
+    assert ims[0].ndim == 3
+    assert ims[0].shape[2] in (1, 3, 4)
+    # Test protection
+    with raises(RuntimeError):
+        imageio.mimread('chelsea.png', 'dummy', length=np.inf)
+    
+    if IS_PYPY:
+        return  # no support for npz format :(
+    
+    # Test mimsave()
+    fname5 = fname3[:-4] + '2.npz'
+    if os.path.isfile(fname5):
+        os.remove(fname5)
+    assert not os.path.isfile(fname5)
+    imageio.mimsave(fname5, [im[:, :, 0] for im in ims])
+    imageio.mimsave(fname5, ims)
+    assert os.path.isfile(fname5)
+    
+    # Test volread()
+    fname4 = get_remote_file('images/stent.npz', test_dir)
+    vol = imageio.volread(fname4)
+    assert vol.ndim == 3
+    assert vol.shape[0] == 256
+    assert vol.shape[1] == 128
+    assert vol.shape[2] == 128
+    
+    # Test volsave()
+    volc = np.zeros((10, 10, 10, 3), np.uint8)  # color volume
+    fname6 = fname4[:-4] + '2.npz'
+    if os.path.isfile(fname6):
+        os.remove(fname6)
+    assert not os.path.isfile(fname6)
+    imageio.volsave(fname6, volc)
+    imageio.volsave(fname6, vol)
+    assert os.path.isfile(fname6)
+    
+    # Test mvolread()
+    vols = imageio.mvolread(fname4)
+    assert isinstance(vols, list)
+    assert len(vols) == 1
+    assert vols[0].shape == vol.shape
+    
+    # Test mvolsave()
+    if os.path.isfile(fname6):
+        os.remove(fname6)
+    assert not os.path.isfile(fname6)
+    imageio.mvolsave(fname6, [volc, volc])
+    imageio.mvolsave(fname6, vols)
+    assert os.path.isfile(fname6)
+    
+    # Fail for save functions
+    raises(ValueError, imageio.imsave, fname2, np.zeros((100, 100, 5)))
+    raises(ValueError, imageio.imsave, fname2, 42)
+    raises(ValueError, imageio.mimsave, fname5, [np.zeros((100, 100, 5))])
+    raises(ValueError, imageio.mimsave, fname5, [42])
+    raises(ValueError, imageio.volsave, fname4, np.zeros((100, 100, 100, 40)))
+    raises(ValueError, imageio.volsave, fname4, 42)
+    raises(ValueError, imageio.mvolsave, fname4, [np.zeros((90, 90, 90, 40))])
+    raises(ValueError, imageio.mvolsave, fname4, [42])
+
+
+def test_example_plugin():
+    """ Test the example plugin """
+    
+    fname = os.path.join(test_dir, 'out.png')
+    R = imageio.formats['dummy'].get_reader(Request('chelsea.png', 'r?'))
+    W = imageio.formats['dummy'].get_writer(Request(fname, 'w?'))
+    #
+    assert len(R) == 1
+    assert R.get_data(0).ndim
+    raises(IndexError, R.get_data, 1)
+    #raises(RuntimeError, R.get_meta_data)
+    assert R.get_meta_data() == {}
+    R.close()
+    #
+    raises(RuntimeError, W.append_data, np.zeros((10, 10)))
+    raises(RuntimeError, W.set_meta_data, {})
+    W.close()
+
+
+run_tests_if_main()
diff --git a/tests/test_dicom.py b/tests/test_dicom.py
new file mode 100644
index 0000000..5c0b2d5
--- /dev/null
+++ b/tests/test_dicom.py
@@ -0,0 +1,191 @@
+""" Test imageio core functionality.
+"""
+
+import os
+from zipfile import ZipFile
+
+import numpy as np
+
+from pytest import raises
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import get_remote_file
+
+
+test_dir = get_test_dir()
+
+
+_prepared = None
+
+
+def _prepare():
+    """ Create two dirs, one with one dataset and one with two datasets
+    """
+    need_internet()
+    
+    global _prepared
+    if _prepared and os.path.isfile(_prepared[2]):
+        return _prepared
+    # Prepare sources
+    fname1 = get_remote_file('images/dicom_sample1.zip')
+    fname2 = get_remote_file('images/dicom_sample2.zip')
+    dname1 = os.path.join(test_dir, 'dicom_sample1')
+    dname2 = os.path.join(test_dir, 'dicom_sample2')
+    # Extract zipfiles
+    z = ZipFile(fname1)
+    z.extractall(dname1)
+    z.extractall(dname2)
+    z = ZipFile(fname2)
+    z.extractall(dname2)
+    # Get arbitrary file names
+    fname1 = os.path.join(dname1, os.listdir(dname1)[0])
+    fname2 = os.path.join(dname2, os.listdir(dname2)[0])
+    # Cache and return
+    _prepared = dname1, dname2, fname1, fname2
+    return dname1, dname2, fname1, fname2
+
+
+def test_read_empty_dir():
+    # Create an empty dir
+    empty = os.path.join(test_dir, 'empty_dir')
+    if not os.path.isdir(empty):
+        os.mkdir(empty)
+    
+    # Tes that no format is found, but no error is raised
+    request = core.Request(empty, 'ri')
+    assert imageio.formats.search_read_format(request) is None
+    
+
+def test_selection():
+    
+    dname1, dname2, fname1, fname2 = _prepare()
+    
+    # Test that DICOM can examine file
+    F = imageio.formats.search_read_format(core.Request(fname1, 'ri'))
+    assert F.name == 'DICOM'
+    assert F is imageio.formats['DICOM']
+    
+    # Test that we cannot save
+    request = core.Request(os.path.join(test_dir, 'test.dcm'), 'wi')
+    assert not F.can_write(request)
+    
+    # Test fail on wrong file
+    fname2 = fname1 + '.fake'
+    bb = open(fname1, 'rb').read()
+    bb = bb[:128] + b'XXXX' + bb[132:]
+    open(fname2, 'wb').write(bb)
+    raises(Exception, F.get_reader, core.Request(fname2, 'ri'))
+    
+    # Test special files with other formats
+    im = imageio.imread(get_remote_file('images/dicom_file01.dcm'))
+    assert im.shape == (512, 512)
+    im = imageio.imread(get_remote_file('images/dicom_file03.dcm'))
+    assert im.shape == (512, 512)
+    im = imageio.imread(get_remote_file('images/dicom_file04.dcm'))
+    assert im.shape == (512, 512)
+    
+    # Expected fails
+    fname = get_remote_file('images/dicom_file90.dcm')
+    raises(RuntimeError, imageio.imread, fname)  # 1.2.840.10008.1.2.4.91 
+    fname = get_remote_file('images/dicom_file91.dcm')
+    raises(RuntimeError, imageio.imread, fname)  # not pixel data
+    
+    # This one *should* work, but does not, see issue #18
+    try:
+        imageio.imread(get_remote_file('images/dicom_file02.dcm'))
+    except Exception:
+        pass
+
+
+def test_progress():
+    
+    dname1, dname2, fname1, fname2 = _prepare()
+    
+    imageio.imread(fname1, progress=True)
+    imageio.imread(fname1, progress=core.StdoutProgressIndicator('test'))
+    imageio.imread(fname1, progress=None)
+    raises(ValueError, imageio.imread, fname1, progress=3)
+
+
+def test_different_read_modes():
+    
+    dname1, dname2, fname1, fname2 = _prepare()
+    
+    for fname, dname, n in [(fname1, dname1, 1), (fname2, dname2, 2)]:
+        
+        # Test imread()
+        im = imageio.imread(fname)
+        assert isinstance(im, np.ndarray)
+        assert im.shape == (512, 512)
+        
+        # Test mimread()
+        ims = imageio.mimread(fname)
+        assert isinstance(ims, list)
+        assert ims[0].shape == im.shape
+        assert len(ims) > 1
+        #    
+        ims2 = imageio.mimread(dname)
+        assert len(ims) == len(ims2)
+        
+        # Test volread()
+        vol = imageio.volread(dname)
+        assert vol.ndim == 3
+        assert vol.shape[0] > 10
+        assert vol.shape[1:] == (512, 512)
+        #
+        vol2 = imageio.volread(fname)  # fname works as well
+        assert (vol == vol2).all()
+        
+        # Test mvolread()
+        vols = imageio.mvolread(dname)
+        assert isinstance(vols, list)
+        assert len(vols) == n
+        assert vols[0].shape == vol.shape
+        assert sum([v.shape[0] for v in vols]) == len(ims)
+    
+
+def test_different_read_modes_with_readers():
+    
+    dname1, dname2, fname1, fname2 = _prepare()
+    
+    for fname, dname, n in [(fname1, dname1, 1), (fname2, dname2, 2)]:
+        
+        # Test imread()
+        R = imageio.read(fname, 'DICOM', 'i')
+        assert len(R) == 1
+        assert isinstance(R.get_meta_data(), dict)
+        assert isinstance(R.get_meta_data(0), dict)
+        
+        # Test mimread()
+        R = imageio.read(fname, 'DICOM', 'I')
+        if n == 1:
+            assert len(R) > 10
+        else:
+            assert len(R) == 20 + 25
+        assert isinstance(R.get_meta_data(), dict)
+        assert isinstance(R.get_meta_data(0), dict)
+        
+        # Test volread()
+        R = imageio.read(fname, 'DICOM', 'v')
+        assert len(R) == n  # we ask for one, but get an honest number
+        assert isinstance(R.get_meta_data(), dict)
+        assert isinstance(R.get_meta_data(0), dict)
+        
+        # Test mvolread()
+        R = imageio.read(fname, 'DICOM', 'V')
+        assert len(R) == n 
+        assert isinstance(R.get_meta_data(), dict)
+        assert isinstance(R.get_meta_data(0), dict)
+        
+        # Touch DicomSeries objects
+        assert repr(R._series[0])
+        assert R._series[0].description
+        assert len(R._series[0].sampling) == 3
+        
+        R = imageio.read(fname, 'DICOM', '?')
+        raises(RuntimeError, R.get_length)
+
+
+run_tests_if_main()
diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py
new file mode 100644
index 0000000..adc9f17
--- /dev/null
+++ b/tests/test_ffmpeg.py
@@ -0,0 +1,269 @@
+""" Test ffmpeg
+
+"""
+
+from io import BytesIO
+import time
+import threading
+
+import numpy as np
+
+from pytest import raises, skip
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import get_remote_file, IS_PYPY
+
+test_dir = get_test_dir()
+
+
+def test_select():
+    
+    fname1 = get_remote_file('images/cockatoo.mp4', test_dir)
+    
+    F = imageio.formats['ffmpeg']
+    assert F.name == 'FFMPEG'
+    
+    assert F.can_read(core.Request(fname1, 'rI'))
+    assert F.can_write(core.Request(fname1, 'wI'))
+    assert not F.can_read(core.Request(fname1, 'ri'))
+    assert not F.can_read(core.Request(fname1, 'rv'))
+
+    # ffmpeg is default
+    assert imageio.formats['.mp4'] is F
+    assert imageio.formats.search_write_format(core.Request(fname1, 'wI')) is F
+    assert imageio.formats.search_read_format(core.Request(fname1, 'rI')) is F
+
+    
+def test_read_and_write():
+    need_internet()
+    
+    R = imageio.read(get_remote_file('images/cockatoo.mp4'), 'ffmpeg')
+    assert R.format is imageio.formats['ffmpeg']
+    
+    fname1 = get_remote_file('images/cockatoo.mp4', test_dir)
+    fname2 = fname1[:-4] + '.out.mp4'
+    
+    # Read
+    ims1 = []
+    with imageio.read(fname1, 'ffmpeg') as R:
+        for i in range(10):
+            im = R.get_next_data()
+            ims1.append(im)
+            assert im.shape == (720, 1280, 3)
+            assert (im.sum() / im.size) > 0  # pypy mean is broken
+        assert im.sum() > 0
+    
+        # Seek
+        im = R.get_data(120)
+        assert im.shape == (720, 1280, 3)
+    
+    # Save
+    with imageio.save(fname2, 'ffmpeg') as W:
+        for im in ims1:
+            W.append_data(im)
+    
+    # Read the result
+    ims2 = imageio.mimread(fname2, 'ffmpeg')
+    assert len(ims1) == len(ims2)
+    
+    # Check
+    for im1, im2 in zip(ims1, ims2):
+        diff = np.abs(im1.astype(np.float32) - im2.astype(np.float32))
+        if IS_PYPY:
+            assert (diff.sum() / diff.size) < 100
+        else:
+            assert diff.mean() < 2.0
+
+
+def test_reader_more():
+    need_internet()
+    
+    fname1 = get_remote_file('images/cockatoo.mp4', test_dir)
+    fname3 = fname1[:-4] + '.stub.mp4'
+    
+    # Get meta data
+    R = imageio.read(fname1, 'ffmpeg', loop=True)
+    meta = R.get_meta_data()
+    assert len(R) == 280
+    assert isinstance(meta, dict)
+    assert 'fps' in meta
+    R.close()
+    
+    # Test size argument
+    im = imageio.read(fname1, 'ffmpeg', size=(50, 50)).get_data(0)
+    assert im.shape == (50, 50, 3)
+    im = imageio.read(fname1, 'ffmpeg', size='40x40').get_data(0)
+    assert im.shape == (40, 40, 3)
+    raises(ValueError, imageio.read, fname1, 'ffmpeg', size=20)
+    raises(ValueError, imageio.read, fname1, 'ffmpeg', pixelformat=20)
+    
+    # Read all frames and test length
+    R = imageio.read(get_remote_file('images/realshort.mp4'), 'ffmpeg')
+    count = 0
+    while True:
+        try:
+            R.get_next_data()
+        except IndexError:
+            break
+        else:
+            count += 1
+    assert count == len(R)
+    assert count in (35, 36)  # allow one frame off size that we know
+    # Test index error -1
+    raises(IndexError, R.get_data, -1)
+    # Now read beyond (simulate broken file)
+    with raises(RuntimeError):
+        R._read_frame()  # ffmpeg seems to have an extra frame, avbin not?
+        R._read_frame()
+    
+    # Test  loop
+    R = imageio.read(get_remote_file('images/realshort.mp4'), 'ffmpeg', loop=1)
+    im1 = R.get_next_data()
+    for i in range(1, len(R)):
+        R.get_next_data()
+    im2 = R.get_next_data()
+    im3 = R.get_data(0)
+    im4 = R.get_data(2)  # touch skipping frames
+    assert (im1 == im2).all()
+    assert (im1 == im3).all()
+    assert not (im1 == im4).all()
+    R.close()
+    
+    # Read invalid
+    open(fname3, 'wb')
+    raises(IOError, imageio.read, fname3, 'ffmpeg')
+    
+    # Read printing info
+    imageio.read(fname1, 'ffmpeg', print_info=True)
+
+
+def test_writer_more():
+    need_internet()
+    
+    fname1 = get_remote_file('images/cockatoo.mp4', test_dir)
+    fname2 = fname1[:-4] + '.out.mp4'
+    
+    W = imageio.save(fname2, 'ffmpeg')
+    with raises(ValueError):  # Invalid shape
+        W.append_data(np.zeros((20, 20, 5), np.uint8))
+    W.append_data(np.zeros((20, 20, 3), np.uint8))
+    with raises(ValueError):  # Different shape from first image
+        W.append_data(np.zeros((20, 19, 3), np.uint8))
+    with raises(ValueError):  # Different depth from first image
+        W.append_data(np.zeros((20, 20, 4), np.uint8))
+    with raises(RuntimeError):  # No meta data
+        W.set_meta_data({'foo': 3})
+    W.close()
+
+
+def test_cvsecs():
+    
+    cvsecs = imageio.plugins.ffmpeg.cvsecs
+    assert cvsecs(20) == 20
+    assert cvsecs(2, 20) == (2 * 60) + 20
+    assert cvsecs(2, 3, 20) == (2 * 3600) + (3 * 60) + 20
+
+
+def test_limit_lines():
+    limit_lines = imageio.plugins.ffmpeg.limit_lines
+    lines = ['foo'] * 10
+    assert len(limit_lines(lines)) == 10
+    lines = ['foo'] * 50
+    assert len(limit_lines(lines)) == 50  # < 2 * N
+    lines = ['foo'] * 70 + ['bar']
+    lines2 = limit_lines(lines)
+    assert len(lines2) == 33  # > 2 * N
+    assert b'last few lines' in lines2[0]
+    assert 'bar' == lines2[-1]
+
+
+def test_framecatcher():
+    
+    class BlockingBytesIO(BytesIO):
+        def __init__(self):
+            BytesIO.__init__(self)
+            self._lock = threading.RLock()
+        
+        def write_and_rewind(self, bb):
+            with self._lock:
+                t = self.tell()
+                self.write(bb)
+                self.seek(t)
+        
+        def read(self, n):
+            if self.closed:
+                return b''
+            while True:
+                time.sleep(0.001)
+                with self._lock:
+                    bb = BytesIO.read(self, n)
+                if bb:
+                    return bb
+    
+    # Test our class
+    file = BlockingBytesIO()
+    file.write_and_rewind(b'v')
+    assert file.read(100) == b'v'
+    
+    file = BlockingBytesIO()
+    N = 100
+    T = imageio.plugins.ffmpeg.FrameCatcher(file, N)
+    
+    # Init None
+    time.sleep(0.1)
+    assert T._frame is None  # get_frame() would stall
+    
+    # Read frame
+    file.write_and_rewind(b'x' * (N - 20))
+    time.sleep(0.2)  # Let it read a part
+    assert T._frame is None  # get_frame() would stall
+    file.write_and_rewind(b'x' * 20)
+    time.sleep(0.2)  # Let it read the rest
+    assert T.get_frame() == b'x' * N
+    # Read frame when we pass plenty of data
+    file.write_and_rewind(b'y' * N * 3)
+    time.sleep(0.2)
+    assert T.get_frame() == b'y' * N
+    # Close
+    file.close()
+
+
+def test_webcam():
+    need_internet()
+    
+    try:
+        imageio.read('<video2>')
+    except Exception:
+        skip('no web cam')
+
+
+def show_in_console():
+    reader = imageio.read('cockatoo.mp4', 'ffmpeg')
+    #reader = imageio.read('<video0>')
+    im = reader.get_next_data()
+    while True:
+        im = reader.get_next_data()
+        print('frame min/max/mean: %1.1f / %1.1f / %1.1f' % 
+              (im.min(), im.max(), (im.sum() / im.size)))
+
+
+def show_in_visvis():
+    reader = imageio.read('cockatoo.mp4', 'ffmpeg')
+    #reader = imageio.read('<video0>')
+    
+    import visvis as vv
+    im = reader.get_next_data()
+    f = vv.clf()
+    f.title = reader.format.name
+    t = vv.imshow(im, clim=(0, 255))
+    
+    while not f._destroyed:
+        t.SetData(reader.get_next_data())
+        vv.processEvents()
+
+
+if __name__ == '__main__':
+    run_tests_if_main()
+    #reader = imageio.read('cockatoo.mp4', 'ffmpeg')
diff --git a/tests/test_freeimage.py b/tests/test_freeimage.py
new file mode 100644
index 0000000..304b689
--- /dev/null
+++ b/tests/test_freeimage.py
@@ -0,0 +1,466 @@
+""" Tests for imageio's freeimage plugin
+"""
+
+import os
+import sys
+
+import numpy as np
+
+from pytest import raises, skip
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import get_remote_file, IS_PYPY
+
+test_dir = get_test_dir()
+
+
+# Create test images LUMINANCE
+im0 = np.zeros((42, 32), np.uint8)
+im0[:16, :] = 200
+im1 = np.zeros((42, 32, 1), np.uint8)
+im1[:16, :] = 200
+# Create test image RGB
+im3 = np.zeros((42, 32, 3), np.uint8)
+im3[:16, :, 0] = 250 
+im3[:, :16, 1] = 200
+im3[50:, :16, 2] = 100
+# Create test image RGBA
+im4 = np.zeros((42, 32, 4), np.uint8)
+im4[:16, :, 0] = 250
+im4[:, :16, 1] = 200
+im4[50:, :16, 2] = 100
+im4[:, :, 3] = 255
+im4[20:, :, 3] = 120
+
+fnamebase = os.path.join(test_dir, 'test')
+
+
+def get_ref_im(colors, crop, float):
+    """ Get reference image with
+    * colors: 0, 1, 3, 4
+    * cropping: 0-> none, 1-> crop, 2-> crop with non-contiguous data
+    * float: False, True
+    """
+    assert colors in (0, 1, 3, 4)
+    assert crop in (0, 1, 2)
+    assert float in (False, True)
+    rim = [im0, im1, None, im3, im4][colors]
+    if float:
+        rim = rim.astype(np.float32) / 255.0
+    if crop == 1:
+        rim = rim[:-1, :-1].copy()
+    elif crop == 2:
+        rim = rim[:-1, :-1]
+    return rim
+
+
+def assert_close(im1, im2, tol=0.0):
+    if im1.ndim == 3 and im1.shape[-1] == 1:
+        im1 = im1.reshape(im1.shape[:-1])
+    if im2.ndim == 3 and im2.shape[-1] == 1:
+        im2 = im2.reshape(im2.shape[:-1])
+    assert im1.shape == im2.shape
+    diff = im1.astype('float32') - im2.astype('float32')
+    diff[15:17, :] = 0  # Mask edge artifacts
+    diff[:, 15:17] = 0
+    assert np.abs(diff).max() <= tol
+    # import visvis as vv
+    # vv.subplot(121); vv.imshow(im1); vv.subplot(122); vv.imshow(im2)
+
+
+def test_get_ref_im():
+    """ A test for our function to get test images """
+    
+    crop = 0
+    for f in (False, True):
+        for colors in (0, 1, 3, 4):
+            rim = get_ref_im(0, crop, f)
+            assert rim.flags.c_contiguous is True
+            assert rim.shape[:2] == (42, 32)
+    
+    crop = 1
+    for f in (False, True):
+        for colors in (0, 1, 3, 4):
+            rim = get_ref_im(0, crop, f)
+            assert rim.flags.c_contiguous is True
+            assert rim.shape[:2] == (41, 31)
+    
+    if IS_PYPY:
+        return 'PYPY cannot have non-contiguous data'
+    
+    crop = 2
+    for f in (False, True):
+        for colors in (0, 1, 3, 4):
+            rim = get_ref_im(0, crop, f)
+            assert rim.flags.c_contiguous is False
+            assert rim.shape[:2] == (41, 31)
+    
+
+def test_freeimage_format():
+    
+    # Format
+    F = imageio.formats['PNG']
+    
+    # Reader
+    R = F.get_reader(core.Request('chelsea.png', 'ri'))
+    assert len(R) == 1
+    assert isinstance(R.get_meta_data(), dict)
+    assert isinstance(R.get_meta_data(0), dict)
+    raises(IndexError, R.get_data, 2)
+    raises(IndexError, R.get_meta_data, 2)
+    
+    # Writer
+    W = F.get_writer(core.Request(fnamebase + '.png', 'wi'))
+    W.append_data(im0)
+    W.set_meta_data({'foo': 3})
+    raises(RuntimeError, W.append_data, im0)
+
+
+def test_freeimage_lib():
+    
+    fi = imageio.plugins.freeimage.fi
+    
+    # Error messages
+    imageio.plugins._freeimage.fi._messages.append('this is a test')
+    assert imageio.plugins._freeimage.fi.get_output_log()
+    imageio.plugins._freeimage.fi._show_any_warnings()
+    imageio.plugins._freeimage.fi._get_error_message()
+    
+    # Test getfif
+    raises(ValueError, fi.getFIF, 'foo.png', 'x')  # mode must be r or w
+    raises(ValueError, fi.getFIF, 'foo.notvalid', 'w')  # invalid ext
+    raises(ValueError, fi.getFIF, 'foo.iff', 'w')  # We cannot write iff
+
+    
+def test_png():
+    
+    for float in (False, True):
+        for crop in (0, 1, 2):
+            for colors in (0, 1, 3, 4):
+                fname = fnamebase + '%i.%i.%i.png' % (float, crop, colors)
+                rim = get_ref_im(colors, crop, float)
+                imageio.imsave(fname, rim)
+                im = imageio.imread(fname)
+                mul = 255 if float else 1
+                assert_close(rim * mul, im, 0.1)  # lossless
+    
+    # Run exact same test, but now in pypy backup mode
+    try:
+        imageio.plugins._freeimage.TEST_NUMPY_NO_STRIDES = True
+        for float in (False, True):
+            for crop in (0, 1, 2):
+                for colors in (0, 1, 3, 4):
+                    fname = fnamebase + '%i.%i.%i.png' % (float, crop, colors)
+                    rim = get_ref_im(colors, crop, float)
+                    imageio.imsave(fname, rim)
+                    im = imageio.imread(fname)
+                    mul = 255 if float else 1
+                    assert_close(rim * mul, im, 0.1)  # lossless
+    finally:
+        imageio.plugins._freeimage.TEST_NUMPY_NO_STRIDES = False
+    
+    # Parameters
+    im = imageio.imread('chelsea.png', ignoregamma=True)
+    imageio.imsave(fnamebase + '.png', im, interlaced=True)
+    
+    # Parameter fail
+    raises(TypeError, imageio.imread, 'chelsea.png', notavalidkwarg=True)
+    raises(TypeError, imageio.imsave, fnamebase + '.png', im, notavalidk=True)
+    
+    # Compression
+    imageio.imsave(fnamebase + '1.png', im, compression=0)
+    imageio.imsave(fnamebase + '2.png', im, compression=9)
+    s1 = os.stat(fnamebase + '1.png').st_size
+    s2 = os.stat(fnamebase + '2.png').st_size
+    assert s2 < s1
+    # Fail
+    raises(ValueError, imageio.imsave, fnamebase + '.png', im, compression=12)
+    
+    # Quantize
+    if sys.platform.startswith('darwin'):
+        return  # quantization segfaults on my osx VM
+    imageio.imsave(fnamebase + '1.png', im, quantize=256)
+    imageio.imsave(fnamebase + '2.png', im, quantize=4)
+    
+    im = imageio.imread(fnamebase + '2.png')  # touch palette read code
+    s1 = os.stat(fnamebase + '1.png').st_size
+    s2 = os.stat(fnamebase + '2.png').st_size
+    assert s1 > s2
+    # Fail
+    fname = fnamebase + '1.png'
+    raises(ValueError, imageio.imsave, fname, im[:, :, :3], quantize=300)
+    raises(ValueError, imageio.imsave, fname, im[:, :, 0], quantize=100)
+
+
+def test_png_dtypes():
+    # See issue #44
+    
+    # Two images, one 0-255, one 0-200
+    im1 = np.zeros((100, 100, 3), dtype='uint8')
+    im2 = np.zeros((100, 100, 3), dtype='uint8')
+    im1[20:80, 20:80, :] = 255
+    im2[20:80, 20:80, :] = 200
+    
+    fname = fnamebase + '.dtype.png'
+    
+    # uint8
+    imageio.imsave(fname, im1)
+    assert_close(im1, imageio.imread(fname))
+    imageio.imsave(fname, im2)
+    assert_close(im2, imageio.imread(fname))
+    
+    # float scaled
+    imageio.imsave(fname, im1 / 255.0)
+    assert_close(im1, imageio.imread(fname))
+    imageio.imsave(fname, im2 / 255.0)
+    assert_close(im2, imageio.imread(fname))
+    
+    # float not scaled
+    imageio.imsave(fname, im1 * 1.0)
+    assert_close(im1, imageio.imread(fname))
+    imageio.imsave(fname, im2 * 1.0)
+    assert_close(im1, imageio.imread(fname))  # scaled
+    
+    # int16
+    imageio.imsave(fname, im1.astype('int16'))
+    assert_close(im1, imageio.imread(fname))
+    imageio.imsave(fname, im2.astype('int16'))
+    assert_close(im1, imageio.imread(fname))  # scaled
+
+
+def test_jpg():
+    
+    for float in (False, True):
+        for crop in (0, 1, 2):
+            for colors in (0, 1, 3):
+                fname = fnamebase + '%i.%i.%i.jpg' % (float, crop, colors)
+                rim = get_ref_im(colors, crop, float)
+                imageio.imsave(fname, rim)
+                im = imageio.imread(fname)
+                mul = 255 if float else 1
+                assert_close(rim * mul, im, 1.1)  # lossy
+    
+    # No alpha in JPEG
+    raises(Exception, imageio.imsave, fname, im4)
+    
+    # Parameters
+    imageio.imsave(fnamebase + '.jpg', im3, progressive=True, optimize=True, 
+                   baseline=True)
+    
+    # Parameter fail
+    raises(TypeError, imageio.imread, fnamebase + '.jpg', notavalidkwarg=True)
+    raises(TypeError, imageio.imsave, fnamebase + '.jpg', im, notavalidk=True)
+    
+    # Compression
+    imageio.imsave(fnamebase + '1.jpg', im3, quality=10)
+    imageio.imsave(fnamebase + '2.jpg', im3, quality=90)
+    s1 = os.stat(fnamebase + '1.jpg').st_size
+    s2 = os.stat(fnamebase + '2.jpg').st_size
+    assert s2 > s1 
+    raises(ValueError, imageio.imsave, fnamebase + '.jpg', im, quality=120)
+
+
+def test_jpg_more():
+    need_internet()
+    
+    # Test broken JPEG
+    fname = fnamebase + '_broken.jpg'
+    open(fname, 'wb').write(b'this is not an image')
+    raises(Exception, imageio.imread, fname)
+    #
+    bb = imageio.imsave(imageio.RETURN_BYTES, get_ref_im(3, 0, 0), 'JPEG')
+    with open(fname, 'wb') as f:
+        f.write(bb[:400])
+        f.write(b' ')
+        f.write(bb[400:])
+    raises(Exception, imageio.imread, fname)
+    
+    # Test EXIF stuff
+    fname = get_remote_file('images/rommel.jpg')
+    im = imageio.imread(fname)
+    assert im.shape[0] > im.shape[1]
+    im = imageio.imread(fname, exifrotate=False)
+    assert im.shape[0] < im.shape[1]
+    im = imageio.imread(fname, exifrotate=2)  # Rotation in Python
+    assert im.shape[0] > im.shape[1]
+    # Write the jpg and check that exif data is maintained
+    if sys.platform.startswith('darwin'):
+        return  # segfaults on my osx VM, why?
+    imageio.imsave(fnamebase + 'rommel.jpg', im)
+    im = imageio.imread(fname)
+    assert im.meta.EXIF_MAIN
+
+
+def test_bmp():
+    
+    for float in (False, True):
+        for crop in (0, 1, 2):
+            for colors in (0, 1, 3, 4):
+                fname = fnamebase + '%i.%i.%i.bmp' % (float, crop, colors)
+                rim = get_ref_im(colors, crop, float)
+                imageio.imsave(fname, rim)
+                im = imageio.imread(fname)
+                mul = 255 if float else 1
+                assert_close(rim * mul, im, 0.1)  # lossless
+    
+    # Compression
+    imageio.imsave(fnamebase + '1.bmp', im3, compression=False)
+    imageio.imsave(fnamebase + '2.bmp', im3, compression=True)
+    s1 = os.stat(fnamebase + '1.bmp').st_size
+    s2 = os.stat(fnamebase + '2.bmp').st_size
+    assert s1 + s2  # todo: bug in FreeImage? assert s1 < s2
+    
+    # Parameter fail
+    raises(TypeError, imageio.imread, fnamebase + '1.bmp', notavalidkwarg=True)
+    raises(TypeError, imageio.imsave, fnamebase + '1.bmp', im, notavalidk=True)
+
+
+def test_gif():
+    # The not-animated gif
+    
+    for float in (False, True):
+        for crop in (0, 1, 2):
+            for colors in (0, 3, 4):
+                if colors > 1 and sys.platform.startswith('darwin'):
+                    continue  # quantize fails, see also png
+                fname = fnamebase + '%i.%i.%i.gif' % (float, crop, colors)
+                rim = get_ref_im(colors, crop, float)
+                imageio.imsave(fname, rim)
+                im = imageio.imread(fname)
+                mul = 255 if float else 1
+                if colors in (0, 1):
+                    im = im[:, :, 0]
+                else:
+                    im = im[:, :, :3]
+                    rim = rim[:, :, :3]
+                assert_close(rim * mul, im, 1.1)  # lossless
+    
+    # Parameter fail
+    raises(TypeError, imageio.imread, fname, notavalidkwarg=True)
+    raises(TypeError, imageio.imsave, fnamebase + '1.gif', im, notavalidk=True)
+
+
+def test_animated_gif():
+    
+    if sys.platform.startswith('darwin'):
+        skip('On OSX quantization of freeimage is unstable')
+    
+    # Get images
+    im = get_ref_im(4, 0, 0)
+    ims = []
+    for i in range(10):
+        im = im.copy()
+        im[:, -5:, 0] = i * 20
+        ims.append(im)
+    
+    # Store - animated GIF always poops out RGB
+    for float in (False, True):
+        for colors in (3, 4):
+            ims1 = ims[:]
+            if float:
+                ims1 = [x.astype(np.float32) / 256 for x in ims1]
+            ims1 = [x[:, :, :colors] for x in ims1]
+            fname = fnamebase + '.animated.%i.gif' % colors
+            imageio.mimsave(fname, ims1, duration=0.2)
+            # Retrieve
+            ims2 = imageio.mimread(fname)
+            ims1 = [x[:, :, :3] for x in ims]  # fresh ref
+            ims2 = [x[:, :, :3] for x in ims2]  # discart alpha
+            for im1, im2 in zip(ims1, ims2):
+                assert_close(im1, im2, 1.1)
+    
+    # We can also store grayscale
+    fname = fnamebase + '.animated.%i.gif' % 1
+    imageio.mimsave(fname, [x[:, :, 0] for x in ims], duration=0.2)
+    imageio.mimsave(fname, [x[:, :, :1] for x in ims], duration=0.2)
+    
+    # Irragular duration. You probably want to check this manually (I did)
+    duration = [0.1 for i in ims]
+    for i in [2, 5, 7]:
+        duration[i] = 0.5
+    imageio.mimsave(fnamebase + '.animated_irr.gif', ims, duration=duration)
+    
+    # Other parameters
+    imageio.mimsave(fnamebase + '.animated.loop2.gif', ims, loop=2, fps=20)
+    R = imageio.read(fnamebase + '.animated.loop2.gif')
+    W = imageio.save(fnamebase + '.animated.palettes100.gif', palettesize=100)
+    assert W._palettesize == 128
+    # Fail
+    raises(IndexError, R.get_meta_data, -1)
+    raises(ValueError, imageio.mimsave, fname, ims, palettesize=300)
+    raises(ValueError, imageio.mimsave, fname, ims, quantizer='foo')
+    raises(ValueError, imageio.mimsave, fname, ims, duration='foo')
+    
+    # Add one duplicate image to ims to touch subractangle with not change
+    ims.append(ims[-1])
+    
+    # Test subrectangles
+    imageio.mimsave(fnamebase + '.subno.gif', ims, subrectangles=False)
+    imageio.mimsave(fnamebase + '.subyes.gif', ims, subrectangles=True)
+    s1 = os.stat(fnamebase + '.subno.gif').st_size
+    s2 = os.stat(fnamebase + '.subyes.gif').st_size
+    assert s2 < s1
+    
+    # Meta (dummy, because always {}
+    assert isinstance(imageio.read(fname).get_meta_data(), dict)
+
+
+def test_ico():
+    
+    for float in (False, True):
+        for crop in (0, ):
+            for colors in (1, 3, 4):
+                fname = fnamebase + '%i.%i.%i.ico' % (float, crop, colors)
+                rim = get_ref_im(colors, crop, float)
+                rim = rim[:32, :32]  # ico needs nice size
+                imageio.imsave(fname, rim)
+                im = imageio.imread(fname)
+                mul = 255 if float else 1
+                assert_close(rim * mul, im, 0.1)  # lossless
+    
+    # Meta data
+    R = imageio.read(fnamebase + '0.0.1.ico')
+    assert isinstance(R.get_meta_data(0), dict)
+    assert isinstance(R.get_meta_data(None), dict)  # But this print warning
+    R.close()
+    writer = imageio.save(fnamebase + 'I.ico')
+    writer.set_meta_data({})
+    writer.close()
+    
+    # Parameters. Note that with makealpha, RGBA images are read in incorrectly
+    im = imageio.imread(fnamebase + '0.0.1.ico', makealpha=True)
+    assert im.ndim == 3 and im.shape[-1] == 4
+    
+    # Parameter fail
+    raises(TypeError, imageio.imread, fname, notavalidkwarg=True)
+    raises(TypeError, imageio.imsave, fnamebase + '1.gif', im, notavalidk=True)
+
+    if sys.platform.startswith('win'):  # issue #21
+        skip('Windows has a known issue with multi-icon files')
+    
+    # Multiple images
+    im = get_ref_im(4, 0, 0)[:32, :32]
+    ims = [np.repeat(np.repeat(im, i, 1), i, 0) for i in (1, 2)]  # SegF on win
+    ims = im, np.column_stack((im, im)), np.row_stack((im, im))  # error on win
+    imageio.mimsave(fnamebase + 'I2.ico', ims)
+    ims2 = imageio.mimread(fnamebase + 'I2.ico')
+    for im1, im2 in zip(ims, ims2):
+        assert_close(im1, im2, 0.1)
+
+
+def test_mng():
+    pass  # MNG seems broken in FreeImage
+    #ims = imageio.imread(get_remote_file('images/mngexample.mng'))
+
+
+def test_other():
+    
+    # Cannot save float
+    im = get_ref_im(3, 0, 1)
+    raises(Exception, imageio.imsave, fnamebase + '.jng', im, 'JNG')
+
+if __name__ == '__main__':
+    #test_animated_gif()
+    run_tests_if_main()
diff --git a/tests/test_freeimage_suite.py b/tests/test_freeimage_suite.py
new file mode 100644
index 0000000..01471a8
--- /dev/null
+++ b/tests/test_freeimage_suite.py
@@ -0,0 +1,95 @@
+""" The test suite of freeimage plugin
+"""
+
+import os
+import sys
+import zipfile
+import shutil
+
+from pytest import raises  # noqa
+from imageio.testing import get_test_dir
+
+import imageio
+from imageio.core import get_remote_file, IS_PYPY, urlopen  # noqa
+
+test_dir = get_test_dir()
+
+
+# Url to download images from
+ulr = ("http://sourceforge.net/projects/freeimage/files/"
+       "Test%20Suite%20%28graphics%29/2.5.0/")
+
+# Names of zipfiles to download
+names = ['png', 'jpeg', 'bmp', 'ico',  'tiff', 'targa', 'koa', 'mng', 'iff', 
+         'psd', 'ppm', 'pcx'] 
+
+# Define what formats are not writable, we will convert these to png
+NOT_WRITABLE = ['.pgm', '.koa', '.pcx', '.mng', '.iff', '.psd', '.lbm']
+
+# Define files that fail (i.e. crash)
+FAILS = []
+if sys.platform.startswith('linux'):
+    FAILS.extend(['quad-jpeg.tif', 'test1g.tif', 'ycbcr-cat.tif'])
+
+THISDIR = os.path.dirname(os.path.abspath(__file__))
+TESTDIR = os.path.join(THISDIR, 'temp')
+ZIPDIR = os.path.join(THISDIR, 'zipped')
+
+
+def run_feeimage_test_suite():
+    """ Run freeimage test suite.
+    Lots of images. Berrer done locally and then checking the result.
+    Not so much suited for CI, I think.
+    """
+    
+    if not os.path.isdir(TESTDIR):
+        os.mkdir(TESTDIR)
+    if not os.path.isdir(ZIPDIR):
+        os.mkdir(ZIPDIR)
+    
+    for name in names:
+        fname = os.path.join(ZIPDIR, name+'.zip')
+        # Make sure that the file is there
+        if not os.path.isfile(fname):
+            print('Downloading %s.zip' % name)
+            f1 = urlopen(ulr+name+'.zip')
+            f2 = open(fname, 'wb')
+            shutil.copyfileobj(f1, f2)
+            f1.close()
+            f2.close()
+        
+        # Check contents
+        zf = zipfile.ZipFile(fname, 'r')
+        subnames = zf.namelist()
+        zf.extractall(TESTDIR)
+        zf.close()
+        
+        # Read and write each one
+        for subname in subnames:
+            if subname in FAILS:
+                continue
+            fname_zip = fname+'/%s' % subname
+            subname_, ext = os.path.splitext(subname)
+            fname_dst1 = os.path.join(TESTDIR, subname+'_1'+ext)
+            fname_dst2 = os.path.join(TESTDIR, subname+'_2'+ext)
+            if os.path.splitext(subname)[1].lower() in NOT_WRITABLE:
+                fname_dst1 += '.png'
+                fname_dst2 += '.png'
+            print('Reading+saving %s' % subname)
+            try:
+                # Read from zip, save to file
+                im = imageio.imread(fname_zip)
+                imageio.imsave(fname_dst1, im)
+                # Read from file, save to file
+                im = imageio.imread(fname_dst1)
+                imageio.imsave(fname_dst2, im)
+            except Exception:
+                e_type, e_value, e_tb = sys.exc_info()
+                del e_tb
+                err = str(e_value)
+                print('woops! ' + fname_zip)
+                print('  ' + err)
+
+
+if __name__ == '__main__':
+    run_feeimage_test_suite()
diff --git a/tests/test_meta.py b/tests/test_meta.py
new file mode 100644
index 0000000..d8bcf21
--- /dev/null
+++ b/tests/test_meta.py
@@ -0,0 +1,147 @@
+""" Test imageio meta stuff, like namespaces and spuriuos imports
+"""
+
+from __future__ import print_function
+
+import os
+import sys
+import subprocess
+
+from pytest import raises  # noqa
+from imageio.testing import run_tests_if_main
+
+import imageio
+
+
+def run_subprocess(command, return_code=False, **kwargs):
+    """ Run command in subprocess and return stdout and stderr.
+    Raise CalledProcessError if the process returned non-zero.
+    """
+    use_kwargs = dict(stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+    use_kwargs.update(kwargs)
+    
+    p = subprocess.Popen(command, **use_kwargs)
+    stdout, stderr = p.communicate()
+    output = (stdout or b'').decode('utf-8'), (stderr or b'').decode('utf-8')
+    if p.returncode:
+        print(output[0], output[1])
+        raise subprocess.CalledProcessError(p.returncode, command, output)
+    return output
+
+
+def loaded_modules(import_module, depth=None, all_modules=False):
+    """ Import the given module in subprocess and return set of loaded modules
+
+    Import a certain module in a clean subprocess and return the
+    modules that are subsequently loaded. The given depth indicates the
+    module level (i.e. depth=1 will only yield 'X.Y' but not 'X.Y.Z').
+    """
+
+    imageio_dir = os.path.dirname(os.path.dirname(imageio.__file__))
+
+    # Get the loaded modules in a clean interpreter
+    code = "import sys, %s; print(', '.join(sys.modules))" % import_module
+    res = run_subprocess([sys.executable, '-c', code], cwd=imageio_dir)[0]
+    loaded_modules = [name.strip() for name in res.split(',')]
+    
+    # Filter by depth
+    filtered_modules = set()
+    if depth is None:
+        filtered_modules = set(loaded_modules)
+    else:
+        for m in loaded_modules:
+            parts = m.split('.')
+            m = '.'.join(parts[:depth])
+            filtered_modules.add(m)
+    
+    # Filter by imageio (or not)
+    if all_modules:
+        return filtered_modules
+    else:
+        imageio_modules = set()
+        for m in filtered_modules:
+            if m.startswith('imageio') and '__future__' not in m:
+                imageio_modules.add(m)
+        return imageio_modules
+
+
+def test_namespace():
+    """ Test that all names from the public API are in the main namespace """
+    
+    has_names = dir(imageio)
+    has_names = set([n for n in has_names if not n.startswith('_')])
+    
+    need_names = ('help formats read save RETURN_BYTES '
+                  'get_reader imread mimread volread mvolread '
+                  'get_writer imwrite mimwrite volwrite mvolwrite '
+                  'read save imsave mimsave volsave mvolsave '  # aliases
+                  ).split(' ')
+    need_names = set([n for n in need_names if n])
+    
+    # Check that all names are there
+    assert need_names.issubset(has_names)
+    
+    # Check that there are no extra names
+    extra_names = has_names.difference(need_names)
+    extra_names.discard('testing')  # can be there during testing
+    assert extra_names == set(['core', 'plugins'])
+
+
+def test_import_nothing():
+    """ Not importing imageio should not import any imageio modules. """
+    modnames = loaded_modules('os', 2)
+    assert modnames == set()
+
+
+def test_import_modules():
+    """ Test that importing imageio does not import modules that should
+    not be imported.
+    """
+    modnames = loaded_modules('imageio', 3)
+    
+    # Test if everything seems to be there
+    assert 'imageio.core' in modnames
+    assert 'imageio.plugins' in modnames
+    assert 'imageio.plugins.ffmpeg' in modnames
+    assert 'imageio.plugins.dicom' in modnames
+    
+    # Test that modules that should not be imported are indeed not imported
+    assert 'imageio.freeze' not in modnames
+    assert 'imageio.testing' not in modnames
+
+
+def test_import_dependencies():
+    """ Test that importing imageio is not dragging in anything other
+    than the known dependencies.
+    """
+    
+    # Get loaded modules when numpy is imported and when imageio is imported
+    modnames_ref = loaded_modules('numpy', 1, True)
+    modnames_new = loaded_modules('imageio', 1, True)
+    
+    # Get the difference; what do we import extra?
+    extra_modules = modnames_new.difference(modnames_ref)
+    
+    known_modules = ['zipfile', 'importlib']  # discard these
+    
+    # Remove modules in standard library
+    stdloc = os.path.dirname(os.__file__)
+    print('os', stdloc)
+    for modname in list(extra_modules):
+        mod = sys.modules[modname]
+        if modname.startswith('_') or modname in known_modules:
+            extra_modules.discard(modname)
+        elif not hasattr(mod, '__file__'):
+            extra_modules.discard(modname)  # buildin module
+        elif os.path.splitext(mod.__file__)[1] in ('.so', '.dylib', '.pyd'):
+            extra_modules.discard(modname)  # buildin module
+        elif os.path.dirname(mod.__file__) == stdloc:
+            extra_modules.discard(modname)
+        else:
+            print(modname, mod.__file__)
+    
+    # Check that only imageio is left
+    assert extra_modules == set(['imageio'])
+
+
+run_tests_if_main()
diff --git a/tests/test_npz.py b/tests/test_npz.py
new file mode 100644
index 0000000..51c5611
--- /dev/null
+++ b/tests/test_npz.py
@@ -0,0 +1,85 @@
+""" Test npz plugin functionality.
+"""
+
+import os
+
+import numpy as np
+
+from pytest import raises
+from imageio.testing import run_tests_if_main, get_test_dir
+
+import imageio
+from imageio.core import get_remote_file, Request, IS_PYPY
+
+test_dir = get_test_dir()
+
+
+def test_npz_format():
+    
+    # Test selection
+    for name in ['npz', '.npz']:
+        format = imageio.formats['npz']
+        assert format.name == 'NPZ'
+        assert format.__module__.endswith('.npz')
+    
+    # Test cannot read
+    png = get_remote_file('images/chelsea.png')
+    assert not format.can_read(Request(png, 'ri'))
+    assert not format.can_write(Request(png, 'wi'))
+
+        
+def test_npz_reading_writing():
+    """ Test reading and saveing npz """
+    
+    if IS_PYPY:
+        return  # no support for npz format :(
+    
+    im2 = np.ones((10, 10), np.uint8) * 2
+    im3 = np.ones((10, 10, 10), np.uint8) * 3
+    im4 = np.ones((10, 10, 10, 10), np.uint8) * 4
+    
+    filename1 = os.path.join(test_dir, 'test_npz.npz')
+
+    # One image
+    imageio.imsave(filename1, im2)
+    im = imageio.imread(filename1)
+    ims = imageio.mimread(filename1)
+    assert (im == im2).all()
+    assert len(ims) == 1
+    
+    # Multiple images
+    imageio.mimsave(filename1, [im2, im2, im2])
+    im = imageio.imread(filename1)
+    ims = imageio.mimread(filename1)
+    assert (im == im2).all()
+    assert len(ims) == 3
+    
+    # Volumes
+    imageio.mvolsave(filename1, [im3, im3])
+    im = imageio.volread(filename1)
+    ims = imageio.mvolread(filename1)
+    assert (im == im3).all()
+    assert len(ims) == 2
+    
+    # Mixed
+    W = imageio.save(filename1)
+    assert W.format.name == 'NPZ'
+    W.append_data(im2)
+    W.append_data(im3)
+    W.append_data(im4)
+    raises(RuntimeError, W.set_meta_data, {})  # no meta data support
+    W.close()
+    #
+    R = imageio.read(filename1)
+    assert R.format.name == 'NPZ'
+    ims = list(R)  # == [im for im in R]
+    assert (ims[0] == im2).all()
+    assert (ims[1] == im3).all()
+    assert (ims[2] == im4).all()
+    # Fail
+    raises(IndexError, R.get_data, -1)
+    raises(IndexError, R.get_data, 3)
+    raises(RuntimeError, R.get_meta_data, None)  # no meta data support
+    raises(RuntimeError, R.get_meta_data, 0)  # no meta data support
+
+run_tests_if_main()
diff --git a/tests/test_swf.py b/tests/test_swf.py
new file mode 100644
index 0000000..1f8fc26
--- /dev/null
+++ b/tests/test_swf.py
@@ -0,0 +1,181 @@
+""" Tests for the shockwave flash plugin
+"""
+
+import os
+
+import numpy as np
+
+from pytest import raises
+from imageio.testing import run_tests_if_main, get_test_dir, need_internet
+
+import imageio
+from imageio import core
+from imageio.core import get_remote_file
+
+
+test_dir = get_test_dir()
+
+mean = lambda x: x.sum() / x.size  # pypy-compat mean
+
+
+# We don't shipt the swf: its rather big and a rather specific format
+need_internet()
+
+
+def test_format_selection():
+    
+    fname1 = get_remote_file('images/stent.swf', test_dir)
+    fname2 = fname1[:-4] + '.out.swf'
+    
+    F = imageio.formats['swf']
+    assert F.name == 'SWF'
+    assert imageio.formats['.swf'] is F
+    
+    assert imageio.read(fname1).format is F
+    assert imageio.save(fname2).format is F
+
+
+def test_reading_saving():
+    
+    fname1 = get_remote_file('images/stent.swf', test_dir)
+    fname2 = fname1[:-4] + '.out.swf'
+    fname3 = fname1[:-4] + '.compressed.swf'
+    fname4 = fname1[:-4] + '.out2.swf'
+    
+    # Read
+    R = imageio.read(fname1)
+    assert len(R) == 10
+    assert R.get_meta_data() == {}  # always empty dict
+    ims1 = []
+    for im in R:
+        assert im.shape == (657, 451, 4)
+        assert mean(im) > 0
+        ims1.append(im)
+    # Seek
+    assert (R.get_data(3) == ims1[3]).all()
+    # Fails
+    raises(IndexError, R.get_data, -1)  # No negative index
+    raises(IndexError, R.get_data, 10)  # Out of bounds
+    R.close()
+    
+    # Test loop
+    R = imageio.read(fname1, loop=True)
+    assert (R.get_data(10) == ims1[0]).all()
+    
+    # setting meta data is ignored
+    W = imageio.save(fname2)
+    W.set_meta_data({'foo': 3})
+    W.close()
+    
+    # Write and re-read, now without loop, and with html page
+    imageio.mimsave(fname2, ims1, loop=False, html=True)
+    ims2 = imageio.mimread(fname2)
+    
+    # Check images. We can expect exact match, since
+    # SWF is lossless.
+    assert len(ims1) == len(ims2)
+    for im1, im2 in zip(ims1, ims2):
+        assert (im1 == im2).all()
+
+    # Test compressed
+    imageio.mimsave(fname3, ims2, compress=True)
+    ims3 = imageio.mimread(fname3)
+    assert len(ims1) == len(ims3)
+    for im1, im3 in zip(ims1, ims3):
+        assert (im1 == im3).all()
+    
+    # Test conventional, Bonus, we don't officially support this.
+    imageio.plugins._swf.write_swf(fname4, ims1)
+    ims4 = imageio.plugins._swf.read_swf(fname4)
+    assert len(ims1) == len(ims4)
+    for im1, im4 in zip(ims1, ims4):
+        assert (im1 == im4).all()
+    
+    # We want to manually validate that this file plays in 3d party tools
+    # So we write a small HTML5 doc that we can load
+    html = """<!DOCTYPE html>
+            <html>
+            <body>
+            
+            Original:
+            <embed src="%s">
+            <br ><br >
+            Written:
+            <embed src="%s">
+            <br ><br >
+            Compressed:
+            <embed src="%s">
+            <br ><br >
+            Written 2:
+            <embed src="%s">
+            </body>
+            </html>
+            """ % (fname1, fname2, fname3, fname4)
+    
+    with open(os.path.join(test_dir, 'test_swf.html'), 'wb') as f:
+        for line in html.splitlines():
+            f.write(line.strip().encode('utf-8') + b'\n')
+
+
+def test_read_from_url():
+    burl = 'https://raw.githubusercontent.com/imageio/imageio-binaries/master/'
+    url = burl + 'images/stent.swf'
+    
+    ims = imageio.mimread(url)
+    assert len(ims) == 10
+
+
+def test_invalid():
+    fname1 = get_remote_file('images/stent.swf', test_dir)
+    fname2 = fname1[:-4] + '.invalid.swf'
+    
+    # Empty file
+    with open(fname2, 'wb'):
+        pass
+    assert not imageio.formats.search_read_format(core.Request(fname2, 'rI'))
+    raises(IOError, imageio.mimread, fname2, 'swf')
+    
+    # File with BS data
+    with open(fname2, 'wb') as f:
+        f.write(b'x'*100)
+    assert not imageio.formats.search_read_format(core.Request(fname2, 'rI'))
+    raises(IOError, imageio.mimread, fname2, 'swf')
+    
+
+def test_lowlevel():
+    # Some tests from low level implementation that is not covered
+    # by using the plugin itself.
+    
+    tag = imageio.plugins._swf.Tag()
+    raises(NotImplementedError, tag.process_tag)
+    assert tag.make_matrix_record() == '00000000'
+    assert tag.make_matrix_record(scale_xy=(1, 1))
+    assert tag.make_matrix_record(rot_xy=(1, 1))
+    assert tag.make_matrix_record(trans_xy=(1, 1))
+    
+    SetBackgroundTag = imageio.plugins._swf.SetBackgroundTag
+    assert SetBackgroundTag(1, 2, 3).rgb == SetBackgroundTag((1, 2, 3)).rgb
+    
+    tag = imageio.plugins._swf.ShapeTag(0, (0, 0), (1, 1))
+    assert tag.make_style_change_record(1, 1, (1, 1))
+    assert tag.make_style_change_record()
+    assert (tag.make_straight_edge_record(2, 3).tobytes() == 
+            tag.make_straight_edge_record((2, 3)).tobytes())
+
+
+def test_types():
+    fname1 = get_remote_file('images/stent.swf', test_dir)
+    fname2 = fname1[:-4] + '.out3.swf'
+    
+    for dtype in [np.uint8, np.float32]:
+        for shape in [(100, 100), (100, 100, 1), (100, 100, 3)]:
+            im1 = np.empty(shape, dtype)  # empty is nice for testing nan
+            imageio.mimsave(fname2, [im1], 'swf')
+            im2 = imageio.mimread(fname2, 'swf')[0]
+            assert im2.shape == (100, 100, 4)
+            assert im2.dtype == np.uint8
+            if len(shape) == 3 and dtype == np.uint8:
+                assert (im1[:, :, 0] == im2[:, :, 0]).all()
+    
+
+run_tests_if_main()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/imageio.git



More information about the debian-science-commits mailing list