diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 29b0ea2c7218b81db7e3bfce6cc7c473bc03cbf7..804ea4d681206cf5712db304de05980d4c3757ea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -148,18 +148,6 @@ variables:
     - bash ./.ci/test_template.sh
 
 
-test:2.7:
-  stage: test
-  image: pauldmccarthy/fsleyes-py27-wxpy4-gtk2
-  <<: *test_template
-
-
-test:3.4:
-  stage: test
-  image: pauldmccarthy/fsleyes-py34-wxpy4-gtk2
-  <<: *test_template
-
-
 test:3.5:
   stage: test
   image: pauldmccarthy/fsleyes-py35-wxpy4-gtk2
@@ -172,6 +160,12 @@ test:3.6:
   <<: *test_template
 
 
+test:3.7:
+  stage: test
+  image: pauldmccarthy/fsleyes-py37-wxpy4-gtk2
+  <<: *test_template
+
+
 #############
 # Style stage
 #############
@@ -197,7 +191,7 @@ build-doc:
    - docker
 
   stage: doc
-  image: python:3.5
+  image: python:3.6
 
   script:
     - bash ./.ci/build_doc.sh
@@ -218,7 +212,7 @@ build-pypi-dist:
   <<: *patch_version
 
   stage: build
-  image: python:3.5
+  image: python:3.6
 
   tags:
     - docker
@@ -242,7 +236,7 @@ deploy-doc:
   <<: *setup_ssh
   stage: deploy
   when:  manual
-  image: python:3.5
+  image: python:3.6
 
   tags:
     - docker
@@ -259,7 +253,7 @@ deploy-pypi:
   <<: *setup_ssh
   stage: deploy
   when:  manual
-  image: python:3.5
+  image: python:3.6
 
   tags:
     - docker
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1eee76621c908e57090146d99b9f96a00d72dd72..3d6b6527a88a63dd5eb408202c72f153f2eacb83 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,33 @@ This document contains the ``fslpy`` release history in reverse chronological
 order.
 
 
+2.0.0 (Under development)
+-------------------------
+
+
+Added
+^^^^^
+
+* Simple built-in `.deprecated` decorator.
+
+
+Changed
+^^^^^^^
+
+* Removed support for Python 2.7 and 3.4.
+* Minimum required version of ``nibabel`` is now 2.3.
+* The :class:`.Image` class now fully delegates to ``nibabel`` for managing
+  file handles.
+* Increased the minimum required version of ``dcm2niix``.
+
+
+Removed
+^^^^^^^
+
+
+* Many deprecated items removed.
+
+
 1.12.0 (Sunday October 21st 2018)
 ---------------------------------
 
diff --git a/README.rst b/README.rst
index eadd675aa467956f9cf4d310ac8d5691112ab32c..bdd032b0c4ae2a65ad1ba6c91d6bf317e4a491d0 100644
--- a/README.rst
+++ b/README.rst
@@ -20,6 +20,9 @@ programming library written in Python. It is used by `FSLeyes
 <https://git.fmrib.ox.ac.uk/fsl/fsleyes/fsleyes/>`_.
 
 
+``fslpy`` is tested against Python versions 3.5, 3.6 and 3.7.
+
+
 Installation
 ------------
 
@@ -56,8 +59,8 @@ Some extra dependencies are listed in `requirements.txt
   on the mesh.
 
 
-If you are using Linux, need to install wxPython first, as binaries are not
-available on PyPI. Change the URL for your specific platform::
+If you are using Linux, you need to install wxPython first, as binaries are
+not available on PyPI. Change the URL for your specific platform::
 
     pip install -f https://extras.wxpython.org/wxPython4/extras/linux/gtk2/ubuntu-16.04/ wxpython
 
@@ -76,6 +79,14 @@ Dependencies for testing and documentation are listed in the
 `requirements-dev.txt <requirements-dev.txt>`_ file.
 
 
+Non-Python dependencies
+^^^^^^^^^^^^^^^^^^^^^^^
+
+
+The ``fsl.data.dicom`` module requires the presence of Chris Rorden's
+`dcm2niix <https://github.com/rordenlab/dcm2niix>`_ program.
+
+
 Documentation
 -------------
 
diff --git a/doc/fsl.utils.deprecated.rst b/doc/fsl.utils.deprecated.rst
new file mode 100644
index 0000000000000000000000000000000000000000..913da66ccc4e7bbeefa475a6bf738b96479997c2
--- /dev/null
+++ b/doc/fsl.utils.deprecated.rst
@@ -0,0 +1,7 @@
+``fsl.utils.deprecated``
+========================
+
+.. automodule:: fsl.utils.deprecated
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/doc/fsl.utils.rst b/doc/fsl.utils.rst
index 574c5296a19c8858702680f0ff14f220f85ef89d..d7bf157ed3154a6c3abe9fefe100da7548ea4087 100644
--- a/doc/fsl.utils.rst
+++ b/doc/fsl.utils.rst
@@ -6,6 +6,7 @@
 
    fsl.utils.assertions
    fsl.utils.cache
+   fsl.utils.deprecated
    fsl.utils.ensure
    fsl.utils.fslsub
    fsl.utils.idle
diff --git a/fsl/data/dicom.py b/fsl/data/dicom.py
index 53b7edefe067583d0ea94aaddcc4024b5faa663e..1457c54eed7f4e017500de67d033f5b4b5626052 100644
--- a/fsl/data/dicom.py
+++ b/fsl/data/dicom.py
@@ -32,7 +32,6 @@ import               re
 import               glob
 import               json
 import               logging
-import               deprecation
 
 import nibabel    as nib
 
@@ -80,38 +79,6 @@ class DicomImage(fslimage.Image):
         return self.__dicomDir
 
 
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use metaKeys instead')
-    def keys(self):
-        """Deprecated - use :meth:`.Image.metaKeys`. """
-        return self.metaKeys()
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use metaValues instead')
-    def values(self):
-        """Deprecated - use :meth:`.Image.metaValues`. """
-        return self.metaValues()
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use metaItems instead')
-    def items(self):
-        """Deprecated - use :meth:`.Image.metaItems`. """
-        return self.metaItems()
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use getMeta instead')
-    def get(self, *args, **kwargs):
-        """Deprecated - use :meth:`.Image.getMeta`. """
-        return self.getMeta(*args, **kwargs)
-
-
 @memoize.memoize
 def enabled():
     """Returns ``True`` if ``dcm2niix`` is present, and recent enough,
diff --git a/fsl/data/gifti.py b/fsl/data/gifti.py
index cd5185183d1770db9080f512d6df7b99a61dad51..b0b018c20e5c1955597fb75f6c740fc796ba861e 100644
--- a/fsl/data/gifti.py
+++ b/fsl/data/gifti.py
@@ -25,7 +25,6 @@ are available:
 
 import            glob
 import os.path as op
-import            deprecation
 
 import numpy   as np
 import nibabel as nib
@@ -35,13 +34,15 @@ import fsl.data.constants as constants
 import fsl.data.mesh      as fslmesh
 
 
-ALLOWED_EXTENSIONS = ['.surf.gii']
+# We include '.gii' here because not all surface
+# GIFTIs follow the file suffix convention.
+ALLOWED_EXTENSIONS = ['.surf.gii', '.gii']
 """List of file extensions that a file containing Gifti surface data
 is expected to have.
 """
 
 
-EXTENSION_DESCRIPTIONS = ['GIFTI surface file']
+EXTENSION_DESCRIPTIONS = ['GIFTI surface file', 'GIFTI file']
 """A description for each of the :data:`ALLOWED_EXTENSIONS`. """
 
 
@@ -306,48 +307,3 @@ def relatedFiles(fname, ftypes=None):
             glob.glob(op.join(dirname, '{}*{}'.format(prefix, ftype))))
 
     return [r for r in related if r != path]
-
-
-class GiftiSurface(fslmesh.TriangleMesh):
-    """Deprecated - use GiftiMesh instead. """
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use GiftiMesh instead')
-    def __init__(self, infile, fixWinding=False):
-        """Deprecated - use GiftiMesh instead. """
-        surfimg, vertices, indices = loadGiftiSurface(infile)
-
-        fslmesh.TriangleMesh.__init__(self, vertices, indices, fixWinding)
-
-        name   = fslpath.removeExt(op.basename(infile), ALLOWED_EXTENSIONS)
-        infile = op.abspath(infile)
-
-        self._Mesh__name       = name
-        self._Mesh__dataSource = infile
-        self.surfImg           = surfimg
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use GiftiMesh instead')
-    def loadVertexData(self, dataSource, vertexData=None):
-        """Deprecated - use GiftiMesh instead. """
-
-        if vertexData is None:
-            if dataSource.endswith('.gii'):
-                vertexData = loadGiftiVertexData(dataSource)[1]
-            else:
-                vertexData = None
-
-        return fslmesh.TriangleMesh.loadVertexData(
-            self, dataSource, vertexData)
-
-
-@deprecation.deprecated(deprecated_in='1.6.0',
-                        removed_in='2.0.0',
-                        details='Use loadGiftiMesh instead')
-def loadGiftiSurface(filename):
-    """Deprecated - use loadGiftiMesh instead."""
-    return loadGiftiMesh(filename)
diff --git a/fsl/data/image.py b/fsl/data/image.py
index 6d1a7e4a831b7539805adb6c8068d22d677b7db2..1b407a3f7c773066c13e75b5bb207de03986f9c3 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -29,19 +29,17 @@ and file names:
    getExt
    removeExt
    defaultExt
-   loadIndexedImageFile
 """
 
 
 import                      os
 import os.path           as op
-import                      shutil
 import                      string
 import                      logging
 import                      tempfile
+import                      warnings
 
 import                      six
-import                      deprecation
 import numpy             as np
 import scipy.ndimage     as ndimage
 
@@ -53,6 +51,7 @@ import fsl.utils.transform   as transform
 import fsl.utils.notifier    as notifier
 import fsl.utils.memoize     as memoize
 import fsl.utils.path        as fslpath
+import fsl.utils.deprecated  as deprecated
 import fsl.data.constants    as constants
 import fsl.data.imagewrapper as imagewrapper
 
@@ -469,10 +468,10 @@ class Nifti(notifier.Notifier, meta.Meta):
         self.__worldToVoxMat = transform.invert(self.__voxToWorldMat)
 
         log.debug('Affine changed:\npixdims: '
-                  '{}\nsform: {}\nqform: {}'.format(
-                      header.get_zooms(),
-                      header.get_sform(),
-                      header.get_qform()))
+                  '%s\nsform: %s\nqform: %s',
+                  header.get_zooms(),
+                  header.get_sform(),
+                  header.get_qform())
 
         self.notify(topic='transform')
 
@@ -502,23 +501,6 @@ class Nifti(notifier.Notifier, meta.Meta):
         return len(self.__shape)
 
 
-    @property
-    @deprecation.deprecated(deprecated_in='1.9.0',
-                            removed_in='2.0.0',
-                            details='Use ndim instead')
-    def ndims(self):
-        """Deprecated - use :mod::meth:``ndim`` instead. """
-        return self.ndim
-
-
-    @deprecation.deprecated(deprecated_in='1.1.0',
-                            removed_in='2.0.0',
-                            details='Use ndims instead')
-    def is4DImage(self):
-        """Returns ``True`` if this image is 4D, ``False`` otherwise. """
-        return len(self.__shape) > 3 and self.__shape[3] > 1
-
-
     def getXFormCode(self, code=None):
         """This method returns the code contained in the NIFTI header,
         indicating the space to which the (transformed) image is oriented.
@@ -605,15 +587,6 @@ class Nifti(notifier.Notifier, meta.Meta):
         return npla.det(self.__voxToWorldMat) > 0
 
 
-    @memoize.Instanceify(memoize.memoize)
-    @deprecation.deprecated(deprecated_in='1.2.0',
-                            removed_in='2.0.0',
-                            details='Use voxToScaledVoxMat instead')
-    def voxelsToScaledVoxels(self):
-        """See :meth:`voxToScaledVoxMat`."""
-        return self.voxToScaledVoxMat
-
-
     @property
     def voxToScaledVoxMat(self):
         """Returns a transformation matrix which transforms from voxel
@@ -821,10 +794,8 @@ class Image(Nifti):
                          incrementally updated as more data is read from memory
                          or disk.
 
-        :arg indexed:    If ``True``, and the file is gzipped, it is opened
-                         using the :mod:`indexed_gzip` package. Otherwise the
-                         file is opened by ``nibabel``. Ignored if ``loadData``
-                         is ``True``.
+        :arg indexed:    Deprecated. Has no effect, and will be removed in
+                         ``fslpy`` 3.0.
 
         :arg threaded:   If ``True``, the :class:`.ImageWrapper` will use a
                          separate thread for data range calculation. Defaults
@@ -839,10 +810,14 @@ class Image(Nifti):
         """
 
         nibImage = None
-        fileobj  = None
+
+        if indexed is not False:
+            warnings.warn('The indexed argument is deprecated '
+                          'and has no effect',
+                          category=DeprecationWarning,
+                          stacklevel=2)
 
         if loadData:
-            indexed  = False
             threaded = False
 
         # Take a copy of the header if one has
@@ -868,24 +843,8 @@ class Image(Nifti):
         # The image parameter may be the name of an image file
         if isinstance(image, six.string_types):
 
-            image = op.abspath(addExt(image))
-
-            # Use indexed_gzip to open gzip files
-            # if requested - this provides fast
-            # on-disk access to the compressed
-            # data.
-            #
-            # If using indexed_gzip, we store a
-            # ref to the file object - we'll close
-            # it when we are destroyed.
-            if indexed and image.endswith('.gz'):
-                nibImage, fileobj = loadIndexedImageFile(image)
-
-            # Otherwise we let nibabel
-            # manage the file reference(s)
-            else:
-                nibImage = nib.load(image, **kwargs)
-
+            image      = op.abspath(addExt(image))
+            nibImage   = nib.load(image, **kwargs)
             dataSource = image
 
         # Or a numpy array - we wrap it in a nibabel image,
@@ -943,17 +902,16 @@ class Image(Nifti):
 
         Nifti.__init__(self, nibImage.header)
 
-        self.name                = name
-        self.__lName             = '{}_{}'.format(id(self), self.name)
-        self.__dataSource        = dataSource
-        self.__fileobj           = fileobj
-        self.__threaded          = threaded
-        self.__nibImage          = nibImage
-        self.__saveState         = dataSource is not None
-        self.__imageWrapper      = imagewrapper.ImageWrapper(self.nibImage,
-                                                             self.name,
-                                                             loadData=loadData,
-                                                             threaded=threaded)
+        self.name           = name
+        self.__lName        = '{}_{}'.format(id(self), self.name)
+        self.__dataSource   = dataSource
+        self.__threaded     = threaded
+        self.__nibImage     = nibImage
+        self.__saveState    = dataSource is not None
+        self.__imageWrapper = imagewrapper.ImageWrapper(self.nibImage,
+                                                        self.name,
+                                                        loadData=loadData,
+                                                        threaded=threaded)
 
         # Listen to ourself for changes
         # to the voxToWorldMat, so we
@@ -992,10 +950,6 @@ class Image(Nifti):
         self.__nibImage     = None
         self.__imageWrapper = None
 
-        if getattr(self, '__fileobj', None) is not None:
-            self.__fileobj.close()
-            self.__fileobj = None
-
 
     def getImageWrapper(self):
         """Returns the :class:`.ImageWrapper` instance used to manage
@@ -1126,13 +1080,13 @@ class Image(Nifti):
         # If an image size threshold has not been specified,
         # then we'll calculate the full data range right now.
         if sizethres is None or nbytes < sizethres:
-            log.debug('{}: Forcing calculation of full '
-                      'data range'.format(self.name))
+            log.debug('%s: Forcing calculation of full '
+                      'data range', self.name)
             self.__imageWrapper[:]
 
         else:
-            log.debug('{}: Calculating data range '
-                      'from sample'.format(self.name))
+            log.debug('%s: Calculating data range '
+                      'from sample', self.name)
 
             # Otherwise if the number of values in the
             # image is bigger than the size threshold,
@@ -1152,6 +1106,8 @@ class Image(Nifti):
         if ``filename`` is ``None``.
         """
 
+        import fsl.utils.imcp as imcp
+
         if self.__dataSource is None and filename is None:
             raise ValueError('A file name must be specified')
 
@@ -1164,87 +1120,45 @@ class Image(Nifti):
         if not looksLikeImage(filename):
             filename = addExt(filename, mustExist=False)
 
-        log.debug('Saving {} to {}'.format(self.name, filename))
+        log.debug('Saving %s to %s', self.name, filename)
 
-        # If this Image is not managing its
-        # own file object, and the image is not
-        # memory-mapped, nibabel does all of
-        # the hard work.
-        newnibimage = False
-        sample      = self[(slice(0, 5),) + (0,) * (len(self.shape) - 1)]
-        ismmap      = isinstance(sample, np.memmap)
+        # We save the image out to a temp file,
+        # then close the old image, move the
+        # temp file to the real destination,
+        # then re-open the file.
+        tmphd, tmpfname = tempfile.mkstemp(suffix=getExt(filename))
+        os.close(tmphd)
 
-        if self.__fileobj is None and (not ismmap):
-            nib.save(self.__nibImage, filename)
-
-        # If the image is memory-mapped, we need
-        # to close and re-open the image
-        elif self.__fileobj is None:
-
-            # We save the image out to a temp file,
-            # then close the old image, move the
-            # temp file to the real destination,
-            # then re-open the file.
-            newnibimage     = True
-            tmphd, tmpfname = tempfile.mkstemp(suffix=op.splitext(filename)[1])
-            os.close(tmphd)
-
-            try:
-                nib.save(self.__nibImage, tmpfname)
+        try:
+            nib.save(self.__nibImage, tmpfname)
 
-                self.__nibImage = None
-                self.header     = None
+            # nibabel should close any old
+            # file handles when the image/
+            # header refs are deleted
+            self.__nibImage = None
+            self.header     = None
 
-                shutil.copy(tmpfname, filename)
+            imcp.imcp(tmpfname, filename, overwrite=True)
 
-                self.__nibImage = nib.load(filename)
-                self.header     = self.__nibImage.header
+            self.__nibImage = nib.load(filename)
+            self.header     = self.__nibImage.header
 
-            except Exception:
-                os.remove(tmpfname)
-                raise
+        finally:
+            os.remove(tmpfname)
 
-        # Otherwise we've got our own file
-        # handle to an IndexedGzipFile
-        else:
-            # Currently indexed_gzip does not support
-            # writing. So we're going to use nibabel
-            # to save the image, then close and re-open
-            # the file.
-            #
-            # Unfortunately this means that we'll
-            # lose the file index (and fast random
-            # access) - I'll fix this when I get a
-            # chance to work on indexed_gzip a bit
-            # more.
-            #
-            # Hopefully I should be able to add write
-            # support to indexed_gzip, such that it
-            # re-builds the index while writing the
-            # compressed data. And then be able to
-            # transfer the index generated from the
-            # write to a new read-only file handle.
-            newnibimage = True
-            nib.save(self.__nibImage, filename)
-            self.__fileobj.close()
-            self.__nibImage, self.__fileobj = loadIndexedImageFile(filename)
-            self.header = self.__nibImage.header
-
-        # If we've created a new nibabel image,
+        # Because we've created a new nibabel image,
         # we have to create a new ImageWrapper
         # instance too, as we have just destroyed
         # the nibabel image we gave to the last
         # one.
-        if newnibimage:
-
-            self.__imageWrapper.deregister(self.__lName)
-            self.__imageWrapper = imagewrapper.ImageWrapper(
-                self.nibImage,
-                self.name,
-                loadData=False,
-                dataRange=self.dataRange,
-                threaded=self.__threaded)
-            self.__imageWrapper.register(self.__lName, self.__dataRangeChanged)
+        self.__imageWrapper.deregister(self.__lName)
+        self.__imageWrapper = imagewrapper.ImageWrapper(
+            self.nibImage,
+            self.name,
+            loadData=False,
+            dataRange=self.dataRange,
+            threaded=self.__threaded)
+        self.__imageWrapper.register(self.__lName, self.__dataRangeChanged)
 
         self.__dataSource = filename
         self.__saveState  = True
@@ -1349,7 +1263,7 @@ class Image(Nifti):
         :arg sliceobj: Something which can slice the image data.
         """
 
-        log.debug('{}: __getitem__ [{}]'.format(self.name, sliceobj))
+        log.debug('%s: __getitem__ [%s]', self.name, sliceobj)
 
         return self.__imageWrapper.__getitem__(sliceobj)
 
@@ -1365,9 +1279,8 @@ class Image(Nifti):
         """
         values = np.array(values)
 
-        log.debug('{}: __setitem__ [{} = {}]'.format(self.name,
-                                                     sliceobj,
-                                                     values.shape))
+        log.debug('%s: __setitem__ [%s = %s]',
+                  self.name, sliceobj, values.shape)
 
         with self.__imageWrapper.skip(self.__lName):
 
@@ -1482,115 +1395,7 @@ def defaultExt():
     return options.get(outputType, '.nii.gz')
 
 
+@deprecated.deprecated('2.0.0', '3.0.0', 'Use nibabel.load instead.')
 def loadIndexedImageFile(filename):
-    """Loads the given image file using ``nibabel`` and ``indexed_gzip``.
-
-    Returns a tuple containing the ``nibabel`` NIFTI image, and the open
-    ``IndexedGzipFile`` handle.
-
-    If ``indexed_gzip`` is not present, the image is loaded normally via
-    ``nibabel.load``.
-    """
-
-    import threading
-
-    try:
-        import indexed_gzip as igzip
-    except ImportError:
-        return nib.load(filename), None
-
-    log.debug('Loading {} using indexed gzip'.format(filename))
-
-    # guessed_image_type returns a
-    # ref to one of the Nifti1Image
-    # or Nifti2Image classes.
-    ftype = nib.loadsave.guessed_image_type(filename)
-    fobj  = igzip.SafeIndexedGzipFile(
-        filename=filename,
-        spacing=4194304,
-        readbuf_size=1048576)
-
-    # See the read_segments
-    # function below
-    fobj._arrayproxy_lock = threading.Lock()
-
-    fmap = ftype.make_file_map()
-    fmap['image'].fileobj = fobj
-    image = ftype.from_file_map(fmap)
-
-    return image, fobj
-
-
-@deprecation.deprecated(deprecated_in='1.3.0',
-                        removed_in='2.0.0',
-                        details='Upgrade to nibabel 2.2.0')
-def read_segments(fileobj, segments, n_bytes):
-    """This function is used in place of the
-    ``nibabel.fileslice.read_segments`` function to ensure thread-safe read
-    access to image data via the ``nibabel.arrayproxy.ArrayProxy`` (the
-    ``dataobj`` attribute of a ``nibabel`` image).
-
-    The ``nibabel`` implementation uses multiple calls to ``seek`` and
-    ``read`` to read segments of data from the file. When accessed by multiple
-    threads, these seeks and reads can become intertwined, which causes a read
-    from one thread to read data from the seek location requested by the other
-    thread.
-
-    This implementation protects the seek/read pairs with a
-    ``threading.Lock``, which is added to ``IndexedGzipFile`` instances that
-    are created in the :func:`loadIndexedImageFile` function.
-
-    .. note:: This patch is not required in nibabel 2.2.0 and newer. It will
-              be removed from ``fslpy`` in version 2.0.0.
-    """
-
-    from mmap import mmap
-
-    try:
-        # fileobj is a nibabel.openers.ImageOpener - the
-        # actual file is available via the fobj attribute
-        lock = getattr(fileobj.fobj, '_arrayproxy_lock')
-
-    except AttributeError:
-        return fileslice.orig_read_segments(fileobj, segments, n_bytes)
-
-    if len(segments) == 0:
-        if n_bytes != 0:
-            raise ValueError("No segments, but non-zero n_bytes")
-        return b''
-    if len(segments) == 1:
-        offset, length = segments[0]
-
-        lock.acquire()
-        try:
-            fileobj.seek(offset)
-            bytes = fileobj.read(length)
-        finally:
-            lock.release()
-
-        if len(bytes) != n_bytes:
-            raise ValueError("Whoops, not enough data in file")
-        return bytes
-
-    # More than one segment
-    bytes = mmap(-1, n_bytes)
-    for offset, length in segments:
-
-        lock.acquire()
-        try:
-            fileobj.seek(offset)
-            bytes.write(fileobj.read(length))
-        finally:
-            lock.release()
-
-    if bytes.tell() != n_bytes:
-        raise ValueError("Oh dear, n_bytes does not look right")
-    return bytes
-
-
-# Monkey-patch the above implementation into
-# nibabel. FSLeyes requires at least 2.1.0 -
-# newer versions do not need to be patched.
-if nib.__version__ == '2.1.0':
-    fileslice.orig_read_segments = fileslice.read_segments
-    fileslice.read_segments      = read_segments
+    """Deprecated - this call is equivalent to calling ``nibabel.load``. """
+    return nib.load(filename)
diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py
index 559c0d974f5563edbafef459870cbbb5b504ddac..b73cb879aaf88ffa1e742477b3625fd7da91a590 100644
--- a/fsl/data/imagewrapper.py
+++ b/fsl/data/imagewrapper.py
@@ -37,11 +37,10 @@ get their definitions straight:
 """
 
 
-import logging
-import collections
-import itertools as it
-
-import deprecation
+import                    logging
+import                    collections
+import collections.abc as abc
+import itertools       as it
 
 import numpy     as np
 import nibabel   as nib
@@ -691,7 +690,7 @@ class ImageWrapper(notifier.Notifier):
 
             # If we are slicing a scalar, the
             # assigned value has to be scalar.
-            if expNdims == 0 and isinstance(values, collections.Sequence):
+            if expNdims == 0 and isinstance(values, abc.Sequence):
 
                 if len(values) > 1:
                     raise IndexError('Invalid assignment: [{}] = {}'.format(
@@ -717,16 +716,6 @@ class ImageWrapper(notifier.Notifier):
         self.__updateDataRangeOnWrite(slices, values)
 
 
-@deprecation.deprecated(deprecated_in='1.7.0',
-                        removed_in='2.0.0',
-                        details='Moved to fsl.utils.naninfrange')
-def naninfrange(data):
-    """Deprecated - moved to :mod:`fsl.utils.naninfrange`. """
-
-    from fsl.utils.naninfrange import naninfrange
-    return naninfrange(data)
-
-
 def isValidFancySliceObj(sliceobj, shape):
     """Returns ``True`` if the given ``sliceobj`` is a valid and fancy slice
     object.
@@ -768,15 +757,6 @@ def canonicalSliceObj(sliceobj, shape):
         return nib.fileslice.canonical_slicers(sliceobj, shape)
 
 
-@deprecation.deprecated(deprecated_in='1.7.0',
-                        removed_in='2.0.0',
-                        details='moved to the fsl.data.image module')
-def canonicalShape(shape):
-    """Deprecated - moved to the :mod:`fsl.data.image` module. """
-    from fsl.data.image import canonicalShape
-    return canonicalShape(shape)
-
-
 def expectedShape(sliceobj, shape):
     """Given a slice object, and the shape of an array to which
     that slice object is going to be applied, returns the expected
@@ -849,7 +829,7 @@ def sliceObjToSliceTuple(sliceobj, shape):
 
     # The sliceobj could be a single sliceobj
     # or integer, instead of a tuple
-    if not isinstance(sliceobj, collections.Sequence):
+    if not isinstance(sliceobj, abc.Sequence):
         sliceobj = [sliceobj]
 
     # Turn e.g. array[6] into array[6, :, :]
diff --git a/fsl/data/mesh.py b/fsl/data/mesh.py
index 59fc266c702e3b82427be74fc764ab41d0797c70..55b996069dfcb54b616bcc616b8b70eff90e02a7 100644
--- a/fsl/data/mesh.py
+++ b/fsl/data/mesh.py
@@ -30,9 +30,6 @@ various things with meshes:
 import logging
 import collections
 
-import six
-import deprecation
-
 import os.path as op
 import numpy   as np
 
@@ -763,109 +760,3 @@ def needsFixing(vertices, indices, fnormals, loBounds, hiBounds):
     # If it isn't, we need to flip the
     # triangle winding order.
     return np.dot(n, transform.normalise(camera - vert)) < 0
-
-
-class TriangleMesh(Mesh):
-    """Deprecated - use :class:`fsl.data.mesh.Mesh`, or one of its sub-classes
-    instead.
-    """
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use fsl.data.mesh.Mesh, or one '
-                                    'of its sub-classes instead')
-    def __init__(self, data, indices=None, fixWinding=False):
-
-        import fsl.data.vtk as fslvtk
-
-        if isinstance(data, six.string_types):
-            name       = op.basename(data)
-            dataSource = data
-            mesh       = fslvtk.VTKMesh(data, fixWinding=False)
-            vertices   = mesh.vertices
-            indices    = mesh.indices
-
-        else:
-            name       = 'TriangleMesh'
-            dataSource = None
-            vertices   = data
-
-        Mesh.__init__(self, indices, name=name, dataSource=dataSource)
-        self.addVertices(vertices, 'default', fixWinding=fixWinding)
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use the Mesh class instead')
-    def loadVertexData(self, dataSource, vertexData=None):
-
-        nvertices = self.vertices.shape[0]
-
-        # Currently only white-space delimited
-        # text files are supported
-        if vertexData is None:
-            vertexData = np.loadtxt(dataSource)
-            vertexData.reshape(nvertices, -1)
-
-        if vertexData.shape[0] != nvertices:
-            raise ValueError('Incompatible size: {}'.format(dataSource))
-
-        self.addVertexData(dataSource, vertexData)
-
-        return vertexData
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use bounds instead')
-    def getBounds(self):
-        """Deprecated - use :meth:`bounds` instead. """
-        return self.bounds
-
-
-    @deprecation.deprecated(deprecated_in='1.6.0',
-                            removed_in='2.0.0',
-                            details='Use the Mesh class instead')
-    def getVertexData(self, dataSource):
-        try:
-            return Mesh.getVertexData(self, dataSource)
-        except KeyError:
-            return self.loadVertexData(dataSource)
-
-
-@deprecation.deprecated(deprecated_in='1.6.0',
-                        removed_in='2.0.0',
-                        details='Use fsl.data.vtk.loadVTKPolydataFile instead')
-def loadVTKPolydataFile(*args, **kwargs):
-    """Deprecated - use :func:`fsl.data.vtk.loadVTKPolydataFile` instead. """
-    import fsl.data.vtk as fslvtk
-    return fslvtk.loadVTKPolydataFile(*args, **kwargs)
-
-
-@deprecation.deprecated(deprecated_in='1.6.0',
-                        removed_in='2.0.0',
-                        details='Use fsl.data.vtk.getFIRSTPrefix instead')
-def getFIRSTPrefix(*args, **kwargs):
-    """Deprecated - use :func:`fsl.data.vtk.getFIRSTPrefix` instead. """
-    import fsl.data.vtk as fslvtk
-    return fslvtk.getFIRSTPrefix(*args, **kwargs)
-
-
-@deprecation.deprecated(deprecated_in='1.6.0',
-                        removed_in='2.0.0',
-                        details='Use fsl.data.vtk.findReferenceImage instead')
-def findReferenceImage(*args, **kwargs):
-    """Deprecated - use :func:`fsl.data.vtk.findReferenceImage` instead. """
-    import fsl.data.vtk as fslvtk
-    return fslvtk.findReferenceImage(*args, **kwargs)
-
-
-ALLOWED_EXTENSIONS = ['.vtk']
-"""Deprecated, will be removed in fslpy 2.0.0. Use
-:attr:`fsl.data.vtk.ALLOWED_EXTENSIONS` instead."""
-
-
-EXTENSION_DESCRIPTIONS = ['VTK polygon model file']
-"""Deprecated, will be removed in fslpy 2.0.0. Use
-:attr:`fsl.data.vtk.EXTENSION_DESCRIPTIONS` instead."""
diff --git a/fsl/utils/async.py b/fsl/utils/async.py
deleted file mode 100644
index 520f77ce62c2c5bd4586571469c305369eb00934..0000000000000000000000000000000000000000
--- a/fsl/utils/async.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-#
-# async.py - Deprecaed - use the idle module instead.
-#
-# Author: Paul McCarthy <pauldmccarthy@gmail.com>
-#
-"""This module is deprecated - use the :mod:`.idle` module instead. """
-
-
-import logging
-import warnings
-
-from .idle import (run,  # noqa
-                   idleReset,
-                   getIdleTimeout,
-                   setIdleTimeout,
-                   inIdle,
-                   cancelIdle,
-                   idle,
-                   idleWhen,
-                   wait,
-                   TaskThreadVeto,
-                   TaskThread,
-                   mutex)
-
-
-log = logging.getLogger(__name__)
-
-
-warnings.warn('fsl.utils.async is deprecated and will be removed '
-              'in fslpy 2.0.0 - use fsl.utils.idle instead',
-              DeprecationWarning)
-log.warning('fsl.utils.async is deprecated and will be removed '
-            'in fslpy 2.0.0 - use fsl.utils.idle instead')
diff --git a/fsl/utils/callfsl.py b/fsl/utils/callfsl.py
deleted file mode 100644
index 598df885bc0d595dda214aff7d1670e0336df85e..0000000000000000000000000000000000000000
--- a/fsl/utils/callfsl.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-#
-# callfsl.py - The callFSL function.
-#
-# Author: Paul McCarthy <pauldmccarthy@gmail.com>
-#
-"""Deprecated - use :mod:`fsl.utils.run` instead.
-
-This module provides the :func:`callFSL` function, which can be
-used to call a FSL command, and retrieve the result.
-"""
-
-
-import               logging
-import subprocess as sp
-import os.path    as op
-
-import               deprecation
-
-from fsl.utils.platform import platform as fslplatform
-
-
-log = logging.getLogger(__name__)
-
-
-@deprecation.deprecated(deprecated_in='1.8.0',
-                        removed_in='2.0.0',
-                        details='Use fsl.utils.run.runfsl instead')
-def callFSL(*args):
-    """Call a FSL command and return the result.
-
-    You can pass the command and arguments as a single string, or as a
-    list/tuple.
-    """
-
-    if fslplatform.fsldir is None:
-        raise RuntimeError('FSL cannot be found!')
-
-    # If we've been given a single argument,
-    # assume it is a string containing the
-    # command and its arguments. Otherwise,
-    # assume it is a sequence containing
-    # separate command and arguments.
-    if len(args) == 1:
-        args = args[0].split()
-
-    args    = list(args)
-    args[0] = op.join(fslplatform.fsldir, 'bin', args[0])
-
-    log.debug('callfsl: {}'.format(' '.join(args)))
-
-    result = sp.check_output(args).decode('utf-8')
-
-    log.debug('result: {}'.format(result))
-
-    return result
diff --git a/fsl/utils/deprecated.py b/fsl/utils/deprecated.py
new file mode 100644
index 0000000000000000000000000000000000000000..c79dddfeb9073420c4d5449edd42ed50d9783b40
--- /dev/null
+++ b/fsl/utils/deprecated.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# deprecated.py - Decorator for deprecating things.
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module provides the :func:`deprecated` function, a simple decorator
+for deprecating functions and methods.
+"""
+
+
+
+import functools as ft
+import              inspect
+import              warnings
+
+
+_warned_cache = set()
+"""Used by the :func:`deprecated` function to keep track of whether a warning
+has already been emitted for the use of a deprecated item.
+"""
+
+
+def deprecated(vin=None, rin=None, msg=None):
+    """Decorator to mark a function or method as deprecated. A
+    ``DeprecationWarning`` is raised via the standard ``warnings`` module.
+
+    :arg vin: Optional version - the warning message will mention that the
+              function is deprecated from this version.
+
+    :arg rin: Optional version - the warning message will mention that the
+              function will be removed in this version.
+
+    :arg msg: Optional message to use in the warning.
+    """
+
+
+    if vin is not None and rin is not None:
+        msgfmt = '{{name}} is deprecated from version {vin} and will be ' \
+                 'removed in {rin}.'.format(vin=vin, rin=rin)
+    elif vin is not None:
+        msgfmt = '{{name}} is deprecated from version {vin}.'.format(vin=vin)
+    elif rin is not None:
+        msgfmt = '{{name}} is deprecated and will be removed in ' \
+                 '{rin}.'.format(rin=rin)
+    else:
+        msgfmt = '{{name}} is deprecated.'
+
+    if msg is not None:
+        msgfmt = msgfmt + ' ' + msg
+
+    def wrapper(thing):
+        name = thing.__name__
+
+        def decorator(*args, **kwargs):
+
+            frame = inspect.stack()[1]
+            ident = '{}:{}'.format(frame.filename, frame.lineno)
+
+            if ident not in _warned_cache:
+                warnings.warn(msgfmt.format(name=name),
+                              category=DeprecationWarning,
+                              stacklevel=2)
+                _warned_cache.add(ident)
+
+            return thing(*args, **kwargs)
+        return ft.update_wrapper(decorator, thing)
+
+    return wrapper
diff --git a/fsl/utils/platform.py b/fsl/utils/platform.py
index cd2764ef9a9accc9a9f0f1a9d4f7e7837a687a8f..26706f31814ba805062fc04fdbc49abd205bda6f 100644
--- a/fsl/utils/platform.py
+++ b/fsl/utils/platform.py
@@ -17,7 +17,6 @@ import os
 import os.path as op
 import sys
 import importlib
-import deprecation
 
 import fsl.utils.notifier as notifier
 
@@ -73,34 +72,6 @@ are running the Linux/GTK wx build.
 """
 
 
-@deprecation.deprecated(deprecated_in='1.2.2',
-                        removed_in='2.0.0',
-                        details='Use fsleyes_widgets.isalive instead')
-def isWidgetAlive(widget):
-    """Returns ``True`` if the given ``wx.Window`` object is "alive" (i.e.
-    has not been destroyed), ``False`` otherwise. Works in both wxPython
-    and wxPython/Phoenix.
-
-    .. warning:: Don't try to test whether a ``wx.MenuItem`` has been
-                 destroyed, as it will probably result in segmentation
-                 faults. Check the parent ``wx.Menu`` instead.
-    """
-
-    import wx
-
-    if platform.wxFlavour == platform.WX_PHOENIX:
-        excType = RuntimeError
-    elif platform.wxFlavour == platform.WX_PYTHON:
-        excType = wx.PyDeadObjectError
-
-    try:
-        widget.GetParent()
-        return True
-
-    except excType:
-        return False
-
-
 class Platform(notifier.Notifier):
     """The ``Platform`` class contains a handful of properties which contain
     information about the platform we are running on.
@@ -138,7 +109,6 @@ class Platform(notifier.Notifier):
         self.WX_MAC_COCOA  = WX_MAC_COCOA
         self.WX_MAC_CARBON = WX_MAC_CARBON
         self.WX_GTK        = WX_GTK
-        self.isWidgetAlive = isWidgetAlive
 
         self.__inSSHSession = False
         self.__inVNCSession = False
diff --git a/fsl/utils/run.py b/fsl/utils/run.py
index a5b9df816f9605edf62b8daa62d082142a898057..e4a4c51961c74c23554fca53cb3fd0394000467b 100644
--- a/fsl/utils/run.py
+++ b/fsl/utils/run.py
@@ -144,10 +144,6 @@ def run(*args, **kwargs):
                    exception is not raised.  Ignored if ``submit`` is
                    specified.
 
-    :arg err:      Deprecated - use ``stderr`` instead.
-
-    :arg ret:      Deprecated - use ``exitcode`` instead.
-
     :arg submit:   Must be passed as a keyword argument. Defaults to ``None``.
                    If ``True``, the command is submitted as a cluster job via
                    the :func:`.fslsub.submit` function.  May also be a
@@ -175,17 +171,6 @@ def run(*args, **kwargs):
                    ``stderr``, and ``exitcode`` arguments.
     """
 
-    if 'err' in kwargs:
-        warnings.warn('err is deprecated and will be removed '
-                      'in fslpy 2.0.0 - use stderr instead',
-                      DeprecationWarning)
-        kwargs['stderr'] = kwargs.get('stderr', kwargs['err'])
-    if 'ret' in kwargs:
-        warnings.warn('ret is deprecated and will be removed '
-                      'in fslpy 2.0.0 - use exitcode instead',
-                      DeprecationWarning)
-        kwargs['exitcode'] = kwargs.get('exitcode', kwargs['ret'])
-
     returnStdout   = kwargs.get('stdout',   True)
     returnStderr   = kwargs.get('stderr',   False)
     returnExitcode = kwargs.get('exitcode', False)
diff --git a/fsl/utils/transform.py b/fsl/utils/transform.py
index 2a5610dd89c172bec7b349d6e3400ad3b3cf21f6..f574eaa764a8e337cb7616f7eaef254e0dfd3a9d 100644
--- a/fsl/utils/transform.py
+++ b/fsl/utils/transform.py
@@ -34,9 +34,9 @@ And a few more functions are provided for working with vectors:
    normalise
 """
 
-import numpy        as np
-import numpy.linalg as linalg
-import collections
+import numpy           as np
+import numpy.linalg    as linalg
+import collections.abc as abc
 
 
 def invert(x):
@@ -94,7 +94,7 @@ def scaleOffsetXform(scales, offsets):
     :returns:     A ``numpy.float32`` array of size :math:`4 \\times 4`.
     """
 
-    oktypes = (collections.Sequence, np.ndarray)
+    oktypes = (abc.Sequence, np.ndarray)
 
     if not isinstance(scales,  oktypes): scales  = [scales]
     if not isinstance(offsets, oktypes): offsets = [offsets]
@@ -398,7 +398,7 @@ def axisBounds(shape,
     if axes is None:
         axes = [0, 1, 2]
 
-    elif not isinstance(axes, collections.Iterable):
+    elif not isinstance(axes, abc.Iterable):
         scalar = True
         axes   = [axes]
 
@@ -507,13 +507,13 @@ def _fillPoints(p, axes):
     or an ``N*2`` or ``N*3`` array.
     """
 
-    if not isinstance(p, collections.Iterable): p = [p]
+    if not isinstance(p, abc.Iterable): p = [p]
 
     p = np.array(p)
 
     if axes is None: return p
 
-    if not isinstance(axes, collections.Iterable): axes = [axes]
+    if not isinstance(axes, abc.Iterable): axes = [axes]
 
     if p.ndim == 1:
         p = p.reshape((len(p), 1))
diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py
index 6728a23272696f295227291d723101cd366e7be5..d3fc2db2379224aa1f3582a1c8e6a96afc8ac164 100644
--- a/fsl/wrappers/wrapperutils.py
+++ b/fsl/wrappers/wrapperutils.py
@@ -85,21 +85,22 @@ and returned::
 """
 
 
-import itertools as it
-import os.path   as op
-import              os
-import              re
-import              sys
-import              glob
-import              random
-import              string
-import              fnmatch
-import              inspect
-import              logging
-import              tempfile
-import              warnings
-import              functools
-import              collections
+import itertools       as it
+import os.path         as op
+import collections.abc as abc
+import                    os
+import                    re
+import                    sys
+import                    glob
+import                    random
+import                    string
+import                    fnmatch
+import                    inspect
+import                    logging
+import                    tempfile
+import                    warnings
+import                    functools
+
 
 import            six
 import nibabel as nib
@@ -282,7 +283,7 @@ def applyArgStyle(style, valsep=None, argmap=None, valmap=None, **kwargs):
 
     # always returns a sequence
     def fmtval(val):
-        if     isinstance(val, collections.Sequence) and \
+        if     isinstance(val, abc.Sequence) and \
            not isinstance(val, six.string_types):
 
             val = [str(v) for v in val]
diff --git a/requirements-extra.txt b/requirements-extra.txt
index fa62f9919dfc96bbca30c655db0cb1ae8308641c..6d8a4c4b960fb3d8447940c410b486765be4fc41 100644
--- a/requirements-extra.txt
+++ b/requirements-extra.txt
@@ -1,4 +1,4 @@
-indexed_gzip>=0.7.0,<1
-wxpython>=3.0.2.0,<4.1
-trimesh>=2.22.28,<3
+indexed_gzip>=0.7.0
+wxpython==4.*
+trimesh>=2.22.28
 rtree==0.8.3
diff --git a/requirements.txt b/requirements.txt
index 51e205639f8e3e8cc298b82ee5116106b036e907..ef041ad9cd02c2d53af3875cb4cecf7d2d077d5f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
 six==1.*
-deprecation>=1.*,<=2.*
 numpy==1.*
-scipy>=0.18,<2
-nibabel==2.*
+scipy>=0.18
+nibabel>=2.3.1
diff --git a/setup.py b/setup.py
index d1212cc37dd875176bbbb267cdb5135787c0f00d..63a6da5b801304ac8f3867c7ab3832eb198aac5e 100644
--- a/setup.py
+++ b/setup.py
@@ -102,10 +102,9 @@ setup(
         'Development Status :: 3 - Alpha',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: Apache Software License',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
         'Topic :: Software Development :: Libraries :: Python Modules'],
 
     packages=packages,
diff --git a/tests/test_atlases.py b/tests/test_atlases.py
index 9853c5a73a2b6d73cede0606638906f9f817a40d..80be87c74736f39cfa4134a3efa4e804d2ce0db3 100644
--- a/tests/test_atlases.py
+++ b/tests/test_atlases.py
@@ -225,7 +225,7 @@ def test_load_atlas():
     reg.rescanAtlases()
 
     probatlas    = reg.loadAtlas('harvardoxford-cortical',
-                                 indexed=True, calcRange=False, loadData=False)
+                                 calcRange=False, loadData=False)
     probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True)
     lblatlas     = reg.loadAtlas('talairach')
 
@@ -240,7 +240,7 @@ def test_find():
     reg.rescanAtlases()
 
     probatlas    = reg.loadAtlas('harvardoxford-cortical',
-                                 indexed=True, calcRange=False, loadData=False)
+                                 calcRange=False, loadData=False)
     probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True)
     lblatlas     = reg.loadAtlas('talairach')
 
@@ -273,7 +273,7 @@ def test_prepareMask():
     reg.rescanAtlases()
 
     probatlas    = reg.loadAtlas('harvardoxford-cortical',
-                                 indexed=True, loadData=False, calcRange=False)
+                                 loadData=False, calcRange=False)
     probsumatlas = reg.loadAtlas('harvardoxford-cortical', loadSummary=True)
     lblatlas     = reg.loadAtlas('talairach')
 
diff --git a/tests/test_atlases_query.py b/tests/test_atlases_query.py
index 29676a09bb81fc0fb280bb602a80e34c8eb72825..f90c8d40618b78ea7f33e0c8168623fcdac31d7c 100644
--- a/tests/test_atlases_query.py
+++ b/tests/test_atlases_query.py
@@ -45,8 +45,7 @@ def _get_atlas(atlasID, res, summary=False):
             kwargs = {}
         else:
             kwargs = {'loadData'  : False,
-                      'calcRange' : False,
-                      'indexed'   : True}
+                      'calcRange' : False}
 
         atlas = fslatlases.loadAtlas(atlasID,
                                      loadSummary=summary,
diff --git a/tests/test_atlasq_ohi.py b/tests/test_atlasq_ohi.py
index 36279ce693ec0cbeb7c783bf3ece588f6c32431d..2c0e4e7aadbeea076278f215b56a5e851458f43d 100644
--- a/tests/test_atlasq_ohi.py
+++ b/tests/test_atlasq_ohi.py
@@ -98,7 +98,6 @@ def test_coords(seed):
         atlas = fslatlases.loadAtlas(
             ad.atlasID,
             resolution=2,
-            indexed=True,
             calcRange=False,
             loadData=False)
 
diff --git a/tests/test_atlasq_query.py b/tests/test_atlasq_query.py
index 5ba48f9190fa9d23ec13d7fd231ec16fa283b66c..987cd01141211a28b26739bdb524d8bee3d9f039 100644
--- a/tests/test_atlasq_query.py
+++ b/tests/test_atlasq_query.py
@@ -241,7 +241,7 @@ def _eval_coord_voxel_query(
         else:
             exp = [q_type, squery, 'No label']
 
-        _stdout = re.sub('\s+', ' ', stdout).strip()
+        _stdout = re.sub(r'\s+', ' ', stdout).strip()
         assert _stdout.strip() == ' '.join(exp).strip()
 
     def evalProbNormalOutput(expprops):
@@ -442,7 +442,6 @@ def test_bad_mask(seed):
             atlas  = fslatlases.loadAtlas(
                 atlasID,
                 loadSummary=use_label,
-                indexed=True,
                 loadData=False,
                 calcRange=False)
             ashape = list(atlas.shape[:3])
diff --git a/tests/test_callfsl.py b/tests/test_callfsl.py
deleted file mode 100644
index 25c972e386a2708a46f5fbcfeb139a995f63868b..0000000000000000000000000000000000000000
--- a/tests/test_callfsl.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env python
-#
-# test_callfsl.py -
-#
-# Author: Paul McCarthy <pauldmccarthy@gmail.com>
-#
-
-import               os
-import os.path    as op
-import subprocess as sp
-
-import numpy      as np
-import nibabel    as nib
-
-import mock
-import pytest
-
-import fsl.utils.callfsl                  as callfsl
-from   fsl.utils.platform import platform as fslplatform
-
-import tests
-
-
-pytestmark = pytest.mark.fsltest
-
-
-def setup_module():
-    fsldir = os.environ.get('FSLDIR', None)
-    if fsldir is None or not op.exists(fsldir):
-        raise Exception('FSLDIR is not set - callfsl tests cannot be run')
-
-
-# mock subprocess.check_output command
-# which expects 'fslstats -m filename'
-# or 'fslinfo ...'
-def mock_check_output(args):
-    if args[0].endswith('fslinfo'):
-        return 'info'.encode('utf-8')
-
-    img = nib.load(args[-2])
-    return str(img.get_data().mean()).encode('utf-8')
-
-
-def test_callfsl():
-
-    with tests.testdir() as testdir:
-
-        fname = op.join(testdir, 'myimage.nii.gz')
-
-        img   = tests.make_random_image(fname)
-        img   = img.get_data()
-
-        # Pass a single string
-        cmd    = 'fslstats {} -m'.format(fname)
-
-        with mock.patch('fsl.utils.callfsl.sp.check_output',
-                        mock_check_output):
-            result = callfsl.callFSL(cmd)
-
-            assert np.isclose(float(result), img.mean())
-
-            # Or pass a list of args
-            result = callfsl.callFSL(*cmd.split())
-            assert np.isclose(float(result), img.mean())
-
-        # Bad commands
-        badcmds = ['fslblob', 'fslstats notafile']
-
-        for cmd in badcmds:
-            with pytest.raises((OSError, sp.CalledProcessError)):
-                callfsl.callFSL(cmd)
-
-        # No FSL - should crash
-        cmd = 'fslinfo {}'.format(fname)
-        with mock.patch('fsl.utils.callfsl.sp.check_output',
-                        mock_check_output):
-            callfsl.callFSL(cmd)
-
-        try:
-            oldval = fslplatform.fsldir
-            fslplatform.fsldir = None
-            with pytest.raises(Exception):
-                callfsl.callFSL(cmd)
-        finally:
-            fslplatform.fsldir = oldval
diff --git a/tests/test_idle.py b/tests/test_idle.py
index afa45b5ad3e1236b2d4fee4644b7f8f4659c1a03..13a8e3c98d679fb1fe5dcb10044fe163fe6f852b 100644
--- a/tests/test_idle.py
+++ b/tests/test_idle.py
@@ -89,7 +89,7 @@ def _wait_for_idle_loop_to_clear():
         idle.idle(busywait)
 
         while not idleDone[0]:
-            wx.Yield()
+            wx.GetApp().Yield()
 
 
 @pytest.mark.wxtest
diff --git a/tests/test_image.py b/tests/test_image.py
index 84a3d244542163e7c164a26a0408c336447af250..d468b4b76a2216b8045b7f2b15b947d4136c6510 100644
--- a/tests/test_image.py
+++ b/tests/test_image.py
@@ -307,7 +307,6 @@ def _test_Image_atts(imgtype):
             assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims)
 
             assert i.ndim       == expndims
-            assert i.ndims      == expndims
             assert i.dtype      == dtype
             assert i.name       == op.basename(path)
             assert i.dataSource == fslpath.addExt(path,
@@ -1048,9 +1047,9 @@ def test_image_resample(seed):
         fname = op.join(td, 'test.nii')
 
         # Random base image shapes
-        for i in range(50):
+        for i in range(25):
 
-            shape = np.random.randint(5, 100, 3)
+            shape = np.random.randint(5, 50, 3)
             make_random_image(fname, shape)
             img = fslimage.Image(fname, mmap=False)
 
@@ -1062,7 +1061,7 @@ def test_image_resample(seed):
             # Random resampled image shapes
             for j in range(10):
 
-                rshape        = np.random.randint(5, 100, 3)
+                rshape        = np.random.randint(5, 50, 3)
                 resampled, xf = img.resample(rshape, order=0)
 
                 img.save('base.nii.gz')
diff --git a/tests/test_image_advanced.py b/tests/test_image_advanced.py
index 69756278781902081f2b8c3250d7eb6d40319a96..2be8678b603a9d716dc461f770ae65acd89c9173 100644
--- a/tests/test_image_advanced.py
+++ b/tests/test_image_advanced.py
@@ -57,7 +57,6 @@ def _test_image_indexed(threaded):
             filename,
             loadData=False,
             calcRange=False,
-            indexed=True,
             threaded=threaded)
 
         # First iteration through the image
@@ -72,6 +71,17 @@ def _test_image_indexed(threaded):
             assert img.dataRange == (0, vol)
         end1 = time.time()
 
+        # Double check that indexed_gzip is
+        # being used (the internal _opener
+        # attribute is not created until
+        # after the first data access)
+        try:
+            import indexed_gzip as igzip
+            assert isinstance(img.nibImage.dataobj._opener.fobj,
+                              igzip.IndexedGzipFile)
+        except ImportError:
+            pass
+
         # Second iteration through
         start2 = time.time()
         for vol in range(data.shape[-1]):
@@ -108,7 +118,6 @@ def _test_image_indexed_read4D(threaded):
             filename,
             loadData=False,
             calcRange=False,
-            indexed=True,
             threaded=threaded)
 
         # Test reading slice through
@@ -126,6 +135,14 @@ def _test_image_indexed_read4D(threaded):
 
             assert np.all(data == np.arange(nvols))
 
+        # double check we're indexing as expected
+        try:
+            import indexed_gzip as igzip
+            assert isinstance(img.nibImage.dataobj._opener.fobj,
+                              igzip.IndexedGzipFile)
+        except ImportError:
+            pass
+
 
 @pytest.mark.igziptest
 @pytest.mark.longtest
@@ -150,13 +167,20 @@ def _test_image_indexed_save(threaded):
             filename,
             loadData=False,
             calcRange=False,
-            indexed=True,
             threaded=threaded)
 
         # access some data
         img[..., 0]
         img[..., 40]
 
+        # double check that igzip is being used
+        try:
+            import indexed_gzip as igzip
+            assert isinstance(img.nibImage.dataobj._opener.fobj,
+                              igzip.IndexedGzipFile)
+        except ImportError:
+            pass
+
         if threaded:
             img.getImageWrapper().getTaskThread().waitUntilIdle()
 
@@ -203,10 +227,8 @@ def _test_image_indexed_save(threaded):
 
 
 @pytest.mark.longtest
-@pytest.mark.igziptest
 def test_image_no_calcRange_threaded():   _test_image_no_calcRange(True)
 @pytest.mark.longtest
-@pytest.mark.igziptest
 def test_image_no_calcRange_unthreaded(): _test_image_no_calcRange(False)
 def _test_image_no_calcRange(threaded):
 
@@ -228,7 +250,6 @@ def _test_image_no_calcRange(threaded):
     # cal_min/max if it is unknown
     assert img.dataRange == (95, 643)
 
-
     for i in [0, 7, 40]:
         img[..., i]
         if threaded:
@@ -238,10 +259,8 @@ def _test_image_no_calcRange(threaded):
 
 
 @pytest.mark.longtest
-@pytest.mark.igziptest
 def test_image_calcRange_threaded():   _test_image_calcRange(True)
 @pytest.mark.longtest
-@pytest.mark.igziptest
 def test_image_calcRange_unthreaded(): _test_image_calcRange(False)
 def _test_image_calcRange(threaded):
 
diff --git a/tests/test_imagewrapper.py b/tests/test_imagewrapper.py
index 1237cd55d02966fc5596c32825dcec071f9c8b2e..61ed5c21bbdbccef14f769b4547f9fdb8c6fd8a4 100644
--- a/tests/test_imagewrapper.py
+++ b/tests/test_imagewrapper.py
@@ -465,7 +465,10 @@ def _test_expansion(coverage, slices, volumes, expansions):
     # coverage, or in one of the expansions.
     dimranges = []
     for d in range(ndims):
-        dimranges.append(np.linspace(nc[0, d], nc[1, d], nc[1, d] / 5, dtype=np.uint32))
+        dimranges.append(np.linspace(nc[0, d],
+                                     nc[1, d],
+                                     int(nc[1, d] / 5),
+                                     dtype=np.uint32))
 
     points = it.product(*dimranges)
 
diff --git a/tests/test_platform.py b/tests/test_platform.py
index 4531f7534a0950ddf86753c88d8a02e09606f02d..67aefe094bedd91c19772c3d842841134faa07f8 100644
--- a/tests/test_platform.py
+++ b/tests/test_platform.py
@@ -208,33 +208,3 @@ def test_detect_ssh():
         p = fslplatform.Platform()
         assert not p.inSSHSession
         assert not p.inVNCSession
-
-
-@pytest.mark.wxtest
-def test_IsWidgetAlive():
-
-    import wx
-
-    passed = [False]
-    app    = wx.App()
-    frame  = wx.Frame(None)
-    btn    = wx.Button(frame)
-    frame.Show()
-
-    def runtest():
-
-        try:
-
-            passed[0] = fslplatform.isWidgetAlive(btn)
-
-            btn.Destroy()
-
-            passed[0] = passed[0] and (not fslplatform.isWidgetAlive(btn))
-        finally:
-            frame.Destroy()
-            app.ExitMainLoop()
-
-    wx.CallLater(500, runtest)
-    app.MainLoop()
-
-    assert passed[0]
diff --git a/tests/test_vest.py b/tests/test_vest.py
index 84638a3ce0227fb00b9dbec438cc112cee05e47e..df74b5b8cf656f3a630d7cd29c2da988ab2b5321 100644
--- a/tests/test_vest.py
+++ b/tests/test_vest.py
@@ -9,6 +9,7 @@
 import os.path as op
 import            shutil
 import            tempfile
+import            warnings
 
 import numpy   as np
 import            pytest
@@ -166,9 +167,12 @@ def test_loadVestLutFile():
         for i in range(4):
             f       = testfiles[i]
             d       = testdata[ i]
-            dnorm   = (d - d.min()) / (d.max() - d.min())
-            lutnorm = vest.loadVestLutFile(f)
-            lut     = vest.loadVestLutFile(f, normalise=False)
+
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore')
+                dnorm   = (d - d.min()) / (d.max() - d.min())
+                lutnorm = vest.loadVestLutFile(f)
+                lut     = vest.loadVestLutFile(f, normalise=False)
 
             assert lut.shape     == d.shape
             assert lutnorm.shape == dnorm.shape